Existe uma maneira de desanexar gráficos matplotlib para que o cálculo possa continuar?

258

Após estas instruções no interpretador Python, obtém-se uma janela com um gráfico:

from matplotlib.pyplot import *
plot([1,2,3])
show()
# other code

Infelizmente, não sei como continuar a explorar interativamente a figura criada show()enquanto o programa faz cálculos adicionais.

É possível de alguma forma? Às vezes, os cálculos são longos e ajudaria se eles prosseguissem durante a análise dos resultados intermediários.

meteore
fonte
5
Não posso confirmar que a solução selecionada de nosklo em 16:52 está funcionando. Para mim, o draw não abre uma janela para exibir o gráfico, apenas o show de bloqueio no final exibe a solução. No entanto, sua resposta a partir das 17:00 está correta. A ativação do modo interativo ion()corrige o problema.
H. Brandsmeier
se você é um programador avançado, pode usar, os.fork()mas lembre-se de que o uso os.fork()pode ser complicado porque você está criando um novo processo, copiando o processo antigo.
Trevor Boyd Smith

Respostas:

214

Use matplotlibas chamadas que não serão bloqueadas:

Usando draw():

from matplotlib.pyplot import plot, draw, show
plot([1,2,3])
draw()
print 'continue computation'

# at the end call show to ensure window won't close.
show()

Usando o modo interativo:

from matplotlib.pyplot import plot, ion, show
ion() # enables interactive mode
plot([1,2,3]) # result shows immediatelly (implicit draw())

print 'continue computation'

# at the end call show to ensure window won't close.
show()
nosklo
fonte
28
Com o matplotlib 0.98.3, a importação correta é do gráfico de importação matplotlib.pyplot, draw, show
meteore 21/01/2009
112
draw()não funciona para mim, não abre nenhuma janela. No entanto, usar em show(block=False)vez de draw()parece fazer o truque no matplotlib 1.1.
rumpel # 03
4
@nosklo, você viu? Você fez isso em um python tutorial
Jan
4
@noskolo e se eu tiver várias figuras, como plotar e mostrar a Fig1 enquanto continua o plano de fundo para continuar? Eu gostaria que essa figura fosse aberta até a próxima figura ser gerada, então no final eu tenho todas as figuras abertas e o código está concluído. Com sua solução atual, fico me aguardando para fechar a Fig1 e o código continua. Obrigado!!
Physiker
9
draw()também não funcionou para mim, apenas pause(0.001): stackoverflow.com/questions/28269157/…
NumesSanguis 25/17/17
133

Use a palavra-chave 'block' para substituir o comportamento de bloqueio, por exemplo

from matplotlib.pyplot import show, plot

plot(1)  
show(block=False)

# your code

para continuar seu código.

Jan
fonte
17
mas isso fechará a janela da plotagem imediatamente, não manterá a plotagem aberta.
LWZ
8
Sim, isso é verdade se você chamar seu script na linha de comando. Se você estiver no shell Ipython, a janela não será fechada.
Janeiro
1
verifique a resposta do @Nico para obter um truque que deixará a janela aberta no caso geral.
Jan
2
Para mim, isso não fecha a janela imediatamente, apenas quando o script é concluído (para que você possa bloquear manualmente no final do script, se quiser que ele permaneça aberto).
luator
Sim, as janelas não bloqueadas serão fechadas quando o script sair . Você pode (a) permitir o bloqueio no seu último gráfico, ou (b) não sair do script (talvez peça uma entrada: "pressione <Enter> para sair do gráfico" ou algo parecido).
Daniel Goldfarb
29

É melhor sempre verificar com a biblioteca que você está usando se ela suporta o uso de maneira não-bloqueadora .

Mas se você quiser uma solução mais genérica ou se não houver outra maneira, poderá executar qualquer coisa que bloqueie em um processo separado usando o multprocessingmódulo incluído no python. A computação continuará:

from multiprocessing import Process
from matplotlib.pyplot import plot, show

def plot_graph(*args):
    for data in args:
        plot(data)
    show()

p = Process(target=plot_graph, args=([1, 2, 3],))
p.start()

print 'yay'
print 'computation continues...'
print 'that rocks.'

