Come posso tracciare in tempo reale in un ciclo while usando matplotlib?


233

Sto cercando di tracciare alcuni dati da una telecamera in tempo reale usando OpenCV. Tuttavia, la stampa in tempo reale (usando matplotlib) non sembra funzionare.

Ho isolato il problema in questo semplice esempio:

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

Mi aspetterei che questo esempio tracci individualmente 1000 punti. Ciò che realmente accade è che la finestra si apre con il primo punto visualizzato (ok con quello), quindi attende che il ciclo finisca prima che popoli il resto del grafico.

Qualche idea sul perché non vedo punti popolati uno alla volta?

Risposte:


313

Ecco la versione funzionante del codice in questione (richiede almeno la versione Matplotlib 1.1.0 del 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()

Nota alcune delle modifiche:

  1. Chiama plt.pause(0.05)per disegnare entrambi i nuovi dati ed esegue il loop degli eventi della GUI (consentendo l'interazione con il mouse).

3
Questo ha funzionato per me in Python2. In Python3 no. Metterebbe in pausa il ciclo dopo aver eseguito il rendering della finestra della trama. Ma dopo aver spostato il metodo plt.show () dopo il ciclo ... lo ha risolto per Python3, per me.
continuazione

1
Strano, ha funzionato bene per me in Python 3 (versione 3.4.0) Matplotlib (versione 1.3.1) Numpy (versione 1.8.1) Ubuntu Linux 3.13.0 64-bit
Velimir Mlaker,

37
invece di plt.show () e plt.draw () basta sostituire plt.draw () con plt.pause (0.1)
denfromufa,

4
Non ha funzionato su Win64 / Anaconda matplotlib .__ versione__ 1.5.0. Si aprì una finestra di figura iniziale, ma non
mostrò

5
Questa risposta richiede una conoscenza a priori dei dati x / y ... che non è necessaria: preferisco 1. non chiamare plt.axis()ma invece crea due elenchi xey e chiama plt.plot(x,y)2. nel tuo ciclo, aggiungi nuovi valori di dati a le due liste 3. chiamaplt.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 il

76

Se sei interessato alla stampa in tempo reale, ti consiglio di esaminare l'API di animazione di matplotlib . In particolare, l'utilizzo blitper evitare di ridisegnare lo sfondo su ogni fotogramma può dare sostanziali guadagni di velocità (~ 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)

Produzione:

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

1
@bejota La versione originale è stata progettata per funzionare all'interno di una sessione matplotlib interattiva. Per farlo funzionare come uno script autonomo, è necessario 1) selezionare esplicitamente un backend per matplotlib e 2) per forzare la figura da visualizzare e disegnare prima di entrare nel ciclo di animazione usando plt.show()e plt.draw(). Ho aggiunto queste modifiche al codice sopra.
ali_m,

2
L'intenzione / motivazione di questo blit()sembra essere molto "migliorare la trama in tempo reale"? Se hai uno sviluppatore / blog matplotlib che discute del perché / scopo / intento / motivazione sarebbe fantastico. (sembra che questa nuova operazione blit converta Matplotlib dall'uso solo per i dati offline o che cambiano molto lentamente, ora puoi usare Matplotlib con dati di aggiornamento molto rapidi ... quasi come un oscilloscopio).
Trevor Boyd Smith,

1
Ho scoperto che questo approccio rende la finestra della trama non rispondente: non riesco a interagire con essa, e farlo potrebbe bloccarlo.
Ninjakannon,

1
Per coloro che ottengono il problema "gtk not found", funziona benissimo con un back-end diverso (ho usato 'TKAgg'). Per trovare un supporto supportato ho usato questa soluzione: stackoverflow.com/questions/3285193/…
James Nelson

1
Il link in questa risposta non sembra funzionare più. Questo potrebbe essere un link aggiornato: scipy-cookbook.readthedocs.io/items/…
awelkie

35

So di essere un po 'in ritardo per rispondere a questa domanda. Tuttavia, ho fatto un po 'di codice qualche tempo fa per tracciare grafici dal vivo, che vorrei condividere:

Codice per 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_())

''''''

 
Di recente ho riscritto il codice per PyQt5.
Codice per 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_())

Provalo e basta. Copia e incolla questo codice in un nuovo file Python ed eseguilo. Dovresti ottenere un grafico bello e scorrevole:

inserisci qui la descrizione dell'immagine


Ho notato che il dataSendLoopthread continuava a girare in background quando chiudevi la finestra. Quindi ho aggiunto la daemon = Trueparola chiave per risolvere quel problema.
K.Mulier,

1
L'ambiente virtuale per questo ha richiesto un po 'di lavoro. Alla fine, ha conda install pyqt=4fatto il trucco.
Reb.Cabin

1
Grazie mille per il codice di base. Mi ha aiutato a creare un'interfaccia utente semplice modificando e aggiungendo funzionalità basate sul codice. Mi ha salvato il tempo =]
Isaac Sim il

Ciao @IsaacSim, grazie mille per il tuo gentile messaggio. Sono contento che questo codice sia stato utile :-)
K.Mulier il

Quindi ho preso questo script e ho aggiunto i timestamp all'asse x modificando il meccanismo dello slot del segnale per utilizzare un tipo np.ndarry ed emettendo un array np. del relativo timestamp e segnale. Sto aggiornando xlim () su ogni disegno del fotogramma che funziona bene per visualizzare il segnale con il nuovo asse, ma non le etichette / segni di spunta x si aggiornano solo brevemente quando cambio la dimensione della finestra. @ K.Mulier Sto fondamentalmente dopo un asse xtick scorrevole come i dati e mi chiedevo se hai avuto successo su qualcosa del genere?
nimig18

33

showprobabilmente non è la scelta migliore per questo. Quello che vorrei fare è pyplot.draw()invece usare . Potresti anche voler includere un piccolo ritardo (ad esempio, time.sleep(0.05)) nel loop in modo da poter vedere accadere i grafici. Se apporto queste modifiche al tuo esempio, funziona per me e vedo ogni punto apparire uno alla volta.


10
Ho una parte molto simile di codice, e quando provo la tua soluzione (disegna invece di mostrare e ritardo) python non apre affatto una finestra di figura, passa solo attraverso il ciclo ...
George Aprilis

31

Nessuno dei metodi ha funzionato per me. Ma ho scoperto che questo grafico a tempo reale matplotlib non funziona mentre è ancora in loop

Tutto ciò che serve è aggiungere

plt.pause(0.0001)

e quindi potresti vedere i nuovi grafici.

Quindi il tuo codice dovrebbe assomigliare a questo e funzionerà

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

6
Questo apre una nuova finestra di figura / trama ogni volta che per me c'è un modo per aggiornare solo la figura esistente? forse è perché sto usando imshow?
Francisco Vargas,

@FranciscoVargas se stai usando imshow, devi usare set_data, guarda qui: stackoverflow.com/questions/17835302/…
Oren,

22

Le risposte migliori (e molte altre) erano basate su plt.pause(), ma quello era un vecchio modo di animare la trama in matplotlib. Non è solo lento, ma fa sì che l'attenzione venga catturata su ogni aggiornamento (ho avuto difficoltà a fermare il processo di stampa di Python).

TL; DR: potresti voler usare matplotlib.animation( come menzionato nella documentazione ).

Dopo aver scavato varie risposte e pezzi di codice, questo in effetti si è rivelato un modo semplice per disegnare i dati in arrivo infinitamente per me.

Ecco il mio codice per un rapido avvio. Traccia l'ora corrente con un numero casuale in [0, 100) ogni 200 ms all'infinito, gestendo anche il ridimensionamento automatico della vista:

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

Puoi anche esplorare blitprestazioni ancora migliori come nella documentazione di FuncAnimation .

Un esempio dalla blitdocumentazione:

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

Ciao, cosa succederà se tutto questo fosse in loop. dire for i in range(1000): x,y = some func_func(). Qui some_func()genera x,ycoppie di dati online , che vorrei tracciare una volta che sono disponibili. È possibile farlo con FuncAnimation. Il mio obiettivo è quello di costruire passo dopo passo la curva definita dai dati con ogni iterazione.
Alexander Cska,

@Alexander Cska pyploy.show()dovrebbe bloccare. Se si desidera aggiungere dati, recuperarli e aggiornarli nella updatefunzione.
Hai Zhang,

Temo di non capire davvero la tua risposta. Amplificeresti il ​​tuo suggerimento, per favore.
Alexander Cska,

