Wie zeichne ich mit matplotlib in Echtzeit in einer while-Schleife?

232

Ich versuche, einige Daten von einer Kamera in Echtzeit mit OpenCV zu zeichnen. Das Echtzeit-Plotten (mit matplotlib) scheint jedoch nicht zu funktionieren.

Ich habe das Problem in diesem einfachen Beispiel isoliert:

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

Ich würde erwarten, dass dieses Beispiel 1000 Punkte einzeln darstellt. Was tatsächlich passiert, ist, dass das Fenster mit dem ersten angezeigten Punkt (ok damit) angezeigt wird und dann darauf wartet, dass die Schleife beendet ist, bevor der Rest des Diagramms ausgefüllt wird.

Irgendwelche Gedanken, warum ich nicht sehe, dass Punkte einzeln besetzt sind?

Chris
quelle

Antworten:

311

Hier ist die Arbeitsversion des betreffenden Codes (erfordert mindestens die Version Matplotlib 1.1.0 vom 14.11.2011):

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

Beachten Sie einige der Änderungen:

  1. Rufen Sie plt.pause(0.05)auf, um beide die neuen Daten zu zeichnen, und es wird die Ereignisschleife der GUI ausgeführt (die Mausinteraktion ermöglicht).
Velimir Mlaker
quelle
3
Dies hat bei mir in Python2 funktioniert. In Python3 war dies nicht der Fall. Nach dem Rendern des Plotfensters wird die Schleife angehalten. Aber nachdem die Methode plt.show () nach der Schleife verschoben wurde, wurde sie für mich für Python3 aufgelöst.
Continuousqa
1
Seltsam, funktionierte in Python 3 (Version 3.4.0) in Ordnung. Matplotlib (Version 1.3.1) Numpy (Version 1.8.1) Ubuntu Linux 3.13.0 64-Bit
Velimir Mlaker
37
anstelle von plt.show () und plt.draw () ersetzen Sie einfach plt.draw () durch plt.pause (0.1)
denfromufa
4
Funktionierte nicht mit Win64 / Anaconda matplotlib .__ version__ 1.5.0. Ein erstes Figurenfenster öffnete sich, zeigte aber nichts an, es blieb in einem blockierten Zustand, bis ich es schloss
isti_spl
5
Diese Antwort erfordert A-priori-Kenntnisse der x / y-Daten ... was nicht benötigt wird: Ich bevorzuge 1. nicht aufrufen, plt.axis()sondern zwei Listen x und y erstellen und plt.plot(x,y)2. in Ihrer Schleife neue Datenwerte anhängen die zwei Listen 3. Anrufplt.gca().lines[0].set_xdata(x); plt.gca().lines[0].set_ydata(y); plt.gca().relim(); plt.gca().autoscale_view(); plt.pause(0.05);
Trevor Boyd Smith
76

Wenn Sie an Echtzeit-Plots interessiert sind, empfehlen wir Ihnen, sich die Animations-API von matplotlib anzusehen . Insbesondere die Verwendung blit, um zu vermeiden, dass der Hintergrund in jedem Frame neu gezeichnet wird, kann zu erheblichen Geschwindigkeitsgewinnen führen (~ 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)

Ausgabe:

Blit = False, average FPS: 54.37
Blit = True, average FPS: 438.27
ali_m
quelle
1
@bejota Die Originalversion wurde für die Verwendung in einer interaktiven Matplotlib-Sitzung entwickelt. Damit es als eigenständiges Skript funktioniert, müssen Sie 1) explizit ein Backend für matplotlib auswählen und 2) die Anzeige und Zeichnung der Figur erzwingen, bevor Sie mit plt.show()und in die Animationsschleife eintreten plt.draw(). Ich habe diese Änderungen zum obigen Code hinzugefügt.
Ali_m
2
Ist die Absicht / Motivation des blit()scheinbar "Verbesserung des Echtzeit-Plots"? Wenn Sie einen matplotlib-Entwickler / Blog haben, der das Warum / den Zweck / die Absicht / die Motivation bespricht, wäre das großartig. (Anscheinend würde diese neue Blit-Operation Matplotlib von der Verwendung nur für Offline- oder sehr langsam wechselnde Daten konvertieren. Jetzt können Sie Matplotlib mit sehr schnell aktualisierten Daten verwenden ... fast wie ein Oszilloskop).
Trevor Boyd Smith
1
Ich habe festgestellt, dass dieser Ansatz dazu führt, dass das Plotfenster nicht mehr reagiert: Ich kann nicht damit interagieren, und dies kann zum Absturz führen.
Ninjakannon
1
Für diejenigen, die das Problem "gtk nicht gefunden" bekommen, funktioniert es gut mit einem anderen Back-End (ich habe 'TKAgg' verwendet). Um eine unterstützte Unterstützung zu finden, habe ich diese Lösung verwendet: stackoverflow.com/questions/3285193/…
James Nelson
1
Der Link in dieser Antwort scheint nicht mehr zu funktionieren. Dies könnte ein aktueller Link sein: scipy-cookbook.readthedocs.io/items/…
awelkie
35

