如何使用matplotlib在while循环中实时绘图?

Chr*_*ris 211 python real-time matplotlib while-loop

我试图使用OpenCV实时绘制来自摄像机的一些数据.但是,实时绘图(使用matplotlib)似乎不起作用.

我把这个问题分成了这个简单的例子:

fig = plt.figure()
plt.axis([0, 1000, 0, 1])

i = 0
x = list()
y = list()

while i < 1000:
    temp_y = np.random.random()
    x.append(i)
    y.append(temp_y)
    plt.scatter(i, temp_y)
    i += 1
    plt.show()
Run Code Online (Sandbox Code Playgroud)

我希望这个例子可以单独绘制1000个点.实际发生的是窗口弹出第一个点显示(确定),然后在填充图表的其余部分之前等待循环完成.

有什么想法,为什么我没有看到一次填充一个点?

Vel*_*ker 285

这是有问题的代码的工作版本(至少需要2011-11-14版本的Matplotlib 1.1.0版本):

import numpy as np
import matplotlib.pyplot as plt

plt.axis([0, 10, 0, 1])

for i in range(10):
    y = np.random.random()
    plt.scatter(i, y)
    plt.pause(0.05)

plt.show()
Run Code Online (Sandbox Code Playgroud)

请注意一些变化:

  1. 调用plt.pause(0.05)两者绘制新数据并运行GUI的事件循环(允许鼠标交互).

  • instead of plt.show() and plt.draw() just replace plt.draw() with plt.pause(0.1) (33认同)
  • 这个答案需要对x/y数据的先验知识...这是不需要的:我更喜欢1.不要调用`plt.axis()`而是创建两个列表x和y并调用`plt.plot (x,y)`2.在循环中,将新数据值附加到两个列表中3.调用`plt.gca().lines [0] .set_xdata(x); .plt.gca()线[0] .set_ydata(Y); .plt.gca()relim(); .plt.gca()autoscale_view(); plt.pause(0.05);` (4认同)
  • 这在Python2中对我有用.在Python3中它没有.它将在渲染绘图窗口后暂停循环.但是在将plt.show()方法移动到循环之后...它为我解决了Python3的问题. (3认同)
  • 在Win64/Anaconda matplotlib .__ version__ 1.5.0上无效.初始数字窗口打开,但没有显示任何内容,它一直处于阻塞状态,直到我关闭它 (3认同)

ali*_*i_m 68

如果您对实时绘图感兴趣,我建议您查看matplotlib的动画API.特别是,使用blit以避免在每个帧上重新绘制背景可以为您带来显着的速度增益(~10x):

#!/usr/bin/env python

import numpy as np
import time
import matplotlib
matplotlib.use('GTKAgg')
from matplotlib import pyplot as plt


def randomwalk(dims=(256, 256), n=20, sigma=5, alpha=0.95, seed=1):
    """ A simple random walk with memory """

    r, c = dims
    gen = np.random.RandomState(seed)
    pos = gen.rand(2, n) * ((r,), (c,))
    old_delta = gen.randn(2, n) * sigma

    while True:
        delta = (1. - alpha) * gen.randn(2, n) * sigma + alpha * old_delta
        pos += delta
        for ii in xrange(n):
            if not (0. <= pos[0, ii] < r):
                pos[0, ii] = abs(pos[0, ii] % r)
            if not (0. <= pos[1, ii] < c):
                pos[1, ii] = abs(pos[1, ii] % c)
        old_delta = delta
        yield pos


def run(niter=1000, doblit=True):
    """
    Display the simulation using matplotlib, optionally using blit for speed
    """

    fig, ax = plt.subplots(1, 1)
    ax.set_aspect('equal')
    ax.set_xlim(0, 255)
    ax.set_ylim(0, 255)
    ax.hold(True)
    rw = randomwalk()
    x, y = rw.next()

    plt.show(False)
    plt.draw()

    if doblit:
        # cache the background
        background = fig.canvas.copy_from_bbox(ax.bbox)

    points = ax.plot(x, y, 'o')[0]
    tic = time.time()

    for ii in xrange(niter):

        # update the xy data
        x, y = rw.next()
        points.set_data(x, y)

        if doblit:
            # restore background
            fig.canvas.restore_region(background)

            # redraw just the points
            ax.draw_artist(points)

            # fill in the axes rectangle
            fig.canvas.blit(ax.bbox)

        else:
            # redraw everything
            fig.canvas.draw()

    plt.close(fig)
    print "Blit = %s, average FPS: %.2f" % (
        str(doblit), niter / (time.time() - tic))

