如何在wxPython中使用matplotlib blitting将matplot.patches添加到matplotlib图中? [英] How to use matplotlib blitting to add matplot.patches to an matplotlib plot in wxPython?

查看:144
本文介绍了如何在wxPython中使用matplotlib blitting将matplot.patches添加到matplotlib图中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用matplotlib库制作图,并在wxPython GUI中显示它.我正在从LIDAR仪器绘制大量数据点.问题是,我想在此图中绘制矩形以指示有趣的区域.但是,当我在与绘图相同的轴上绘制一个矩形时,整个绘图将重新绘制,这会花费很多时间.这是因为self.canvas.draw()这个函数可以重新绘制所有内容.

I am making a plot using the matplotlib library and showing it in my wxPython GUI. I am plotting a massive amount of data points from a LIDAR instrument. The thing is, I would like to draw rectangles in this plot to indicate interesting areas. But when I draw a rectangle on the same axes as the plot, the whole plot gets replotted which takes lots of time. This is because of the self.canvas.draw(), a function which replots everything.

该代码在GUI中显示如下:

The code gets displayed as follows in the GUI:

GUI的打印屏幕

这里是该问题的最小工作示例. U可以通过按住鼠标右键来绘制矩形.使用左侧的按钮绘制NetCDF数据后,矩形的绘制会变得很慢.我使用ImportanceOfBeingErnest提供的示例尝试了一些划线操作,但是经过大量尝试,我仍然没有设法使其正常工作.

要使最低限度的工作示例正常工作,您将必须在plot_Data()函数下指定NetCDF文件的路径.我提供了NetCDF文件,可在此处下载:

To make the minimal working example work, you will have to specify the path to the NetCDF file under the plot_Data() function. I provided the NetCDF file which to download here:

下载NetCDF文件

如何在onselect函数中将self.square转换为self.canvas?

How can I blit the self.square to the self.canvas in the onselect function?

import netCDF4 as nc

import matplotlib
matplotlib.use('WXAgg')

from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas

import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.widgets

import time

import wx

class rightPanel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
        self.initiate_Matplotlib_Plot_Canvas()        
        self.add_Matplotlib_Widgets()

    def initiate_Matplotlib_Plot_Canvas(self):
        self.figure = Figure()
        self.axes = self.figure.add_subplot(111)
        self.colorbar = None
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, proportion=1, flag=wx.ALL | wx.GROW)
        self.SetSizer(self.sizer)
        self.Fit()
        self.canvas.draw()

    def add_Matplotlib_Widgets(self):

        self.rectangleSelector = matplotlib.widgets.RectangleSelector(self.axes, self.onselect,
                                                                      drawtype="box", useblit=True,
                                                                      button=[3], interactive=False
                                                                      )

    def onselect(self, eclick, erelease):
        tstart = time.time()
        x1, y1 = eclick.xdata, eclick.ydata
        x2, y2 = erelease.xdata, erelease.ydata

        height = y2-y1
        width = x2-x1


        self.square = matplotlib.patches.Rectangle((x1,y1), width, 
                                                   height, angle=0.0, edgecolor='red',
                                                   fill=False
                                                   #blit=True gives Unknown property blit
                                                   )


        self.axes.add_patch(self.square)
        self.canvas.draw()

        # =============================================================================
        #         self.background = self.canvas.copy_from_bbox(self.axes.bbox)
        #         
        #         
        #         self.canvas.restore_region(self.background)
        #        
        #         self.axes.draw_artist(self.square)
        #        
        #         self.canvas.blit(self.axes.bbox)
        # =============================================================================


        tend = time.time()
        print("Took " + str(tend-tstart) + " sec")

    def plot_Data(self):
        """This function gets called by the leftPanel onUpdatePlot. This updates
        the plot to the set variables from the widgets"""

        path = "C:\\Users\\TEST_DATA\\cesar_uvlidar_backscatter_la1_t30s_v1.0_20100501.nc"

        nc_data = self.NetCDF_READ(path)

        print("plotting......")
        vmin_value = 10**2
        vmax_value = 10**-5

        combo_value = nc_data['perp_beta']

        self.axes.clear()
        plot_object = self.axes.pcolormesh(combo_value.T, cmap='rainbow', 
                                           norm=colors.LogNorm(vmin=vmin_value, vmax=vmax_value))

        self.axes.set_title("Insert title here")

        if self.colorbar is None:
            self.colorbar = self.figure.colorbar(plot_object)
        else:
            self.colorbar.update_normal(plot_object)

        self.colorbar.update_normal(plot_object)

        print('canvas draw..............')
        self.canvas.draw()


        print("plotting succesfull")
###############################################################################
###############################################################################
        """BELOW HERE IS JUST DATA MANAGEMENT AND FRAME/PANEL INIT"""
###############################################################################
###############################################################################        
    def NetCDF_READ(self, path):
        in_nc = nc.Dataset(path)

        list_of_keys = in_nc.variables.keys()

        nc_data = {}    #Create an empty dictionary to store NetCDF variables

        for item in list_of_keys:
            variable_shape = in_nc.variables[item].shape
            variable_dimensions = len(variable_shape)
            if variable_dimensions > 1:
                nc_data[item] = in_nc.variables[item][...]      #Adding netCDF variables to dictonary

        return nc_data

class leftPanel(wx.Panel):

    def __init__(self, parent, mainPanel):
        wx.Panel.__init__(self, parent)

        button = wx.Button(self, -1, label="PRESS TO PLOT")
        button.Bind(wx.EVT_BUTTON, self.onButton)
        self.mainPanel = mainPanel

    def onButton(self, event):
        self.mainPanel.rightPanel.plot_Data()

