Построение неблокирующим способом с Matplotlib

138

Последние несколько дней я играл с Numpy и matplotlib. У меня возникли проблемы с попыткой построить график функции matplotlib без блокировки выполнения. Я знаю, что на SO уже есть много тем, задающих похожие вопросы, и я довольно много гуглил, но мне не удалось заставить эту работу.

Я пробовал использовать show (block = False), как предлагают некоторые люди, но все, что я получил, - это замороженное окно. Если я просто вызываю show (), результат отображается правильно, но выполнение блокируется до закрытия окна. Судя по другим темам, которые я читал, я подозреваю, что работает ли show (block = False) или нет, зависит от серверной части. Это верно? Моя серверная часть - Qt4Agg. Не могли бы вы взглянуть на мой код и сказать, видите ли вы что-то не так? Вот мой код. Спасибо за любую помощь.

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. Я забыл сказать, что хотел бы обновлять существующее окно каждый раз, когда что-то рисую, вместо того, чтобы создавать новое.

opetroch
источник
1
вы пробовали интерактивный режим matplotlib plt.ion()раньше plt.show()? Тогда он должен быть неблокирующим, поскольку каждый график порождается в дочернем потоке.
Анзель
@Anzel Я только что попробовал, но, похоже, без разницы.
opetroch
3
Как вы запускаете свой сценарий? Если я запустил ваш пример кода из командной строки / терминала, он, похоже, будет работать нормально, но я думаю, что в прошлом у меня были проблемы, когда я пытался делать подобные вещи из IPython QtConsole или IDE.
Мариус
1
@Marius Ага !! Ты прав. На самом деле я запускаю его с консоли моей IDE (PyCharm). При запуске из командной строки plt.show (block = False) работает нормально! Не буду ли я слишком многого спрашивать, если спрошу вас, нашли ли вы какую-либо идею / решение для этого? Большое спасибо!
opetroch
Я действительно не знаю, извини. Я действительно не понимаю деталей того, как matplotlib взаимодействует с консолью, поэтому я обычно просто переключаюсь на запуск из командной строки, если мне нужно сделать это с помощью matplotlib.
Мариус

Ответы:

167

Я долго искал решения и нашел ответ .

Похоже, для того , чтобы получить то , что вы (и я) хочу, вам нужно сочетание plt.ion(), plt.show()(не с block=False) и, самое главное, plt.pause(.001)(или любой другой раз , когда вы хотите). Пауза необходима , потому что GUI события происходят в то время как основной код спит, в том числе рисунка. Возможно, это реализовано путем сбора времени из спящего потока, так что, возможно, IDE вмешиваются в это - я не знаю.

Вот реализация, которая работает для меня на python 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
источник
3
Ваш ответ очень помог мне в решении похожей проблемы, с которой я столкнулся. Раньше я plt.drawследил за ним, plt.show(block = False)но потом он перестал работать: Рисунок не отвечает, закрытие привело к сбою iPython. Мое решение заключалось в удалении каждого экземпляра plt.draw()и замене его на plt.pause(0.001). Вместо того, чтобы за ним следовало plt.show(block = False)как plt.drawбыло раньше, ему предшествовали символы plt.ion()и plt.show(). Теперь у меня есть, MatplotlibDeprecationWarningно он позволяет мне строить цифры, так что я доволен этим решением.
blue_chip 08
3
Обратите внимание, что в python 2.7 вам нужно использовать raw_inputnot input. Смотрите здесь
Крис
Действительно полезный обходной путь, когда реактивный "анимированный" подход невозможен! Кто-нибудь знает, как избавиться от предупреждения об устаревании?
Фредерик Фортье
Может кто-нибудь сказать мне, почему я получаю замороженную командную строку, когда пытаюсь добавить plt.ion перед plt.show?
Габриэль Аугусто
@GabrielAugusto Я не уверен, что могло вызвать это, и я не совсем понимаю, что вы имеете в виду. Я только что протестировал этот пример на Python 3.6, и он все еще работает. Если вы использовали тот же шаблон, и он зависает, возможно, с вашей установкой что-то не так. Вы должны сначала проверить, работает ли нормальное черчение. Если вы пробовали что-то другое, в комментариях ничего не поделать. В любом случае вы можете задать отдельный вопрос.
krs013
23

Вот простой прием, который мне подходит:

  1. Используйте аргумент block = False внутри show: plt.show (block = False)
  2. Используйте другой plt.show () в конце скрипта .py.

Пример :

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

Примечание : plt.show()это последняя строка моего скрипта.