if __name__ == '__main__':
    run(doblit=False)
    run(doblit=True)
Run Code Online (Sandbox Code Playgroud)

输出:

Blit = False, average FPS: 54.37
Blit = True, average FPS: 438.27
Run Code Online (Sandbox Code Playgroud)

  • `blit()`的意图/动机似乎非常"改善实时绘图"?如果你有一个matplotlib开发人员/博客讨论为什么/目的/意图/动机会很棒.(看起来这个新的blit操作会将Matplotlib转换为仅用于离线或非常缓慢变化的数据到现在你可以使用Matplotlib以非常快速的更新数据......几乎像示波器). (2认同)
  • 我发现这种方法会使绘图窗口无响应:我无法与它交互,这样做可能会使其崩溃。 (2认同)
  • 对于那些遇到“gtk not found”问题的人,它可以在不同的后端(我使用“TKAgg”)下正常工作。为了找到受支持的支持,我使用了以下解决方案:/sf/ask/229963541/ (2认同)
  • 此答案中的链接似乎不再有效。这可能是一个最新的链接:http://scipy-cookbook.readthedocs.io/items/Matplotlib_Animations.html?highlight=animations (2认同)

Bre*_*arn 33

show可能不是这个的最佳选择.我会做的是pyplot.draw()改用.您还可能希望time.sleep(0.05)在循环中包含一个小的时间延迟(例如),以便您可以看到正在发生的情节.如果我对你的例子进行了这些修改,它对我有用,我看到每个点一次出现一个.

  • 我有非常相似的代码部分,当我尝试你的解决方案(绘制而不是显示和时间延迟)时,python根本没有打开一个数字窗口,只是循环... (9认同)

K.M*_*ier 31

我知道我回答这个问题有点晚了.不过,我前段时间制作了一些代码来绘制实时图表,我想分享一下:

###################################################################
#                                                                 #
#                    PLOT A LIVE GRAPH (PyQt4)                    #
#                  -----------------------------                  #
#            EMBED A MATPLOTLIB ANIMATION INSIDE YOUR             #
#            OWN GUI!                                             #
#                                                                 #
###################################################################


import sys
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt4Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading


def setCustomSize(x, width, height):
    sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
    sizePolicy.setHorizontalStretch(0)
    sizePolicy.setVerticalStretch(0)
    sizePolicy.setHeightForWidth(x.sizePolicy().hasHeightForWidth())
    x.setSizePolicy(sizePolicy)
    x.setMinimumSize(QtCore.QSize(width, height))
    x.setMaximumSize(QtCore.QSize(width, height))

''''''

class CustomMainWindow(QtGui.QMainWindow):

    def __init__(self):

        super(CustomMainWindow, self).__init__()

        # Define the geometry of the main window
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("my first window")

        # Create FRAME_A
        self.FRAME_A = QtGui.QFrame(self)
        self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QtGui.QColor(210,210,235,255).name())
        self.LAYOUT_A = QtGui.QGridLayout()
        self.FRAME_A.setLayout(self.LAYOUT_A)
        self.setCentralWidget(self.FRAME_A)

        # Place the zoom button
        self.zoomBtn = QtGui.QPushButton(text = 'zoom')
        setCustomSize(self.zoomBtn, 100, 50)
        self.zoomBtn.clicked.connect(self.zoomBtnAction)
        self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0))

        # Place the matplotlib figure
        self.myFig = CustomFigCanvas()
        self.LAYOUT_A.addWidget(self.myFig, *(0,1))

        # Add the callbackfunc to ..
        myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
        myDataLoop.start()

        self.show()

    ''''''


    def zoomBtnAction(self):
        print("zoom in")
        self.myFig.zoomIn(5)

    ''''''

    def addData_callbackFunc(self, value):
        # print("Add data: " + str(value))
        self.myFig.addData(value)



''' End Class '''