print 'Now lets wait for the graph be closed to continue...:'
p.join()

Isso tem a sobrecarga de iniciar um novo processo e às vezes é mais difícil depurar em cenários complexos, então eu prefiro a outra solução (usando matplotlibas chamadas de API sem bloqueio de API )

nosklo
fonte
Obrigado! Como ainda não tenho o Python 2.6 no meu sistema, usei o threading.Thread como substituto do Process. Observei que as instruções de impressão subsequentes se tornam insuportavelmente lentas (terceira impressão, emiti KeyboardInterrupt após 1 minuto de espera). Isso é um efeito do uso da segmentação em vez do multiprocessamento?
meteore 21/01/09
@eteu: Sim, enfiar é uma merda. Você sempre pode obter o multiprocessamento para python <2.6 a partir daqui: pyprocessing.berlios.de
nosklo
Isto é absolutamente excelente. Você tem uma idéia de por que as instruções de impressão não são executadas no Emacs (modo python) até que a janela de plotagem seja fechada?
meteore 21/01/09
No Ubuntu 8.10 (Intrepid), o pacote (para python <2.6) é chamado de python-processing e você o importa com 'import processing'
meteore 23/01/09
1
Você não perdeu o if __name__ == '__main__':?
Wernight 19/03/2013
25

Experimentar

import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.show(block=False)
# other code
# [...]

# Put
plt.show()
# at the very end of your script to make sure Python doesn't bail out
# before you finished examining.

A show()documentação diz:

No modo não interativo, exiba todas as figuras e bloqueie até que as figuras tenham sido fechadas; no modo interativo, não tem efeito, a menos que os números tenham sido criados antes de uma mudança do modo não interativo para o interativo (não recomendado). Nesse caso, exibe as figuras, mas não bloqueia.

Um único argumento experimental de palavra-chave, block, pode ser definido como True ou False para substituir o comportamento de bloqueio descrito acima.

Nico Schlömer
fonte
por que não usar draw (); [outro código]; mostrar() ? Tanto quanto eu sei bloco = False foi preterido
Bogdan
Por que você acha que foi preterido? Eu o vejo documentado aqui .
Nico Schlömer 19/09/16
11

IMPORTANTE : Apenas para deixar algo claro. Presumo que os comandos estejam dentro de um .pyscript e o script seja chamado usando, por exemplo, python script.pydo console.

Uma maneira simples que funciona para mim é:

  1. Use o bloco = Falso dentro de show: plt.show (bloco = Falso)
  2. Use outro show () no final do script .py.

Exemplo de script.py arquivo:

plt.imshow(*something*)                                                               
plt.colorbar()                                                                             
plt.xlabel("true ")                                                                   
plt.ylabel("predicted ")                                                              
plt.title(" the matrix")  

# Add block = False                                           
plt.show(block = False)

################################
# OTHER CALCULATIONS AND CODE HERE ! ! !
################################

# the next command is the last line of my script
plt.show()

seralouk
fonte
8

No meu caso, eu queria que várias janelas fossem exibidas enquanto elas estavam sendo computadas. Para referência, este é o caminho:

from matplotlib.pyplot import draw, figure, show
f1, f2 = figure(), figure()
af1 = f1.add_subplot(111)
af2 = f2.add_subplot(111)
af1.plot([1,2,3])
af2.plot([6,5,4])
draw() 
print 'continuing computation'
show()

PS. Um guia bastante útil para a interface OO do matplotlib .

meteore
fonte
6

Bem, tive um grande problema para descobrir os comandos não-bloqueadores ... Mas, finalmente, consegui refazer o exemplo " Cookbook / Matplotlib / Animations - Animating selected plot elements ", para que funcione com threads ( e passe dados entre threads também via variáveis ​​globais ou através de um multiprocessoPipe ) no Python 2.6.5 no Ubuntu 10.04.

O script pode ser encontrado aqui: Animating_selected_plot_elements-thread.py - caso contrário, colado abaixo ( com menos comentários ) para referência:

import sys
import gtk, gobject
import matplotlib
matplotlib.use('GTKAgg')
import pylab as p
import numpy as nx 
import time

import threading 



ax = p.subplot(111)
canvas = ax.figure.canvas

# for profiling
tstart = time.time()

