高效的Matplotlib重绘 [英] Efficient Matplotlib Redrawing

查看:117
本文介绍了高效的Matplotlib重绘的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Matplotlib允许用户通过鼠标单击选择有趣的数据点,使用的方法与

I'm using Matplotlib to allow the user to select interesting data points with mouseclicks, using a very similar method to this answer.

有效地,散点图显示在热图图像上,单击鼠标可以添加或删除散点.

Effectively, a scatter plot is displayed over a heatmap image and mouse clicks can add or remove scatter points.

我的数据是使用pcolormesh()在后台绘制的,因此,当我使用axis.figure.canvas.draw()更新画布时,会同时绘制散点和背景热图.考虑到热图的大小,这对于可用的界面来说太慢了.

My data is drawn in the background using pcolormesh(), so when I update the canvas using axis.figure.canvas.draw() both the scatter points and the background heatmap are redrawn. Given the size of the heatmap, this is too slow for a usable interface.

是否可以有选择地仅重绘散点而无需重绘背景?

Is there a way of selectively redrawing just the scatter points without redrawing the background?

示例代码:

points = []  # Holds a list of (x,y) scatter points 

def onclick(event):
    # Click event handler to add points
    points.append( (event.x, event.y) )
    ax.figure.canvas.draw()

fig = plt.figure()
ax = plt.figure()
# Plot the background
ax.pcolormesh(heatmap_data)

# Add the listener to handle clicks
cid = fig.canvas.mpl_connect("button_press_event", onclick)
plt.show()

推荐答案

当然!你想要的是发短信.如果您不是在编写gui,则可以使用matplotlib.animation来简化其中的一些操作,但是如果您想让内容具有交互性,则需要直接处理它.

Sure! What you want is blitting. If you weren't writing a gui, you could simplify some of this by using matplotlib.animation, but you'll need to handle it directly if you want things to be interactive.

用matplotlib术语,您需要组合fig.canvas.copy_from_bbox,然后交替调用fig.canvas.restore_region(background)ax.draw_artist(what_you_want_to_draw)fig.canvas.blit:

In matplotlib terms, you want a combination of fig.canvas.copy_from_bbox, and then alternately call fig.canvas.restore_region(background), ax.draw_artist(what_you_want_to_draw) and fig.canvas.blit:

background = fig.canvas.copy_from_bbox(ax.bbox)

for x, y in user_interactions:
    fig.canvas.restore_region(background)
    points.append([x, y])
    scatter.set_offsets(points)
    ax.draw_artist(scatter)
    fig.canvas.blit(ax.bbox)


简单的划痕示例:添加点

在您的情况下,如果仅添加点,则实际上可以跳过保存和还原背景.但是,如果走那条路线,由于抗锯齿点会相互重复重绘,因此您会对该图进行一些微妙的更改.


Simple Blitting Example: Adding Points

In your case, if you're only adding points, you can actually skip saving and restoring the background. If you go that route, though, you'll wind up with some subtle changes to the plot due to antialiased points being repeatedly redrawn on top of each other.

无论如何,这是您想要的事物类型的最简单示例.如上所述,这仅涉及添加点,并且跳过了保存和恢复背景的操作:

At any rate, here's the simplest possible example of the type of thing you're wanting. This only deals with adding points, and skips saving and restoring the background as I mentioned above:

import matplotlib.pyplot as plt
import numpy as np

def main():
    fig, ax = plt.subplots()
    ax.pcolormesh(np.random.random((100, 100)), cmap='gray')

    ClickToDrawPoints(ax).show()

class ClickToDrawPoints(object):
    def __init__(self, ax):
        self.ax = ax
        self.fig = ax.figure
        self.xy = []
        self.points = ax.scatter([], [], s=200, color='red', picker=20)
        self.fig.canvas.mpl_connect('button_press_event', self.on_click)

    def on_click(self, event):
        if event.inaxes is None:
            return
        self.xy.append([event.xdata, event.ydata])
        self.points.set_offsets(self.xy)
        self.ax.draw_artist(self.points)
        self.fig.canvas.blit(self.ax.bbox)

    def show(self):
        plt.show()

main()


有时候简单就是太简单

但是,假设我们要右键单击以删除一个点.


Sometimes Simple is Too Simple

However, let's say we wanted to make right-clicks delete a point.

在这种情况下,我们需要能够在不重绘背景的情况下恢复背景.

In that case, we need to be able to restore the background without redrawing it.

好的,一切都很好.我们将使用与答案顶部提到的伪代码段相似的内容.

Ok, all well and good. We'll use something similar to the pseudocode snippet I mentioned at the top of the answer.

但是,有一个警告:如果调整了图的大小,我们需要更新背景.同样,如果以交互方式缩放/平移轴,则需要更新背景.基本上,您需要在绘制绘图时随时更新背景.

However, there's a caveat: If the figure is resized, we need to update the background. Similarly, if the axes is interactively zoomed/panned, we need to update the background. Basically, you need to update the background anytime the plot is drawn.

