在 Matplotlib 中快速渲染到缓冲区
Fast rendering to buffer in Matplotlib
我有一个 Kivy 应用程序,它使用 matplotlib 在应用程序 GUI 中渲染图形。这意味着应用程序创建一个 matplotlib 图并获取图的缓冲区以将其显示在 Image 小部件中。
现在,每次我想更新图形时,我都会重新创建一个图形并绘制所有内容,调用 refresh_gui_image
。
import matplotlib.pyplot as plt
def draw_matplotlib_buffer(image, *elements):
fig = plt.figure(figsize=(5,5), dpi=200)
ax = plt.Axes([0, 0, 1, 1])
ax.set_axis_off()
fig.add_axis(ax)
ax.imshow(image)
for elem in elements:
# Suppose such a function exists and return a matplotlib.collection.PatchCollection
patchCollection = elem.get_collection()
ax.add_collection(patchCollection)
buffer = fig.canvas.print_to_buffer()
plt.close(fig)
return buffer
# imageWidget is a kivy Widget instance
def refresh_gui_image(imageWidget, image, *elements):
size = image.shape()
imageBuffer = draw_matplotlib_buffer(image, *elements)
imageWidget.texture.blit_buffer(imageBuffer, size=size, colorfmt='rgba', bufferfmt='ubyte')
imageWidget.canvas.ask_update()
在上面的代码中,*elements
代表多组对象。通常,我有 2 到 4 个集合,其中包含 10 到 2000 个对象。每个对象都用一个补丁表示,每个集合是图上的一个 PatchCollection。
效果很好。使用当前代码,每次调用 refresh_gui_image
时都会重新绘制每个补丁。当集合变大(如 2000 个)对象时,更新速度太慢(几秒钟)。我想用 matplotlib 进行更快的渲染,知道 一些集合不必重绘,图像留在背景中,也不必重绘.
我知道可以使用 blitting 和 animated artists,这是我尝试过的,遵循 matplotlib 文档的 this tutorial:
import matplotlib.pyplot as plt
# fig and ax are now global variable
# bg holds the background that stays identical
fig = None
ax = None
bg = None
def init_matplotlib_data(image, *elements):
global fig, ax, bg
fig = plt.figure(figsize=(5,5), dpi=200)
ax = plt.Axes([0, 0, 1, 1])
ax.set_axis_off()
fig.add_axis(ax)
ax.imshow(image)
fig.canvas.draw() # I don't want a window to open, just want to have a cached renderer
bg = fig.canvas.copy_from_bbox(fig.bbox)
for elem in elements:
# Suppose such a function exists and return a matplotlib.collection.PatchCollection
patchCollection = elem.get_collection(animated=True)
patchCollection.set_animated(True)
ax.add_collection(patchCollection)
def draw_matplotlib_buffer(image, *artists_to_redraw):
global fig, ax, bg
fig.canvas.restore_region(bg)
for artist in artists_to_redraw:
ax.draw_artist(artist)
fig.canvas.blit(fig.bbox)
buffer = fig.canvas.print_to_buffer()
return buffer
我调用 init_matplotlib_data
一次,refresh_gui_image
需要多少次就调用多少次,我需要更新艺术家。关键是我正确地获取了我的图像背景,但是我无法成功地获取 fig.canvas.print_to_buffer()
返回的缓冲区中的补丁集合。我取消设置集合的 animated
标志,这次它们正确显示。在我看来,经过一些测试 ax.draw_artist()
和 fig.canvas.blit()
没有效果。我不明白的另一个行为是,如果我将 animated=True
传递给 ax.imshow(image)
,图像仍然被绘制。
为什么 ax.draw_artist
和 fig.canvas.blit
函数没有按预期更新 fig.canvas.print_to_buffer
返回的缓冲区?
显然,blitting 是用于 GUI 的特殊功能。即使Agg后端支持blitting,也不代表blitting可以单独使用
我想出了一个解决方案,我可以存储我想画的每一位艺术家,并在需要时更改他们的数据。然后我使用 fig.canvas.print_to_buffer()
,我不确定它到底做了什么,但我认为这个数字已经完全重绘了。它可能不像 blitting 那样快,但它的优点是无需为每次更新重新分配和重新创建每个艺术家。也可以通过调用艺术家的 remove()
方法将艺术家从 canvas 中移除,然后用 ax.add_artist(..)
.
重新放置。
我认为这个解决方案回答了我的问题,因为它是在将 canvas 转储到缓冲区时使用 matplotlib 进行动态绘图的最快解决方案。
我有一个 Kivy 应用程序,它使用 matplotlib 在应用程序 GUI 中渲染图形。这意味着应用程序创建一个 matplotlib 图并获取图的缓冲区以将其显示在 Image 小部件中。
现在,每次我想更新图形时,我都会重新创建一个图形并绘制所有内容,调用 refresh_gui_image
。
import matplotlib.pyplot as plt
def draw_matplotlib_buffer(image, *elements):
fig = plt.figure(figsize=(5,5), dpi=200)
ax = plt.Axes([0, 0, 1, 1])
ax.set_axis_off()
fig.add_axis(ax)
ax.imshow(image)
for elem in elements:
# Suppose such a function exists and return a matplotlib.collection.PatchCollection
patchCollection = elem.get_collection()
ax.add_collection(patchCollection)
buffer = fig.canvas.print_to_buffer()
plt.close(fig)
return buffer
# imageWidget is a kivy Widget instance
def refresh_gui_image(imageWidget, image, *elements):
size = image.shape()
imageBuffer = draw_matplotlib_buffer(image, *elements)
imageWidget.texture.blit_buffer(imageBuffer, size=size, colorfmt='rgba', bufferfmt='ubyte')
imageWidget.canvas.ask_update()
在上面的代码中,*elements
代表多组对象。通常,我有 2 到 4 个集合,其中包含 10 到 2000 个对象。每个对象都用一个补丁表示,每个集合是图上的一个 PatchCollection。
效果很好。使用当前代码,每次调用 refresh_gui_image
时都会重新绘制每个补丁。当集合变大(如 2000 个)对象时,更新速度太慢(几秒钟)。我想用 matplotlib 进行更快的渲染,知道 一些集合不必重绘,图像留在背景中,也不必重绘.
我知道可以使用 blitting 和 animated artists,这是我尝试过的,遵循 matplotlib 文档的 this tutorial:
import matplotlib.pyplot as plt
# fig and ax are now global variable
# bg holds the background that stays identical
fig = None
ax = None
bg = None
def init_matplotlib_data(image, *elements):
global fig, ax, bg
fig = plt.figure(figsize=(5,5), dpi=200)
ax = plt.Axes([0, 0, 1, 1])
ax.set_axis_off()
fig.add_axis(ax)
ax.imshow(image)
fig.canvas.draw() # I don't want a window to open, just want to have a cached renderer
bg = fig.canvas.copy_from_bbox(fig.bbox)
for elem in elements:
# Suppose such a function exists and return a matplotlib.collection.PatchCollection
patchCollection = elem.get_collection(animated=True)
patchCollection.set_animated(True)
ax.add_collection(patchCollection)
def draw_matplotlib_buffer(image, *artists_to_redraw):
global fig, ax, bg
fig.canvas.restore_region(bg)
for artist in artists_to_redraw:
ax.draw_artist(artist)
fig.canvas.blit(fig.bbox)
buffer = fig.canvas.print_to_buffer()
return buffer
我调用 init_matplotlib_data
一次,refresh_gui_image
需要多少次就调用多少次,我需要更新艺术家。关键是我正确地获取了我的图像背景,但是我无法成功地获取 fig.canvas.print_to_buffer()
返回的缓冲区中的补丁集合。我取消设置集合的 animated
标志,这次它们正确显示。在我看来,经过一些测试 ax.draw_artist()
和 fig.canvas.blit()
没有效果。我不明白的另一个行为是,如果我将 animated=True
传递给 ax.imshow(image)
,图像仍然被绘制。
为什么 ax.draw_artist
和 fig.canvas.blit
函数没有按预期更新 fig.canvas.print_to_buffer
返回的缓冲区?
显然,blitting 是用于 GUI 的特殊功能。即使Agg后端支持blitting,也不代表blitting可以单独使用
我想出了一个解决方案,我可以存储我想画的每一位艺术家,并在需要时更改他们的数据。然后我使用 fig.canvas.print_to_buffer()
,我不确定它到底做了什么,但我认为这个数字已经完全重绘了。它可能不像 blitting 那样快,但它的优点是无需为每次更新重新分配和重新创建每个艺术家。也可以通过调用艺术家的 remove()
方法将艺术家从 canvas 中移除,然后用 ax.add_artist(..)
.
我认为这个解决方案回答了我的问题,因为它是在将 canvas 转储到缓冲区时使用 matplotlib 进行动态绘图的最快解决方案。