# create the initial line
x = nx.arange(0,2*nx.pi,0.01)
line, = ax.plot(x, nx.sin(x), animated=True)

# save the clean slate background -- everything but the animated line
# is drawn and saved in the pixel buffer background
background = canvas.copy_from_bbox(ax.bbox)


# just a plain global var to pass data (from main, to plot update thread)
global mypass

# http://docs.python.org/library/multiprocessing.html#pipes-and-queues
from multiprocessing import Pipe
global pipe1main, pipe1upd
pipe1main, pipe1upd = Pipe()


# the kind of processing we might want to do in a main() function,
# will now be done in a "main thread" - so it can run in
# parallel with gobject.idle_add(update_line)
def threadMainTest():
    global mypass
    global runthread
    global pipe1main

    print "tt"

    interncount = 1

    while runthread: 
        mypass += 1
        if mypass > 100: # start "speeding up" animation, only after 100 counts have passed
            interncount *= 1.03
        pipe1main.send(interncount)
        time.sleep(0.01)
    return


# main plot / GUI update
def update_line(*args):
    global mypass
    global t0
    global runthread
    global pipe1upd

    if not runthread:
        return False 

    if pipe1upd.poll(): # check first if there is anything to receive
        myinterncount = pipe1upd.recv()

    update_line.cnt = mypass

    # restore the clean slate background
    canvas.restore_region(background)
    # update the data
    line.set_ydata(nx.sin(x+(update_line.cnt+myinterncount)/10.0))
    # just draw the animated artist
    ax.draw_artist(line)
    # just redraw the axes rectangle
    canvas.blit(ax.bbox)

    if update_line.cnt>=500:
        # print the timing info and quit
        print 'FPS:' , update_line.cnt/(time.time()-tstart)

        runthread=0
        t0.join(1)   
        print "exiting"
        sys.exit(0)

    return True



global runthread

update_line.cnt = 0
mypass = 0

runthread=1

gobject.idle_add(update_line)

global t0
t0 = threading.Thread(target=threadMainTest)
t0.start() 

# start the graphics update thread
p.show()

print "out" # will never print - show() blocks indefinitely! 

Espero que isso ajude alguém,
Saúde!

sdaau
fonte
5

Em muitos casos, é mais conveniente salvar a imagem como um arquivo .png no disco rígido. Aqui está o porquê:

Vantagens:

  • Você pode abri-lo, dar uma olhada e fechá-lo a qualquer momento no processo. Isso é particularmente conveniente quando seu aplicativo está sendo executado por um longo tempo.
  • Nada aparece e você não é obrigado a ter as janelas abertas. Isso é particularmente conveniente quando você está lidando com muitos números.
  • Sua imagem está acessível para referência posterior e não é perdida ao fechar a janela da figura.

Recua:

  • A única coisa em que consigo pensar é que você terá que procurar a pasta e abrir a imagem por conta própria.
elgehelge
fonte
Se você está tentando gerar muitas imagens, concordo plenamente.
fantabolous
6
Os png de retrocesso não são interativos: \
Inverse
5

Se você estiver trabalhando no console, ou seja, IPythonvocê pode usar plt.show(block=False)como indicado nas outras respostas. Mas se você é preguiçoso, basta digitar:

plt.show(0)

Qual será o mesmo.

Anton Protopopov
fonte
5

Eu também tive que adicionar plt.pause(0.001)ao meu código para fazê-lo funcionar dentro de um loop for (caso contrário, ele mostraria apenas o primeiro e o último gráfico):

import matplotlib.pyplot as plt

plt.scatter([0], [1])
plt.draw()
plt.show(block=False)

for i in range(10):
    plt.scatter([i], [i+1])
    plt.draw()
    plt.pause(0.001)
Martin Pecka
fonte
Isso funcionou para mim com o matplotlib3 no macOS. Ótimo!
Jerry Ma
4

No meu sistema, show () não bloqueia, embora eu quisesse que o script esperasse o usuário interagir com o gráfico (e coletar dados usando retornos de chamada 'pick_event') antes de continuar.

Para bloquear a execução até que a janela de plotagem seja fechada, usei o seguinte:

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(x,y)

# set processing to continue when window closed
def onclose(event):
    fig.canvas.stop_event_loop()