Ich weiß, dass ich etwas spät dran bin, um diese Frage zu beantworten. Trotzdem habe ich vor einiger Zeit Code erstellt, um Live-Diagramme zu zeichnen, die ich gerne teilen möchte:

Code für PyQt4:

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

''''''

 
Ich habe kürzlich den Code für PyQt5 neu geschrieben.
Code für PyQt5:

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

import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading

class CustomMainWindow(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 = QFrame(self)
        self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name())
        self.LAYOUT_A = QGridLayout()
        self.FRAME_A.setLayout(self.LAYOUT_A)
        self.setCentralWidget(self.FRAME_A)
        # Place the zoom button
        self.zoomBtn = QPushButton(text = 'zoom')
        self.zoomBtn.setFixedSize(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()
        return

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

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

''' 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)
        return

    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([], [])
        return

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

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

    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
        return

    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]
        return

''' 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(QObject):
    data_signal = 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 = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Plastique'))
    myGUI = CustomMainWindow()
    sys.exit(app.exec_())

Probieren Sie es einfach aus. Kopieren Sie diesen Code, fügen Sie ihn in eine neue Python-Datei ein und führen Sie ihn aus. Sie sollten ein schönes, sich reibungslos bewegendes Diagramm erhalten:

Geben Sie hier die Bildbeschreibung ein

K. Mulier
quelle
Ich habe festgestellt, dass der dataSendLoopThread im Hintergrund weiter lief, wenn Sie das Fenster schließen. Also habe ich das daemon = TrueSchlüsselwort hinzugefügt , um dieses Problem zu lösen.
K. Mulier
1
Die virtuelle Umgebung dafür hat ein wenig Arbeit gekostet. Endlich conda install pyqt=4hat der Trick.
Reb.Cabin
1
Vielen Dank für den Basiscode. Es hat mir geholfen, eine einfache Benutzeroberfläche aufzubauen, indem ich Funktionen basierend auf Ihrem Code geändert und hinzugefügt habe. Es hat mir Zeit gespart =]
Isaac Sim
Hallo @IsaacSim, vielen Dank für Ihre freundliche Nachricht. Ich bin froh, dass dieser Code hilfreich war :-)
K.Mulier
Also habe ich dieses Skript genommen und der x-Achse Zeitstempel hinzugefügt, indem ich den Signalschlitzmechanismus so geändert habe, dass ein np.ndarry-Typ verwendet wird, und ein np.array des relativen Zeitstempels und Signals ausgegeben habe. Ich aktualisiere das xlim () bei jedem Frame Draw, was gut für die Anzeige des Signals mit der neuen Achse geeignet ist, aber nicht die x-Labels / Ticks werden nur kurz aktualisiert, wenn ich die Fenstergröße ändere. @ K.Mulier Ich bin im Grunde genommen nach einer gleitenden xtick-Achse wie den Daten und habe mich gefragt, ob Sie bei so etwas Erfolg hatten?
nimig18
33

showist wahrscheinlich nicht die beste Wahl dafür. Was ich tun würde, ist pyplot.draw()stattdessen zu verwenden. Möglicherweise möchten Sie auch eine kleine Zeitverzögerung (z. B. time.sleep(0.05)) in die Schleife aufnehmen, damit Sie sehen können, wie die Diagramme ausgeführt werden. Wenn ich diese Änderungen an Ihrem Beispiel vornehme, funktioniert dies für mich und ich sehe, dass jeder Punkt einzeln angezeigt wird.