Voglio dire, se chiami pyplot.showin un loop, il loop verrà bloccato da questa chiamata e non continuerà. Se vuoi aggiungere i dati alla curva passo dopo passo, inserisci la tua logica update, che verrà chiamata ogni volta, intervalquindi è anche passo-passo.
Hai Zhang,

Il codice di Zhang funziona dalla console ma non in jupyter. Ho solo una trama vuota lì. In effetti, quando popolo un array in jupyter in un ciclo sequenziale e stampo l'array man mano che cresce con un'istruzione pet.plot, posso ottenere una stampa dagli array singolarmente ma solo una trama. vedi questo codice: gist.github.com/bwanaaa/12252cf36b35fced0eb3c2f64a76cb8a
aquagremlin

15

So che questa domanda è vecchia, ma ora c'è un pacchetto disponibile chiamato drawnow su GitHub come "python-drawnow". Ciò fornisce un'interfaccia simile al drawnow di MATLAB: puoi facilmente aggiornare una figura.

Un esempio per il tuo caso d'uso:

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 è un wrapper sottile plt.drawma offre la possibilità di confermare (o eseguire il debug) dopo la visualizzazione delle figure.


Questo fa sì che tk si blocchi da qualche parte
chwi

In tal caso, presenta un problema con più contesto github.com/scottsievert/python-drawnow/issues
Scott

+1 Questo ha funzionato per me nel tracciare dati live per frame di acquisizione video da opencv, mentre matplotlib si è bloccato.
jj080808,

Ho provato questo e sembrava più lento di altri metodi.
Dave C,

non usare, riavvio del server, matplotlib congelato
big-vl

6

Il problema sembra essere che ti aspetti plt.show()di mostrare la finestra e poi di tornare. Non lo fa. Il programma si fermerà a quel punto e riprenderà solo dopo aver chiuso la finestra. Dovresti essere in grado di verificarlo: se chiudi la finestra e dovrebbe apparire un'altra finestra.

Per risolvere il problema basta chiamare plt.show()una volta dopo il ciclo. Quindi ottieni la trama completa. (Ma non una "trama in tempo reale")

Puoi provare a impostare l'argomento della parola chiave in blockquesto modo: plt.show(block=False)una volta all'inizio e poi usa .draw()per aggiornare.


1
la trama in tempo reale è davvero ciò che sto cercando. Farò un test di 5 ore su qualcosa e voglio vedere come stanno andando le cose.
Chris,

@Chris sei stato in grado di condurre il test di 5 ore? Sto anche cercando qualcosa di simile. Sto usando plyplot.pause (time_duration) per aggiornare la trama. C'è un altro modo per farlo?
Prakhar Mohan Srivastava,

4

Ecco una versione che devo lavorare sul mio sistema.

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)

La linea drawnow (makeFig) può essere sostituita con un makeFig (); sequenza plt.draw () e funziona ancora bene.


1
Come fai a sapere per quanto tempo mettere in pausa? Sembra dipendere dalla trama stessa.
CMCDragonkai,

1

Se vuoi disegnare e non congelare il tuo thread man mano che vengono disegnati più punti, dovresti usare plt.pause () non time.sleep ()

sto usando il seguente codice per tracciare una serie di coordinate 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)

1

Un'altra opzione è quella di andare con il bokeh . IMO, è una buona alternativa almeno per le trame in tempo reale. Ecco una versione bokeh del codice nella domanda:

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)

e per eseguirlo:

pip3 install bokeh
bokeh serve --show test.py

bokeh mostra il risultato in un browser Web tramite comunicazioni tramite websocket. È particolarmente utile quando i dati vengono generati da processi remoti senza server.

trama campione del bokeh


0

Un esempio di caso d'uso per tracciare l'utilizzo della CPU in tempo reale.

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

Inizia davvero a rallentare dopo circa 2 minuti. Quale potrebbe essere la ragione? Forse i punti precedenti, che non rientrano nella visione attuale, dovrebbero essere eliminati.
pfabri,

Sembra davvero bello, ma ci sono un paio di problemi: 1. è impossibile uscire 2. dopo pochi minuti il ​​programma consuma quasi 100 Mb di RAM e inizia a rallentare notevolmente.
pfabri,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.