class CustomFigCanvas(FigureCanvas, TimedAnimation):

    def __init__(self):

        self.addedData = []
        print(matplotlib.__version__)

        # The data
        self.xlim = 200
        self.n = np.linspace(0, self.xlim - 1, self.xlim)
        a = []
        b = []
        a.append(2.0)
        a.append(4.0)
        a.append(2.0)
        b.append(4.0)
        b.append(3.0)
        b.append(4.0)
        self.y = (self.n * 0.0) + 50

        # The window
        self.fig = Figure(figsize=(5,5), dpi=100)
        self.ax1 = self.fig.add_subplot(111)


        # self.ax1 settings
        self.ax1.set_xlabel('time')
        self.ax1.set_ylabel('raw data')
        self.line1 = Line2D([], [], color='blue')
        self.line1_tail = Line2D([], [], color='red', linewidth=2)
        self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
        self.ax1.add_line(self.line1)
        self.ax1.add_line(self.line1_tail)
        self.ax1.add_line(self.line1_head)
        self.ax1.set_xlim(0, self.xlim - 1)
        self.ax1.set_ylim(0, 100)


        FigureCanvas.__init__(self, self.fig)
        TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)

    def new_frame_seq(self):
        return iter(range(self.n.size))

    def _init_draw(self):
        lines = [self.line1, self.line1_tail, self.line1_head]
        for l in lines:
            l.set_data([], [])

    def addData(self, value):
        self.addedData.append(value)

    def zoomIn(self, value):
        bottom = self.ax1.get_ylim()[0]
        top = self.ax1.get_ylim()[1]
        bottom += value
        top -= value
        self.ax1.set_ylim(bottom,top)
        self.draw()


    def _step(self, *args):
        # Extends the _step() method for the TimedAnimation class.
        try:
            TimedAnimation._step(self, *args)
        except Exception as e:
            self.abc += 1
            print(str(self.abc))
            TimedAnimation._stop(self)
            pass

    def _draw_frame(self, framedata):
        margin = 2
        while(len(self.addedData) > 0):
            self.y = np.roll(self.y, -1)
            self.y[-1] = self.addedData[0]
            del(self.addedData[0])


        self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
        self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
        self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
        self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]

''' End Class '''

# You need to setup a signal slot mechanism, to 
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QtCore.QObject):
    data_signal = QtCore.pyqtSignal(float)

''' End Class '''


def dataSendLoop(addData_callbackFunc):
    # Setup the signal-slot mechanism.
    mySrc = Communicate()
    mySrc.data_signal.connect(addData_callbackFunc)

    # Simulate some data
    n = np.linspace(0, 499, 500)
    y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
    i = 0

    while(True):
        if(i > 499):
            i = 0
        time.sleep(0.1)
        mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
        i += 1
    ###
###


if __name__== '__main__':
    app = QtGui.QApplication(sys.argv)
    QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
    myGUI = CustomMainWindow()
    sys.exit(app.exec_())

''''''
Run Code Online (Sandbox Code Playgroud)

试一试吧.将此代码复制粘贴到新的python文件中,然后运行它.你应该得到一个漂亮,流畅的图表:

在此输入图像描述

  • 非常感谢基本代码。它帮助我通过根据您的代码修改和添加功能来构建一些简单的 UI。它节省了我的时间 = ] (3认同)
  • 我注意到当您关闭窗口时,“dataSendLoop”线程继续在后台运行。所以我添加了 `daemon = True` 关键字来解决这个问题。 (2认同)
  • @DavidCian,没有比带有工作代码的帖子更好的选择了 (2认同)
  • @Paul Cool,[此处](https://brainflow.org/2021-07-05-real-time-example/),来自我自己的用例。如果工作代码始终是好代码,那么软件工程就不会是一件事;)。事实证明,实际上 PyQtGraph 客观上更好,因为它从一开始就是为在线绘图而设计的,与 Matplotlib 非常不同。 (2认同)

Ore*_*ren 30

这些方法都不适合我.但我发现这个 实时matplotlib图仍然在循环中不起作用

你只需要添加

plt.pause(0.0001)
Run Code Online (Sandbox Code Playgroud)

而且你可以看到新的情节.

所以你的代码应该是这样的,它会起作用

import matplotlib.pyplot as plt
import numpy as np
plt.ion() ## Note this correction
fig=plt.figure()
plt.axis([0,1000,0,1])

i=0
x=list()
y=list()

while i <1000:
    temp_y=np.random.random();
    x.append(i);
    y.append(temp_y);
    plt.scatter(i,temp_y);
    i+=1;
    plt.show()
    plt.pause(0.0001) #Note this correction
Run Code Online (Sandbox Code Playgroud)

  • 每次为我打开一个新的图形/情节窗口有没有办法只更新现有的数字?也许是因为我正在使用imshow? (3认同)

Hai*_*ang 18

顶部(和许多其他)答案是建立在plt.pause(),但这是一种动画matplotlib中的情节的老方法.它不仅速度慢,而且在每次更新时都会引起焦点(我很难停止绘制python进程).

TL; DR:您可能想要使用matplotlib.animation(如文档中所述).

在挖掘了各种答案和代码之后,事实证明这是为我无限地绘制传入数据的一种平滑方式.

这是我的快速入门代码.它以无限的每200ms在[0,100]中随机编号绘制当前时间,同时还处理视图的自动重新缩放:

from datetime import datetime
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation
from random import randrange

x_data, y_data = [], []

figure = pyplot.figure()
line, = pyplot.plot_date(x_data, y_data, '-')

def update(frame):
    x_data.append(datetime.now())
    y_data.append(randrange(0, 100))
    line.set_data(x_data, y_data)
    figure.gca().relim()
    figure.gca().autoscale_view()
    return line,

animation = FuncAnimation(figure, update, interval=200)

pyplot.show()
Run Code Online (Sandbox Code Playgroud)

您还可以在FuncAnimation文档中探索blit更好的性能.

  • 嗨,如果这一切都在循环中会发生什么。说“for i in range(1000): x,y = some func_func()”。这里 `some_func()` 生成在线 `x,y` 数据对,我想在它们可用时绘制它们。是否可以使用“FuncAnimation”来做到这一点。我的目标是在每次迭代中逐步构建由数据定义的曲线。 (2认同)

Sco*_*ott 13

我知道这个问题已经过时了,但是现在GitHub上有一个叫做drawow的软件包就是"python-drawnow".这提供了类似于MATLAB的绘图界面 - 您可以轻松更新图形.

您的用例的示例:

import matplotlib.pyplot as plt
from drawnow import drawnow

def make_fig():
    plt.scatter(x, y)  # I think you meant this

plt.ion()  # enable interactivity
fig = plt.figure()  # make a figure

x = list()
y = list()

for i in range(1000):
    temp_y = np.random.random()
    x.append(i)
    y.append(temp_y)  # or any arbitrary update to your figure's data
    i += 1
    drawnow(make_fig)
Run Code Online (Sandbox Code Playgroud)

python-drawnow是一个薄的包装器,plt.draw但提供了在图形显示后确认(或调试)的能力.


Ham*_*hei 7

另一种选择是使用bokeh。IMO,至少对于实时绘图来说,这是一个不错的选择。这是问题中代码的散景版本:

from bokeh.plotting import curdoc, figure
import random
import time

def update():
    global i
    temp_y = random.random()
    r.data_source.stream({'x': [i], 'y': [temp_y]})
    i += 1

i = 0
p = figure()
r = p.circle([], [])
curdoc().add_root(p)
curdoc().add_periodic_callback(update, 100)
Run Code Online (Sandbox Code Playgroud)

并运行它:

pip3 install bokeh
bokeh serve --show test.py
Run Code Online (Sandbox Code Playgroud)

bokeh 通过 websocket 通信在 Web 浏览器中显示结果。当数据由远程无头服务器进程生成时,它特别有用。

散景示例图


Mic*_*rer 5

问题似乎是你希望plt.show()显示窗口然后返回.它没有这样做.程序将在此时停止,并且只有在关闭窗口后才会恢复.您应该能够测试:如果您关闭窗口,则应弹出另一个窗口.

要解决该问题,请plt.show()在循环后调用一次.然后你得到完整的情节.(但不是'实时绘图')

您可以尝试block像这样设置关键字参数:plt.show(block=False)一开始,然后用于.draw()更新.

  • 实时绘图确实是我想要的。我将对某件事进行 5 小时的测试,并想看看事情进展如何。 (2认同)

sle*_*har 5

这是我必须在我的系统上工作的版本。

import matplotlib.pyplot as plt
from drawnow import drawnow
import numpy as np

def makeFig():
    plt.scatter(xList,yList) # I think you meant this

plt.ion() # enable interactivity
fig=plt.figure() # make a figure

xList=list()
yList=list()

for i in np.arange(50):
    y=np.random.random()
    xList.append(i)
    yList.append(y)
    drawnow(makeFig)
    #makeFig()      The drawnow(makeFig) command can be replaced
    #plt.draw()     with makeFig(); plt.draw()
    plt.pause(0.001)
Run Code Online (Sandbox Code Playgroud)

drawow(makeFig) 线可以用 makeFig() 替换;plt.draw() 序列,它仍然可以正常工作。


Nil*_*age 5

实时绘制 CPU 使用率的示例用例。

import time
import psutil
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

i = 0
x, y = [], []

while True:
    x.append(i)
    y.append(psutil.cpu_percent())

    ax.plot(x, y, color='b')

    fig.canvas.draw()

    ax.set_xlim(left=max(0, i - 50), right=i + 50)
    fig.show()
    plt.pause(0.05)
    i += 1
Run Code Online (Sandbox Code Playgroud)