BrenBarn
quelle
10
Ich habe einen sehr ähnlichen Teil des Codes, und wenn ich Ihre Lösung versuche (Zeichnen statt Zeigen und Zeitverzögerung), öffnet Python überhaupt kein Zahlenfenster, sondern geht nur durch die Schleife ...
George Aprilis
31

Keine der Methoden hat bei mir funktioniert. Aber ich habe festgestellt, dass dieser Echtzeit-Matplotlib-Plot nicht funktioniert, während er sich noch in einer Schleife befindet

Alles was Sie brauchen, ist hinzuzufügen

plt.pause(0.0001)

und dann konnte man die neuen Handlungen sehen.

Ihr Code sollte also so aussehen, und es wird funktionieren

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
Oren
quelle
6
Dies öffnet jedes Mal ein neues Figuren- / Handlungsfenster für mich. Gibt es eine Möglichkeit, die vorhandene Figur einfach zu aktualisieren? Vielleicht, weil ich imshow benutze?
Francisco Vargas
@FranciscoVargas Wenn Sie imshow verwenden, müssen Sie set_data verwenden, schauen Sie hier: stackoverflow.com/questions/17835302/…
Oren
22

Die besten (und viele andere) Antworten wurden aufgebaut plt.pause(), aber das war eine alte Art, die Handlung in matplotlib zu animieren. Es ist nicht nur langsam, sondern führt auch dazu, dass bei jedem Update der Fokus erfasst wird (es fiel mir schwer, den Plot-Python-Prozess zu stoppen).

TL; DR: Möglicherweise möchten Sie verwenden matplotlib.animation( wie in der Dokumentation erwähnt ).

Nachdem ich verschiedene Antworten und Codeteile durchgesehen hatte, erwies sich dies für mich als eine reibungslose Möglichkeit, eingehende Daten unendlich zu zeichnen.

Hier ist mein Code für einen schnellen Start. Es zeichnet die aktuelle Zeit mit einer Zufallszahl in [0, 100] alle 200 ms unendlich auf und behandelt gleichzeitig die automatische Neuskalierung der Ansicht:

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

Sie können auch blitnach einer noch besseren Leistung suchen, wie in der FuncAnimation-Dokumentation .

Ein Beispiel aus der blitDokumentation:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro')

def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,

def update(frame):
    xdata.append(frame)
    ydata.append(np.sin(frame))
    ln.set_data(xdata, ydata)
    return ln,

ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init, blit=True)
plt.show()
Hai Zhang
quelle
Hallo, was wird passieren, wenn dies alles in einer Schleife wäre? sagen for i in range(1000): x,y = some func_func(). Hier werden some_func()Online- x,yDatenpaare generiert , die ich zeichnen möchte, sobald sie verfügbar sind. Ist es möglich, dies mit zu tun FuncAnimation. Mein Ziel ist es, die durch die Daten definierte Kurve Schritt für Schritt mit jeder Iteration zu erstellen.
Alexander Cska
@ Alexander Cska pyploy.show()sollte blockieren. Wenn Sie Daten anhängen möchten, rufen Sie diese ab und aktualisieren Sie sie in der updateFunktion.
Hai Zhang
Ich befürchte, dass ich Ihre Antwort nicht wirklich verstehe. Würden Sie bitte Ihren Vorschlag verstärken?
Alexander Cska
Ich meine, wenn Sie pyplot.showin einer Schleife aufrufen , wird die Schleife durch diesen Aufruf blockiert und nicht fortgesetzt. Wenn Sie Daten Schritt für Schritt an die Kurve anhängen möchten, geben Sie Ihre Logik ein update, die jeweils aufgerufen wird, intervaldamit sie auch Schritt für Schritt ausgeführt wird.
Hai Zhang
Zhangs Code funktioniert von der Konsole aus, aber nicht im Jupiter. Ich bekomme dort nur eine leere Handlung. In der Tat, wenn ich ein Array in Jupiter in einer sequentiellen Schleife fülle und das Array drucke, während es mit einer pet.plot-Anweisung wächst, kann ich einen Ausdruck der Arrays einzeln erhalten, aber nur einen Plot. Siehe diesen Code: gist.github.com/bwanaaa/12252cf36b35fced0eb3c2f64a76cb8a
aquagremlin
15