class MainPanel(wx.Panel):

    def __init__(self, parent):
        """Initializing the mainPanel. This class is called by the frame."""

        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour('red')

        """Acquire the width and height of the monitor"""
        width, height = wx.GetDisplaySize()
        """Split mainpanel into two sections"""
        self.vSplitter = wx.SplitterWindow(self, size=(width,(height-100)))

        self.leftPanel = leftPanel(self.vSplitter, self) 
        self.rightPanel = rightPanel(self.vSplitter)

        self.vSplitter.SplitVertically(self.leftPanel, self.rightPanel,102)

class UV_Lidar(wx.Frame):
    """Uppermost class. This class contains everything and calls everything.
    It is the container around the mainClass, which on its turn is the container around
    the leftPanel class and the rightPanel class. This class generates the menubar, menu items,
    toolbar and toolbar items"""

    def __init__(self, parent, id):
        print("UV-lidar> Initializing GUI...")
        wx.Frame.__init__(self, parent, id, 'UV-lidar application')

        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.mainPanel = MainPanel(self)

    def OnCloseWindow(self, event):
        self.Destroy()

if __name__ == '__main__':
    app = wx.App()
    frame = UV_Lidar(parent=None, id=-1)
    frame.Show()
    print("UV-lidar> ")
    print("UV-lidar> Initializing GUI OK")
    app.MainLoop()

推荐答案

我自己找到了解决方案:

I have found the solution myself:

为了使matplotlib修补程序失效,必须首先将修补程序添加到轴中.然后在轴上绘制补丁,然后可以将补丁在画布上绘制.

In order to blit a matplotlib patch, you will have to first add the patch to the axes. Then draw the patch on the axes and then you can blit the patch to the canvas.

    square = matplotlib.patches.Rectangle((x1,y1), width, 
                                               height, angle=0.0, edgecolor='red',
                                               fill=False)

    self.axes.add_patch(square)
    self.axes.draw_artist(square)
    self.canvas.blit(self.axes.bbox)

如果您不想使用self.canvas.draw,但仍使用具有useblit = True的matplotlib小部件,则可以将图另存为背景图像:self.background = self.canvas.copy_from_bbox(self.axes.bbox),并在以后使用:self.canvas.restore_region(self.background)将其还原.这比画完所有东西要快得多!

If you do not want to use self.canvas.draw but still use matplotlib widgets which have useblit=True, you can save the plot as a background image: self.background = self.canvas.copy_from_bbox(self.axes.bbox) and restore it later by using: self.canvas.restore_region(self.background). This is a lot faster than drawing everything over!

当在useblit = True中使用matplotlib的RectangleSelector小部件时,它将创建另一个背景实例变量,这会干扰您自己的背景实例变量.要解决此问题,您将必须将RectangleSelector小部件的背景实例变量设置为等于您自己的背景实例变量.但是,仅应在RectangleSelector小部件不再处于活动状态之后才能执行此操作.否则,它将一些绘图动画保存到背景中. 因此,一旦RectangleSelector变为非活动状态,,您可以使用self.rectangleSelector.background = self.background

When using the matplotlib's RectangleSelector widget with useblit=True, it will create another background instance variable, which interferes with your own background instance variable. To fix this problem, you will have to set the background instance variable of the RectangleSelector widget to be equal to your own background instance variable. However, this should only be done after the RectangleSelector widget is no longer active. Otherwise it will save some of the drawing animation to the background. So once the RectangleSelector has become inactive, you can update its background using: self.rectangleSelector.background = self.background

下面给出了必须编辑的代码. wx.CallLater(0, lambda: self.tbd(square))用于使RectangleSelector小部件的背景实例变量仅在变为非活动状态时进行更新.

The code that had to be edited is given below. wx.CallLater(0, lambda: self.tbd(square)) is used so that the background instance variable of the RectangleSelector widget is updated only when it has become inactive.

def add_Matplotlib_Widgets(self):
    """Calling these instances creates another self.background in memory. Because the widget classes
    restores their self-made background after the widget closes it interferes with the restoring of 
    our leftPanel self.background. In order to compesate for this problem, all background instances 
    should be equal to eachother. They are made equal in the update_All_Background_Instances(self) 
    function"""


    """Creating a widget that serves as the selector to draw a square on the plot"""
    self.rectangleSelector = matplotlib.widgets.RectangleSelector(self.axes, self.onselect,
                                                                  drawtype="box", useblit=True,
                                                                  button=[3], interactive=False
                                                              )

def onselect(self, eclick, erelease):
    self.tstart = time.time()
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata

    height = y2-y1
    width = x2-x1



    square = matplotlib.patches.Rectangle((x1,y1), width, 
                                               height, angle=0.0, edgecolor='red',
                                               fill=False
                                               #blit=True gives Unknown property blit
                                               )

    """In order to keep the right background and not save any rectangle drawing animations 
    on the background, the RectangleSelector widget has to be closed first before saving 
    or restoring the background"""
    wx.CallLater(0, lambda: self.tbd(square))


def tbd(self, square):

    """leftPanel background is restored"""
    self.canvas.restore_region(self.background)

    self.axes.add_patch(square)
    self.axes.draw_artist(square)


    self.canvas.blit(self.axes.bbox)

    """leftPanel background is updated"""
    self.background = self.canvas.copy_from_bbox(self.axes.bbox)


    """Setting all backgrounds equal to the leftPanel self.background"""
    self.update_All_Background_Instances()

    print('Took '+ str(time.time()-self.tstart) + ' s')

def update_All_Background_Instances(self):
    """This function sets all of the background instance variables equal 
    to the lefPanel self.background instance variable"""
    self.rectangleSelector.background = self.background        

这篇关于如何在wxPython中使用matplotlib blitting将matplot.patches添加到matplotlib图中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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