Как построить график в реальном времени в цикле while с помощью matplotlib?

Я пытаюсь построить некоторые данные с камеры в режиме реального времени, используя 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()

Я ожидал бы, что этот пример построит 1000 пунктов отдельно. На самом деле происходит то, что окно всплывает с показом первой точки (хорошо с этим), затем ждет окончания цикла, прежде чем заполнить остальную часть графика.

Любые мысли, почему я не вижу точек, населенных по одному?

15 ответов

Решение

Вот рабочая версия рассматриваемого кода (требуется как минимум версия Matplotlib 1.1.0 от 2011-11-14):

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()

Обратите внимание на некоторые изменения:

  1. Вызов plt.pause(0.05) чтобы оба нарисовать новые данные, и он запускает цикл событий GUI (с учетом взаимодействия с мышью).

Если вы заинтересованы в построении графиков в реальном времени, я бы порекомендовал изучить API анимации matplotlib. В частности, используя 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)

Выход:

Blit = False, average FPS: 54.37
Blit = True, average FPS: 438.27

Я знаю, что немного опоздал, чтобы ответить на этот вопрос. Тем не менее, некоторое время назад я сделал некоторый код для построения графиков, которыми я хотел бы поделиться:

###################################################################
#                                                                 #
#                     PLOTTING A LIVE GRAPH                       #
#                  ----------------------------                   #
#            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_())

''''''

Просто попробуйте. Скопируйте и вставьте этот код в новый python-файл и запустите его. Вы должны получить красивый, плавно движущийся график:

введите описание изображения здесь

Лучшие (и многие другие) ответы были построены на plt.pause(), но это был старый способ анимации сюжета в matplotlib. Это не только медленно, но и заставляет фокусироваться на каждом обновлении (мне было трудно остановить процесс построения Python).

TL; DR: вы можете использовать matplotlib.animation ( как указано в документации).

После поиска различных ответов и фрагментов кода это фактически оказалось для меня гладким способом бесконечного рисования входящих данных.

Вот мой код для быстрого начала. Он отображает текущее время со случайным числом в [0, 100) каждые 200 мс бесконечно, а также обрабатывает автоматическое изменение масштаба представления:

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()

Вы также можете исследовать blit для еще лучшей производительности, как в документации FuncAnimation.

Ни один из методов не работал для меня. Но я обнаружил, что этот график Matplotlib в реальном времени не работает, пока еще в цикле

Все, что вам нужно, это добавить

plt.pause(0.0001)

и чем вы могли видеть новый сюжет.

Итак, ваш код должен выглядеть так, и он будет работать

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

show вероятно, не лучший выбор для этого. Что бы я сделал, это использовать pyplot.draw() вместо. Вы также можете включить небольшую задержку (например, time.sleep(0.05)) в цикле, чтобы вы могли видеть происходящие графики. Если я внесу эти изменения в ваш пример, это сработает для меня, и я увижу, что каждая точка появляется по одному.

Я знаю, что этот вопрос старый, но теперь на GitHub есть пакет, называемый drawnow как "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)

python-drawnow - это тонкая оболочка вокруг plt.draw но предоставляет возможность подтверждения (или отладки) после отображения рисунка.

Другой вариант - использовать боке. ИМО, это хорошая альтернатива, по крайней мере, для графиков в реальном времени. Вот версия кода в вопросе с эффектом боке:

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)

и для его запуска:

pip3 install bokeh
bokeh serve --show test.py

bokeh показывает результат в веб-браузере через веб-соединение. Это особенно полезно, когда данные генерируются удаленными процессами безголового сервера.

Кажется, проблема в том, что вы ожидаете plt.show() чтобы показать окно, а затем вернуться. Это не делает этого. Программа остановится в этой точке и возобновит работу только после закрытия окна. Вы должны быть в состоянии проверить это: если вы закроете окно, а затем появится другое окно.

Чтобы решить эту проблему, просто позвоните plt.show() один раз после вашей петли. Тогда вы получите полный сюжет. (Но не в режиме реального времени)