Ich weiß, dass diese Frage alt ist, aber es gibt jetzt ein Paket namens drawow auf GitHub als "python-drawow". Dies bietet eine Oberfläche ähnlich der von MATLAB gezeichneten - Sie können eine Figur einfach aktualisieren.

Ein Beispiel für Ihren Anwendungsfall:

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-Drawow ist ein dünner Wrapper plt.draw, bietet jedoch die Möglichkeit, nach der Anzeige der Abbildung zu bestätigen (oder zu debuggen).

Scott
quelle
Dies lässt tk irgendwo hängen
chwi
Wenn ja, melden Sie ein Problem mit mehr Kontext github.com/scottsievert/python-drawnow/issues
Scott
+1 Dies funktionierte für mich, um Live-Daten pro Frame der Videoaufnahme von opencv zu zeichnen, während matplotlib einfrierte.
jj080808
Ich habe es versucht und es schien langsamer als andere Methoden.
Dave C
Nicht verwenden, mein Server neu starten, Matplotlib eingefroren
Big-Vl
6

Das Problem scheint zu sein, dass Sie erwarten plt.show(), das Fenster anzuzeigen und dann zurückzukehren. Das macht es nicht. Das Programm stoppt an diesem Punkt und wird erst fortgesetzt, wenn Sie das Fenster schließen. Sie sollten dies testen können: Wenn Sie das Fenster schließen und dann ein anderes Fenster angezeigt wird.

Um dieses Problem zu beheben, rufen plt.show()Sie einfach einmal nach Ihrer Schleife auf. Dann erhalten Sie die komplette Handlung. (Aber kein "Echtzeit-Plotten")

Sie können versuchen, das Schlüsselwortargument blockwie folgt festzulegen : plt.show(block=False)einmal am Anfang und dann .draw()zum Aktualisieren verwenden.

Michael Mauderer
quelle
1
Echtzeit-Plotten ist wirklich das, was ich will. Ich werde einen 5-stündigen Test für etwas durchführen und möchte sehen, wie sich die Dinge entwickeln.
Chris
@ Chris konnten Sie den 5-Stunden-Test durchführen? Ich suche auch etwas ähnliches. Ich verwende plyplot.pause (time_duration), um den Plot zu aktualisieren. Gibt es eine andere Möglichkeit, dies zu tun?
Prakhar Mohan Srivastava
4

Hier ist eine Version, die ich auf meinem System arbeiten musste.

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)

Die gezeichnete Linie (makeFig) kann durch eine makeFig () ersetzt werden. plt.draw () Sequenz und es funktioniert immer noch OK.

Slehar
quelle
1
Woher weißt du, wie lange du pausieren musst? Es scheint von der Handlung selbst abzuhängen.
CMCDragonkai
1

Wenn Sie Ihren Thread zeichnen und nicht einfrieren möchten, wenn mehr Punkte gezeichnet werden, sollten Sie plt.pause () und nicht time.sleep () verwenden.

Ich benutze den folgenden Code, um eine Reihe von xy-Koordinaten zu zeichnen.

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)
user2672474
quelle
1

Eine andere Möglichkeit ist, mit Bokeh zu gehen . IMO, es ist eine gute Alternative, zumindest für Echtzeit-Plots. Hier ist eine Bokeh-Version des Codes in der Frage:

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)

und zum Ausführen:

pip3 install bokeh
bokeh serve --show test.py

bokeh zeigt das Ergebnis in einem Webbrowser über Websocket-Kommunikation. Dies ist besonders nützlich, wenn Daten von Remote-Headless-Serverprozessen generiert werden.

Bokeh-Beispielplot

Hamid Fadishei
quelle
0

Ein beispielhafter Anwendungsfall zum Zeichnen der CPU-Auslastung in Echtzeit.

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
Nilani Algiriyage
quelle
Nach ca. 2 Minuten beginnt es wirklich langsamer zu werden. Was könnte der Grund sein? Möglicherweise sollten frühere Punkte, die außerhalb der aktuellen Ansicht liegen, gestrichen werden.
Pfabri
Das sieht wirklich gut aus, aber es gibt ein paar Probleme: 1. Es ist unmöglich zu beenden. 2. Nach nur wenigen Minuten verbraucht das Programm fast 100 MB RAM und verlangsamt sich dramatisch.
Pfabri