Drukowanie w sposób nieblokujący za pomocą Matplotlib

147

Bawiłem się Numpy i matplotlib w ciągu ostatnich kilku dni. Mam problemy z utworzeniem funkcji wykresu matplotlib bez blokowania wykonywania. Wiem, że w SO jest już wiele wątków z podobnymi pytaniami, a ja sporo szukałem w Google, ale nie udało mi się tego zrobić.

Próbowałem użyć show (block = False), jak sugerują niektórzy, ale wszystko, co otrzymuję, to zamrożone okno. Jeśli po prostu wywołam show (), wynik zostanie poprawnie wykreślony, ale wykonanie zostanie zablokowane do momentu zamknięcia okna. Z innych wątków, które czytałem, podejrzewam, że to, czy show (blok = False) działa, czy nie, zależy od zaplecza. Czy to jest poprawne? Mój zaplecze to Qt4Agg. Czy mógłbyś rzucić okiem na mój kod i powiedzieć mi, czy coś jest nie tak? Oto mój kod. Dzięki za wszelką pomoc.

from math import *
from matplotlib import pyplot as plt
print plt.get_backend()



def main():
    x = range(-50, 51, 1)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4

        y = [Xi**pow for Xi in x]
        print y

        plt.plot(x, y)
        plt.draw()
        #plt.show()             #this plots correctly, but blocks execution.
        plt.show(block=False)   #this creates an empty frozen window.
        _ = raw_input("Press [enter] to continue.")


if __name__ == '__main__':
    main()

PS. Zapomniałem powiedzieć, że chciałbym aktualizować istniejące okno za każdym razem, gdy coś wykreślam, zamiast tworzyć nowe.

opetroch
źródło
1
masz spróbuj tryb matplotlib interaktywną z plt.ion()przed plt.show()? Powinien wtedy być nieblokujący, ponieważ każdy wątek jest odradzany w wątku podrzędnym.
Anzel
@Anzel Właśnie to wypróbowałem, ale wydaje się, że nie ma to znaczenia.
opetroch
3
Jak uruchamiasz swój skrypt? Jeśli uruchomię twój przykładowy kod z terminala / wiersza poleceń, wydaje się, że działa dobrze, ale myślę, że miałem w przeszłości problemy, gdy próbowałem robić takie rzeczy z IPython QtConsole lub IDE.
Marius
1
@Marius Aha !! Masz rację. Rzeczywiście uruchamiam go z konsoli mojego IDE (PyCharm). Po uruchomieniu go z wiersza poleceń cmd, plt.show (blok = False), działa dobrze! Czy będę pytać za dużo, jeśli zapytam, czy znalazłeś jakiś pomysł / rozwiązanie na to? Wielkie dzięki!
opetroch
Naprawdę nie wiem, przepraszam. Naprawdę nie rozumiem szczegółów interakcji matplotlib z konsolą, więc generalnie po prostu przełączam się na uruchamianie z wiersza poleceń, jeśli muszę to zrobić matplotlib.
Marius

Odpowiedzi:

176

Długo szukałem rozwiązań i znalazłem tę odpowiedź .

Wygląda na to, aby dostać to, czego (i) chce, trzeba kombinacji plt.ion(), plt.show()(nie block=False) i, co najważniejsze, plt.pause(.001)(lub bez względu na czas chcesz). Przerwa jest potrzebna, ponieważ zdarzenia GUI zdarzyć natomiast główny kod jest snem, w tym rysunku. Możliwe, że jest to realizowane poprzez zbieranie czasu z uśpionego wątku, więc może IDE z tym mieszają - nie wiem.

Oto implementacja, która działa dla mnie w Pythonie 3.5:

import numpy as np
from matplotlib import pyplot as plt

def main():
    plt.axis([-50,50,0,10000])
    plt.ion()
    plt.show()

    x = np.arange(-50, 51)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4
        y = [Xi**pow for Xi in x]
        plt.plot(x, y)
        plt.draw()
        plt.pause(0.001)
        input("Press [enter] to continue.")

if __name__ == '__main__':
    main()
krs013
źródło
3
Twoja odpowiedź bardzo mi pomogła w rozwiązaniu podobnego problemu, który miałem. Wcześniej miałem plt.drawnastępuje plt.show(block = False)ale potem przestał działać: Rysunek nie reaguje, zamykając rozbił ipython. Moim rozwiązaniem było usunięcie każdego wystąpienia plt.draw()i zastąpienie go plt.pause(0.001). Zamiast tego, plt.show(block = False)jak plt.drawpoprzednio, poprzedza go plt.ion()i plt.show(). Mam teraz, MatplotlibDeprecationWarningale pozwala mi wykreślić moje dane, więc jestem zadowolony z tego rozwiązania.
blue_chip
3
Zauważ, że w Pythonie 2.7 musisz używać raw_inputnot input. Zobacz tutaj
Chris
Naprawdę przydatne obejście, gdy reaktywne podejście „animacji” nie jest możliwe! Czy ktoś wie, jak pozbyć się ostrzeżenia o wycofaniu?
Frederic Fortier,
Czy ktoś może mi powiedzieć, dlaczego pojawia się wiersz polecenia zamrożenia, gdy próbuję dodać plt.ion przed plt.show?
Gabriel Augusto
@GabrielAugusto Nie jestem pewien, co może to spowodować, i nie jestem pewien, co masz na myśli. Właśnie przetestowałem ten przykład w Pythonie 3.6 i nadal działa. Jeśli użyłeś tego samego wzorca i zawiesił się, być może coś jest nie tak z twoją instalacją. Powinieneś najpierw sprawdzić, czy normalne drukowanie działa. Jeśli spróbowałeś czegoś innego, w komentarzach nie ma wiele do zrobienia. W obu przypadkach możesz rozważyć zadanie osobnego pytania.
krs013
24