fig.canvas.mpl_connect('close_event', onclose)

fig.show() # this call does not block on my system
fig.canvas.start_event_loop_default() # block here until window closed

# continue with further processing, perhaps using result from callbacks

Observe, no entanto, que canvas.start_event_loop_default () produziu o seguinte aviso:

C:\Python26\lib\site-packages\matplotlib\backend_bases.py:2051: DeprecationWarning: Using default event loop until function specific to this GUI is implemented
  warnings.warn(str,DeprecationWarning)

embora o script ainda tenha sido executado.

Andrew
fonte
Obrigado! O Spyder importa -pylab na inicialização, o que geralmente é útil, mas significa que show () não será bloqueado quando ioff () - isso permite que você corrija esse comportamento!
perdeu
3

Eu também queria que meus gráficos exibissem o restante do código (e continuem exibindo) mesmo se houver um erro (às vezes eu uso gráficos para depuração). Eu codifiquei esse pequeno truque para que quaisquer plotagens dentro desta withdeclaração se comportem como tal.

Provavelmente, isso é um pouco fora do padrão e não é aconselhável para o código de produção. Provavelmente, existem muitas "dicas" ocultas neste código.

from contextlib import contextmanager

@contextmanager
def keep_plots_open(keep_show_open_on_exit=True, even_when_error=True):
    '''
    To continue excecuting code when plt.show() is called
    and keep the plot on displaying before this contex manager exits
    (even if an error caused the exit).
    '''
    import matplotlib.pyplot
    show_original = matplotlib.pyplot.show
    def show_replacement(*args, **kwargs):
        kwargs['block'] = False
        show_original(*args, **kwargs)
    matplotlib.pyplot.show = show_replacement

    pylab_exists = True
    try:
        import pylab
    except ImportError: 
        pylab_exists = False
    if pylab_exists:
        pylab.show = show_replacement

    try:
        yield
    except Exception, err:
        if keep_show_open_on_exit and even_when_error:
            print "*********************************************"
            print "Error early edition while waiting for show():" 
            print "*********************************************"
            import traceback
            print traceback.format_exc()
            show_original()
            print "*********************************************"
            raise
    finally:
        matplotlib.pyplot.show = show_original
        if pylab_exists:
            pylab.show = show_original
    if keep_show_open_on_exit:
        show_original()

# ***********************
# Running example
# ***********************
import pylab as pl
import time
if __name__ == '__main__':
    with keep_plots_open():
        pl.figure('a')
        pl.plot([1,2,3], [4,5,6])     
        pl.plot([3,2,1], [4,5,6])
        pl.show()

        pl.figure('b')
        pl.plot([1,2,3], [4,5,6])
        pl.show()

        time.sleep(1)
        print '...'
        time.sleep(1)
        print '...'
        time.sleep(1)
        print '...'
        this_will_surely_cause_an_error

Se / quando implementar um bom "mantenha os gráficos abertos (mesmo que ocorra um erro) e permita que novos gráficos sejam exibidos", gostaria que o script saísse corretamente se nenhuma interferência do usuário disser o contrário (para fins de execução em lote).

Posso usar algo como uma pergunta de tempo limite "Fim do script! \ NPressione p se você quiser que a saída da plotagem seja pausada (você tem 5 segundos):" em /programming/26704840/corner -casos-para-minha-espera-para-usuário-interrupção-de-implementação .

Simon Streicher
fonte
2
plt.figure(1)
plt.imshow(your_first_image)

plt.figure(2)
plt.imshow(your_second_image)

plt.show(block=False) # That's important 

raw_input("Press ENTER to exist") # Useful when you run your Python script from the terminal and you want to hold the running to see your figures until you press Enter
thanhtang
fonte
16
Como alguém pressionaria a tecla antes de existir?
grovina
2

O OP pergunta sobre a retirada de matplotlibparcelas. A maioria das respostas pressupõe a execução de comandos de um interpretador python. O caso de uso apresentado aqui é a minha preferência para testar o código em um terminal (por exemplo, bash) em que a file.pyé executado e você deseja que o gráfico apareça, mas o script python seja concluído e retorne ao prompt de comando.