seralouk
источник
8
Это создает (для меня, в Linux, Anaconda, Python 2.7, бэкэнд по умолчанию) пустое окно, которое остается пустым до самого конца выполнения, когда оно, наконец, заполняется. Не полезно для обновления графика во время выполнения. :-(
sh37211 04
@ sh37211 Не уверен, какова ваша цель. В некоторых случаях, когда вы пытаетесь построить что-то, но после команды построения у вас есть другие команды, это полезно, поскольку позволяет вам строить график и выполнять другие команды. Дополнительную информацию см. В этом сообщении: stackoverflow.com/questions/458209/… . Если вы хотите обновить сюжет, это должен быть другой способ.
seralouk
17

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

Результат:

Синусоидальная анимация

Отказ от ответственности: я сопровождаю pyformulas.

Ссылка: Matplotlib: сохранить график в массив numpy

Изображение по умолчанию
источник
9

Многие из этих ответов слишком раздуты, и, судя по тому, что я могу найти, ответ не так уж и сложен для понимания.

Вы можете использовать, plt.ion()если хотите, но я нашел использование plt.draw()столь же эффективным

Для моего конкретного проекта , я черчение изображения, но вы можете использовать plot()или scatter()или что - то вместо того figimage(), это не имеет значения.

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

Или

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

Если вы используете реальную цифру.
Я использовал ответы @ krs013 и @Default Picture, чтобы понять это.
Надеюсь, это избавит кого-то от необходимости запускать каждую фигуру в отдельном потоке или от необходимости читать эти романы, чтобы понять это.

iggy12345
источник
3

Живой график

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

если объем данных слишком велик, вы можете снизить частоту обновления с помощью простого счетчика

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

Участок после выхода из программы

Это была моя настоящая проблема, на которую не мог найти удовлетворительный ответ, я хотел, чтобы график не закрывался после завершения сценария (например, MATLAB),

Если задуматься, то после завершения сценария программа завершается, и нет логического способа сохранить сюжет таким образом, поэтому есть два варианта

  1. заблокировать сценарий от выхода (это plt.show (), а не то, что я хочу)
  2. запустить сюжет в отдельном потоке (слишком сложно)

меня это не устроило, поэтому я нашел другое нестандартное решение

SaveToFile и просмотр во внешней программе просмотра

Для этого сохранение и просмотр должны быть быстрыми, а программа просмотра не должна блокировать файл и должна автоматически обновлять содержимое.

Выбор формата для сохранения

векторные форматы небольшие и быстрые

  • SVG хорош, но не может найти для него хорошей программы просмотра, кроме веб-браузера, который по умолчанию требует обновления вручную.
  • PDF может поддерживать векторные форматы, и есть легкие программы просмотра, которые поддерживают обновление в реальном времени.

Быстрый легкий просмотрщик с обновлением в реальном времени

Для PDF есть несколько хороших вариантов

  • В Windows я использую SumatraPDF, который бесплатный, быстрый и легкий (в моем случае используется только 1,8 МБ ОЗУ)

  • В Linux есть несколько вариантов, таких как Evince (GNOME) и Ocular (KDE).

Образец кода и результаты

Пример кода для вывода графика в файл

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

после первого запуска откройте выходной файл в одной из упомянутых выше программ просмотра и наслаждайтесь.

Вот скриншот VSCode вместе с SumatraPDF, также процесс достаточно быстрый, чтобы получить частоту обновления в режиме полу-реального времени (я могу получить около 10 Гц на моей настройке, просто используйте time.sleep()между интервалами) pyPlot нелипкая

Ali80
источник
2

Ответ Игги был для меня самым простым, но я получил следующую ошибку при выполнении следующей subplotкоманды, которой не было, когда я просто выполнял show:

MatplotlibDeprecationWarning: добавление осей с использованием тех же аргументов, что и предыдущие оси, в настоящее время повторно использует более ранний экземпляр. В будущей версии всегда будет создаваться и возвращаться новый экземпляр. Между тем, это предупреждение можно подавить, и обеспечить дальнейшее поведение, передав уникальную метку каждому экземпляру осей.

Чтобы избежать этой ошибки, полезно закрыть (или очистить ) график после того, как пользователь нажмет Enter.

Вот код, который у меня сработал:

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
источник
0

Пакет Python drawow позволяет обновлять график в реальном времени без блокировки.
Он также работает с веб-камерой и OpenCV, например, для построения измерений для каждого кадра.
Смотрите исходный пост .

Исмаэль ЭЛЬ-АТИФИ
источник