Prosta sztuczka, która działa dla mnie, jest następująca:

  1. Użyj argumentu block = False wewnątrz show: plt.show (block = False)
  2. Użyj innej plt.show () na końcu skryptu .py.

Przykład :

import matplotlib.pyplot as plt

plt.imshow(add_something)
plt.xlabel("x")
plt.ylabel("y")

plt.show(block=False)

#more code here (e.g. do calculations and use print to see them on the screen

plt.show()

Uwaga : plt.show()to ostatnia linia mojego skryptu.

seralouk
źródło
8
To tworzy (dla mnie, w Linuksie, Anaconda, Python 2.7, domyślny backend) puste okno, które pozostaje puste do samego końca wykonywania, kiedy w końcu zostanie wypełnione. Nie jest przydatne do aktualizowania wykresu w trakcie wykonywania. :-(
sh37211
@ sh37211 Nie wiesz, jaki jest twój cel. W niektórych przypadkach, gdy próbujesz coś wykreślić, ale po poleceniu wydruku masz inne polecenia, wtedy jest to przydatne, ponieważ umożliwia wykreślenie i wykonanie innych poleceń. Zobacz ten post, aby uzyskać więcej informacji na ten temat: stackoverflow.com/questions/458209/… . Jeśli chcesz zaktualizować działkę, powinno to być w inny sposób.
seralouk
18

Możesz uniknąć blokowania wykonania, zapisując wykres do tablicy, a następnie wyświetlając tablicę w innym wątku. Oto przykład jednoczesnego generowania i wyświetlania wykresów przy użyciu pf.screen z pyformulas 0.2.8 :

import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time

fig = plt.figure()

canvas = np.zeros((480,640))
screen = pf.screen(canvas, 'Sinusoid')

start = time.time()
while True:
    now = time.time() - start

    x = np.linspace(now-2, now, 100)
    y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
    plt.xlim(now-2,now+1)
    plt.ylim(-3,3)
    plt.plot(x, y, c='black')

    # If we haven't already shown or saved the plot, then we need to draw the figure first...
    fig.canvas.draw()

    image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
    image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))

    screen.update(image)

#screen.close()

Wynik:

Animacja sinusowa

Zastrzeżenie: jestem opiekunem pyformulas.

Źródła : Matplotlib: save plot to numpy array

Obraz domyślny
źródło
9

Wiele z tych odpowiedzi jest bardzo zawyżonych iz tego, co mogę znaleźć, odpowiedź nie jest wcale taka trudna do zrozumienia.

Możesz użyć, plt.ion()jeśli chcesz, ale uważam, że używanie plt.draw()jest równie skuteczne

W moim konkretnym projekcie kreślę obrazy, ale zamiast tego możesz użyć plot()lub scatter()lub cokolwiek innego figimage(), to nie ma znaczenia.

plt.figimage(image_to_show)
plt.draw()
plt.pause(0.001)

Lub

fig = plt.figure()
...
fig.figimage(image_to_show)
fig.canvas.draw()
plt.pause(0.001)

Jeśli używasz rzeczywistej figury.
Użyłem @ krs013 i odpowiedzi @Default Picture, aby to zrozumiećMam
nadzieję , że to oszczędza komuś wypuszczania każdej postaci w osobnym wątku lub czytania tych powieści tylko po to, aby to zrozumieć

iggy12345
źródło
5

Drukowanie na żywo

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
# plt.axis([x[0], x[-1], -1, 1])      # disable autoscaling
for point in x:
    plt.plot(point, np.sin(2 * point), '.', color='b')
    plt.draw()
    plt.pause(0.01)
# plt.clf()                           # clear the current figure

jeśli ilość danych jest zbyt duża, można obniżyć szybkość aktualizacji za pomocą prostego licznika

cnt += 1
if (cnt == 10):       # update plot each 10 points
    plt.draw()
    plt.pause(0.01)
    cnt = 0

Działka wstrzymana po zakończeniu programu

To był mój rzeczywisty problem, na który nie mogłem znaleźć satysfakcjonującej odpowiedzi, chciałem stworzyć wykres, który nie zamknął się po zakończeniu skryptu (jak MATLAB),