很快,您需要变得相当复杂.

Pretty soon you need to get fairly complex.

这是您最终摆放到位的脚手架"类型的一般示例.

Here's a general example of the kind of "scaffolding" you wind up putting in place.

这有点效率低下,因为情节被绘制了两次. (例如,平移将是 slow ).可以解决这个问题,但是我将这些例子再留一遍.

This is somewhat inefficient, as the plot gets drawn twice. (e.g. panning will be slow). It is possible to get around that, but I'll leave those examples for another time.

这实现了添加点,拖动点和删除点.要在交互式缩放/平移后添加/拖动点,请再次单击工具栏上的缩放/平移工具以将其禁用.

This implements adding points, dragging points, and deleting points. To add/drag a point after interactively zooming/panning, click to zoom/pan tool on the toolbar again to disable them.

这是一个相当复杂的示例,但希望它可以使人们理解通常以交互方式绘制/拖动/编辑/删除matplotlib艺术家而无需重新绘制整个图的框架类型.

This is a fairly complex example, but hopefully it gives a sense of the type of framework that one would typically build to interactively draw/drag/edit/delete matplotlib artists without redrawing the entire plot.

import numpy as np
import matplotlib.pyplot as plt

class DrawDragPoints(object):
    """
    Demonstrates a basic example of the "scaffolding" you need to efficiently
    blit drawable/draggable/deleteable artists on top of a background.
    """
    def __init__(self):
        self.fig, self.ax = self.setup_axes()
        self.xy = []
        self.tolerance = 10
        self._num_clicks = 0

        # The artist we'll be modifying...
        self.points = self.ax.scatter([], [], s=200, color='red',
                                      picker=self.tolerance, animated=True)

        connect = self.fig.canvas.mpl_connect
        connect('button_press_event', self.on_click)
        self.draw_cid = connect('draw_event', self.grab_background)

    def setup_axes(self):
        """Setup the figure/axes and plot any background artists."""
        fig, ax = plt.subplots()

        # imshow would be _much_ faster in this case, but let's deliberately
        # use something slow...
        ax.pcolormesh(np.random.random((1000, 1000)), cmap='gray')

        ax.set_title('Left click to add/drag a point\nRight-click to delete')
        return fig, ax

    def on_click(self, event):
        """Decide whether to add, delete, or drag a point."""
        # If we're using a tool on the toolbar, don't add/draw a point...
        if self.fig.canvas.toolbar._active is not None:
            return

        contains, info = self.points.contains(event)
        if contains:
            i = info['ind'][0]
            if event.button == 1:
                self.start_drag(i)
            elif event.button == 3:
                self.delete_point(i)
        else:
            self.add_point(event)

    def update(self):
        """Update the artist for any changes to self.xy."""
        self.points.set_offsets(self.xy)
        self.blit()

    def add_point(self, event):
        self.xy.append([event.xdata, event.ydata])
        self.update()

    def delete_point(self, i):
        self.xy.pop(i)
        self.update()

    def start_drag(self, i):
        """Bind mouse motion to updating a particular point."""
        self.drag_i = i
        connect = self.fig.canvas.mpl_connect
        cid1 = connect('motion_notify_event', self.drag_update)
        cid2 = connect('button_release_event', self.end_drag)
        self.drag_cids = [cid1, cid2]

    def drag_update(self, event):
        """Update a point that's being moved interactively."""
        self.xy[self.drag_i] = [event.xdata, event.ydata]
        self.update()

    def end_drag(self, event):
        """End the binding of mouse motion to a particular point."""
        for cid in self.drag_cids:
            self.fig.canvas.mpl_disconnect(cid)

    def safe_draw(self):
        """Temporarily disconnect the draw_event callback to avoid recursion"""
        canvas = self.fig.canvas
        canvas.mpl_disconnect(self.draw_cid)
        canvas.draw()
        self.draw_cid = canvas.mpl_connect('draw_event', self.grab_background)

    def grab_background(self, event=None):
        """
        When the figure is resized, hide the points, draw everything,
        and update the background.
        """
        self.points.set_visible(False)
        self.safe_draw()

        # With most backends (e.g. TkAgg), we could grab (and refresh, in
        # self.blit) self.ax.bbox instead of self.fig.bbox, but Qt4Agg, and
        # some others, requires us to update the _full_ canvas, instead.
        self.background = self.fig.canvas.copy_from_bbox(self.fig.bbox)

        self.points.set_visible(True)
        self.blit()

    def blit(self):
        """
        Efficiently update the figure, without needing to redraw the
        "background" artists.
        """
        self.fig.canvas.restore_region(self.background)
        self.ax.draw_artist(self.points)
        self.fig.canvas.blit(self.fig.bbox)

    def show(self):
        plt.show()

DrawDragPoints().show()

这篇关于高效的Matplotlib重绘的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