Вы можете попробовать установить ключевое слово-аргумент block как это: plt.show(block=False) один раз в начале, а затем используйте .draw() обновлять.

Пример варианта использования для построения графика использования ЦП в режиме реального времени.

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

Вот версия, которую я получил для работы в моей системе.

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)

Строка drawnow(makeFig) может быть заменена на makeFig(); Последовательность plt.draw(), и она все еще работает нормально.

Живой график с круговым буфером с сохраненным стилем линии:

      import os
import time
import psutil
import collections

import matplotlib.pyplot as plt

pts_n = 100
x = collections.deque(maxlen=pts_n)
y = collections.deque(maxlen=pts_n)
(line, ) = plt.plot(x, y, linestyle="--")

my_process = psutil.Process(os.getpid())
t_start = time.time()
while True:
    x.append(time.time() - t_start)
    y.append(my_process.cpu_percent())

    line.set_xdata(x)
    line.set_ydata(y)
    plt.gca().relim()
    plt.gca().autoscale_view()
    plt.pause(0.1)

Если вы хотите рисовать, а не замораживать поток, так как рисуется больше точек, вы должны использовать plt.pause(), а не time.sleep()

Я использую следующий код, чтобы построить серию координат XY.

import matplotlib.pyplot as plt 
import math


pi = 3.14159

fig, ax = plt.subplots()

x = []
y = []

def PointsInCircum(r,n=20):
    circle = [(math.cos(2*pi/n*x)*r,math.sin(2*pi/n*x)*r) for x in xrange(0,n+1)]
    return circle

circle_list = PointsInCircum(3, 50)

for t in range(len(circle_list)):
    if t == 0:
        points, = ax.plot(x, y, marker='o', linestyle='--')
        ax.set_xlim(-4, 4) 
        ax.set_ylim(-4, 4) 
    else:
        x_coord, y_coord = circle_list.pop()
        x.append(x_coord)
        y.append(y_coord)
        points.set_data(x, y)
    plt.pause(0.01)

Это правильный способ построить динамическую анимацию графиков в реальном времени с использованием цикла while.

pip install celluloid # это захватит изображение / анимацию

      import matplotlib.pyplot as plt
import numpy as np
from celluloid import Camera # getting the camera
import matplotlib.animation as animation
from IPython import display
import time
from IPython.display import HTML

import warnings
%matplotlib notebook
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')

fig = plt.figure() #Empty fig object
ax = fig.add_subplot() #Empty axis object
camera = Camera(fig) # Camera object to capture the snap

def f(x):
    ''' function to create a sine wave'''
    return np.sin(x) + np.random.normal(scale=0.1, size=len(x))

l = []

while True:
    value = np.random.randint(9) #random number generator
    l.append(value) # appneds each time number is generated
    X = np.linspace(10, len(l)) # creates a line space for x axis, Equal to the length of l

    for i in range(10): #plots 10 such lines
        plt.plot(X, f(X))

    fig.show() #shows the figure object
    fig.canvas.draw() 
    camera.snap() # camera object to capture teh animation
    time.sleep(1)

А для экономии и т. Д .:

      animation = camera.animate(interval = 200, repeat = True, repeat_delay = 500)
HTML(animation.to_html5_video())
animation.save('abc.mp4') # to save 

вывод:

Я создал этот код с немного другой точки зрения:

      import numpy as np
from matplotlib import pyplot


figure = pyplot.figure()
# get current axes  # If figure.axes == [], a new one is created
axes = figure.gca()
axes.axis([0, 1000, 0, 1])
figure.show()

x_val, x_values, y_values = 0, list(), list()
while x_val < 1000:
    if not pyplot.fignum_exists(figure.number):
        break  # break when window is closed
    y_val = np.random.random()
    x_values.append(x_val)
    y_values.append(y_val)
    axes.scatter(x_val, y_val)
    x_val += 1
    figure.canvas.draw()
    figure.canvas.flush_events()
Другие вопросы по тегам