Jeśli się nad tym zastanowić, to po zakończeniu skryptu program zostaje zakończony i nie ma logicznego sposobu na utrzymanie wykresu w ten sposób, więc są dwie opcje

  1. zablokuj wyjście ze skryptu (to jest plt.show (), a nie to, czego chcę)
  2. uruchomić fabułę na osobnym wątku (zbyt skomplikowane)

nie było to dla mnie satysfakcjonujące, więc znalazłem inne rozwiązanie poza pudełkiem

SaveToFile i View w zewnętrznej przeglądarce

W tym celu zapisywanie i przeglądanie powinno być szybkie, a przeglądarka nie powinna blokować pliku i powinna automatycznie aktualizować zawartość

Wybieranie formatu do zapisania

formaty wektorowe są małe i szybkie

  • SVG jest dobry, ale nie można znaleźć dla niego dobrej przeglądarki, z wyjątkiem przeglądarki internetowej, która domyślnie wymaga ręcznego odświeżenia
  • PDF może obsługiwać formaty wektorowe i są lekkie przeglądarki obsługujące aktualizację na żywo

Szybka i lekka przeglądarka z aktualizacją na żywo

W przypadku formatu PDF jest kilka dobrych opcji

  • W systemie Windows używam SumatraPDF, który jest darmowy, szybki i lekki (używa tylko 1,8 MB pamięci RAM w moim przypadku)

  • W systemie Linux jest kilka opcji, takich jak Evince (GNOME) i Ocular (KDE)

Przykładowy kod i wyniki

Przykładowy kod do wysyłania wykresu do pliku

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(2 * x)
plt.plot(x, y)
plt.savefig("fig.pdf")

po pierwszym uruchomieniu otwórz plik wyjściowy w jednej z wyżej wymienionych przeglądarek i ciesz się.

Oto zrzut ekranu VSCode obok SumatraPDF, również proces jest wystarczająco szybki, aby uzyskać częstotliwość odświeżania pół-na żywo (mogę uzyskać blisko 10 Hz na mojej konfiguracji, po prostu używam time.sleep()między interwałami) pyPlot, bez blokowania

Ali80
źródło
2

Odpowiedź Iggy'ego była dla mnie najłatwiejsza do naśladowania, ale otrzymałem następujący błąd podczas wykonywania kolejnego subplotpolecenia, którego nie było, gdy robiłem show:

MatplotlibDeprecationWarning: Dodanie osi przy użyciu tych samych argumentów, co w przypadku poprzednich osi, obecnie ponownie wykorzystuje wcześniejszą instancję. W przyszłej wersji zawsze zostanie utworzona i zwrócona nowa instancja. W międzyczasie to ostrzeżenie można pominąć i zapewnić przyszłe zachowanie, przekazując unikalną etykietę do każdego wystąpienia osi.

Aby uniknąć tego błędu, pomaga zamknąć (lub wyczyścić ) wykres po wejściu użytkownika.

Oto kod, który działał dla mnie:

def plt_show():
    '''Text-blocking version of plt.show()
    Use this instead of plt.show()'''
    plt.draw()
    plt.pause(0.001)
    input("Press enter to continue...")
    plt.close()
Pro Q
źródło
1

Pakiet drawnow w Pythonie pozwala na aktualizację wykresu w czasie rzeczywistym w sposób nieblokujący.
Działa również z kamerą internetową i OpenCV, na przykład do kreślenia miar dla każdej klatki.
Zobacz oryginalny post .

Ismael EL ATIFI
źródło
1

Doszedłem do wniosku, że plt.pause(0.001)potrzebna jest tylko komenda i nic więcej.

plt.show () i plt.draw () są niepotrzebne i / lub blokują się w taki czy inny sposób. Oto kod, który rysuje i aktualizuje figurę i kontynuuje działanie. Zasadniczo plt.pause (0,001) wydaje się być najbliższym odpowiednikiem drawnow w programie Matlab.

Niestety te wykresy nie będą interaktywne (zawieszają się), z wyjątkiem wstawienia polecenia input (), ale wtedy kod się zatrzyma.

Dokumentacja polecenia plt.pause (interwał) podaje:

Jeśli istnieje aktywna figura, zostanie ona zaktualizowana i wyświetlona przed pauzą ...... Można to wykorzystać do prostej animacji.

i to jest dokładnie to, czego chcemy. Wypróbuj ten kod:

import numpy as np
from matplotlib import pyplot as plt

x = np.arange(0, 51)
for pow in range(10, 50):
    y = np.power(x, pow/10)

    plt.cla()                      # erase previous lines
    plt.axis([-50, 50, 0, 10000])
    plt.plot(x, y)

    # use plt.pause() instead of the blocking plt.show() and plt.draw()
    # this DOES the actual plotting (plus a pause)

    plt.pause(0.1)  # pick 0.001 or smaller if you don't want to wait
    
    # you can put your input() statement here or anything else
Jim
źródło