Esse arquivo independente usa multiprocessingpara iniciar um processo separado para plotagem de dados matplotlib. O thread principal sai usando o os._exit(1)mencionado neste post. As os._exit()forças principais saem, mas deixam o matplotlibprocesso filho vivo e responsivo até que a janela da plotagem seja fechada. É um processo completamente separado.

Essa abordagem é um pouco como uma sessão de desenvolvimento do Matlab com janelas de figuras que vêm com um prompt de comando responsivo. Com essa abordagem, você perdeu todo o contato com o processo da janela de figuras, mas tudo bem para desenvolvimento e depuração. Apenas feche a janela e continue testando.

multiprocessingfoi desenvolvido para execução de código somente em python, o que talvez o torne mais adequado subprocess. multiprocessingé multiplataforma, portanto, isso deve funcionar bem no Windows ou Mac com pouco ou nenhum ajuste. Não há necessidade de verificar o sistema operacional subjacente. Isso foi testado no Linux, Ubuntu 18.04LTS.

#!/usr/bin/python3

import time
import multiprocessing
import os

def plot_graph(data):
    from matplotlib.pyplot import plot, draw, show
    print("entered plot_graph()")
    plot(data)
    show() # this will block and remain a viable process as long as the figure window is open
    print("exiting plot_graph() process")

if __name__ == "__main__":
    print("starting __main__")
    multiprocessing.Process(target=plot_graph, args=([1, 2, 3],)).start()
    time.sleep(5)
    print("exiting main")
    os._exit(0) # this exits immediately with no cleanup or buffer flushing

A execução file.pyexibe uma janela de figura e __main__sai, mas a janela de figura multiprocessing+ matplotlibpermanece responsiva com zoom, pan e outros botões porque é um processo independente.

Verifique os processos no prompt de comando bash com:

ps ax|grep -v grep |grep file.py

Marc Compere
fonte
Eu estava tentando usar sua solução, mas ela não parece funcionar para mim e estou tentando descobrir o porquê. Não estou executando o código pelo terminal, mas pelo Pycharm IDE, se isso faz alguma diferença, embora não deva.
ttsesm
1
ok, o que finalmente funcionou para mim foi definir o processo filho .daemon=Falsecomo descrito aqui stackoverflow.com/a/49607287/1476932 No entanto, sys.exit()não finalizei o processo pai conforme descrito lá até eu fechar a janela filho. Por outro lado, o uso os._exit(0)do exemplo acima funcionou.
ttsesm
1

Na minha opinião, as respostas neste tópico fornecem métodos que não funcionam para todos os sistemas e em situações mais complexas, como animações. Sugiro dar uma olhada na resposta do MiKTeX no seguinte segmento, onde foi encontrado um método robusto: Como esperar até que a animação matplotlib termine?

MikeTeX
fonte
0

Se você deseja abrir várias figuras, mantendo todas abertas, esse código funcionou para mim:

show(block=False)
draw()
DomDev
fonte
show (block = False) foi preterido e agora não está mais funcionando #
Bogdan
0

Embora não esteja respondendo diretamente à solicitação dos OPs, estou postando esta solução alternativa, pois pode ajudar alguém nessa situação:

  • Estou criando um .exe com o pyinstaller, pois não consigo instalar o python onde preciso gerar os gráficos, por isso preciso do script python para gerar o gráfico, salve-o como .png, feche-o e continue com o próximo, implementado como vários gráficos no um loop ou usando uma função.

para este Im usando:

import matplotlib.pyplot as plt
#code generating the plot in a loop or function
#saving the plot
plt.savefig(var+'_plot.png',bbox_inches='tight', dpi=250) 
#you can allways reopen the plot using
os.system(var+'_plot.png') # unfortunately .png allows no interaction.
#the following avoids plot blocking the execution while in non-interactive mode
plt.show(block=False) 
#and the following closes the plot while next iteration will generate new instance.
plt.close() 

Onde "var" identifica a plotagem no loop, para que não seja substituída.

patowski1981
fonte
-1

Use plt.show(block=False)e no final da sua chamada de script plt.show().

Isso garantirá que a janela não seja fechada quando o script for concluído.

Ken Mueller
fonte
Veja a resposta de @ nico-schlömer
Josh Wolff