Быстрый Live Plotting в Matplotlib / PyPlot
В течение многих лет я изо всех сил пытался получить эффективный прямой эфир в matplotlib, и по сей день я остаюсь неудовлетворенным.
я хочу redraw_figure
функция, которая обновляет фигуру "вживую" (по мере выполнения кода) и отображает последние графики, если я остановлюсь на точке останова.
Вот некоторый демонстрационный код:
import time
from matplotlib import pyplot as plt
import numpy as np
def live_update_demo():
plt.subplot(2, 1, 1)
h1 = plt.imshow(np.random.randn(30, 30))
redraw_figure()
plt.subplot(2, 1, 2)
h2, = plt.plot(np.random.randn(50))
redraw_figure()
t_start = time.time()
for i in xrange(1000):
h1.set_data(np.random.randn(30, 30))
redraw_figure()
h2.set_ydata(np.random.randn(50))
redraw_figure()
print 'Mean Frame Rate: %.3gFPS' % ((i+1) / (time.time() - t_start))
def redraw_figure():
plt.draw()
plt.pause(0.00001)
live_update_demo()
Графики должны обновляться в режиме реального времени при запуске кода, и мы должны видеть последние данные при остановке на любой точке останова после redraw_figure()
, Вопрос в том, как лучше всего реализовать redraw_figure()
В реализации выше (plt.draw(); plt.pause(0.00001)
), это работает, но очень медленно (~3.7FPS)
Я могу реализовать это как:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
И он работает быстрее (~11FPS), но графики не обновляются, когда вы останавливаетесь на точках останова (например, если я ставлю точку останова на t_start = ...
линия, второй сюжет не появляется).
Как ни странно, то, что действительно работает, вызывает вызов шоу дважды:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
plt.show(block=False)
Что дает ~ 11FPS и сохраняет графики в актуальном состоянии, если вы прервались на любой линии.
Теперь я слышал, что ключевое слово "block" устарело. А вызов одной и той же функции дважды кажется странным, вероятно, непереносимым хаком.
Итак, что я могу добавить в эту функцию, которая будет отображаться с разумной частотой кадров, не является гигантским клуджем и предпочтительно будет работать с бэкэндами и системами?
Некоторые заметки:
- Я на OSX, и использую
TkAgg
бэкэнд, но решения на любой бэкэнд / системе приветствуются - Интерактивный режим "Вкл" не будет работать, потому что он не обновляет в прямом эфире. Он просто обновляется в консоли Python, когда интерпретатор ожидает ввода данных пользователем.
Блог предложил реализацию:
def redraw_figure (): fig = plt.gcf () fig.canvas.draw () fig.canvas.flush_events ()
Но, по крайней мере, в моей системе это совсем не перерисовывает графики.
Так что, если у кого-то есть ответ, вы бы очень обрадовали меня и тысячи других. Их счастье, вероятно, будет распространяться на их друзей и родственников, а также на их друзей и родственников и так далее, чтобы вы могли потенциально улучшить жизнь миллиардов.
Выводы
ImportanceOfBeingErnest показывает, как вы можете использовать blit для более быстрой прорисовки, но это не так просто, как поместить что-то другое в redraw_figure
функция (вам нужно отслеживать, какие вещи перерисовать).
2 ответа
Прежде всего, код, который размещен в вопросе, работает на моей машине со скоростью 7 кадров в секунду, а QT4Agg используется в качестве бэкэнда.
Теперь, как было предложено во многих сообщениях, как здесь или здесь, используя blit
может быть вариант. Хотя в этой статье упоминается, что блиц вызывает сильную утечку памяти, я не мог этого наблюдать.
Я немного изменил ваш код и сравнил частоту кадров с использованием Blit и без него. Код ниже дает
- 18 кадров в секунду при запуске без блитта
- 28 кадров в секунду с блитом
Код:
import time
from matplotlib import pyplot as plt
import numpy as np
def live_update_demo(blit = False):
x = np.linspace(0,50., num=100)
X,Y = np.meshgrid(x,x)
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)
fig.canvas.draw() # note that the first draw comes before setting data
h1 = ax1.imshow(X, vmin=-1, vmax=1, interpolation="None", cmap="RdBu")
h2, = ax2.plot(x, lw=3)
text = ax2.text(0.8,1.5, "")
ax2.set_ylim([-1,1])
if blit:
# cache the background
axbackground = fig.canvas.copy_from_bbox(ax1.bbox)
ax2background = fig.canvas.copy_from_bbox(ax2.bbox)
t_start = time.time()
k=0.
for i in np.arange(1000):
h1.set_data(np.sin(X/3.+k)*np.cos(Y/3.+k))
h2.set_ydata(np.sin(x/3.+k))
tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= ((i+1) / (time.time() - t_start)) )
text.set_text(tx)
#print tx
k+=0.11
if blit:
# restore background
fig.canvas.restore_region(axbackground)
fig.canvas.restore_region(ax2background)
# redraw just the points
ax1.draw_artist(h1)
ax2.draw_artist(h2)
# fill in the axes rectangle
fig.canvas.blit(ax1.bbox)
fig.canvas.blit(ax2.bbox)
# in this post http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
# it is mentionned that blit causes strong memory leakage.
# however, I did not observe that.
else:
# redraw everything
fig.canvas.draw()
fig.canvas.flush_events()
plt.pause(0.000000000001)
#plt.pause calls canvas.draw(), as can be read here:
#http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
#however with Qt4 (and TkAgg??) this is needed. It seems,using a different backend,
#one can avoid plt.pause() and gain even more speed.
live_update_demo(True) # 28 fps
#live_update_demo(False) # 18 fps
Обновить:
Для более быстрой прорисовки можно рассмотреть использование pyqtgraph.
Как сказано в документации pyqtgraph: "Для построения графиков pyqtgraph далеко не так полон / зрел, как matplotlib, но работает намного быстрее".
Я перенес вышеприведенный пример на pyqtgraph. И хотя это выглядит некрасиво, на моей машине он работает с 250 кадрами в секунду.
Подводя итог,
- matplotlib (без блиттинга): 18 кадров в секунду
- matplotlib (с блиттингом): 28 кадров в секунду
- pyqtgraph: 250 кадров в секунду
код pyqtgraph:
import sys
import time
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#### Create Gui Elements ###########
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtGui.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.label = QtGui.QLabel()
self.mainbox.layout().addWidget(self.label)
self.view = self.canvas.addViewBox()
self.view.setAspectLocked(True)
self.view.setRange(QtCore.QRectF(0,0, 100, 100))
# image plot
self.img = pg.ImageItem(border='w')
self.view.addItem(self.img)
self.canvas.nextRow()
# line plot
self.otherplot = self.canvas.addPlot()
self.h2 = self.otherplot.plot(pen='y')
#### Set Data #####################
self.x = np.linspace(0,50., num=100)
self.X,self.Y = np.meshgrid(self.x,self.x)
self.counter = 0
self.fps = 0.
self.lastupdate = time.time()
#### Start #####################
self._update()
def _update(self):
self.data = np.sin(self.X/3.+self.counter/9.)*np.cos(self.Y/3.+self.counter/9.)
self.ydata = np.sin(self.x/3.+ self.counter/9.)
self.img.setImage(self.data)
self.h2.setData(self.ydata)
now = time.time()
dt = (now-self.lastupdate)
if dt <= 0:
dt = 0.000000000001
fps2 = 1.0 / dt
self.lastupdate = now
self.fps = self.fps * 0.9 + fps2 * 0.1
tx = 'Mean Frame Rate: {fps:.3f} FPS'.format(fps=self.fps )
self.label.setText(tx)
QtCore.QTimer.singleShot(1, self._update)
self.counter += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Вот один из способов построения прямых графиков: получить график в виде массива изображений, а затем вывести изображение на многопоточный экран.
Пример использования экрана пиформул (~ 30 FPS):
import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time
fig = plt.figure()
screen = pf.screen(title='Plot')
start = time.time()
for i in range(10000):
t = time.time() - start
x = np.linspace(t-3, t, 100)
y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
plt.xlim(t-3,t)
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