如何使用 matplotlib blitting 将 matplot.patches 添加到 wxPython 中的 matplotlib 图?

2023-12-22

我正在使用 matplotlib 库制作一个绘图并在我的 wxPython GUI 中显示它。我正在绘制来自激光雷达仪器的大量数据点。问题是,我想在该图中绘制矩形来指示有趣的区域。但是,当我在与绘图相同的轴上绘制一个矩形时,整个绘图将被重新绘制,这需要花费大量时间。这是因为 self.canvas.draw() 是一个重新绘制所有内容的函数。

代码在 GUI 中显示如下:

GUI 的打印屏幕 https://i.stack.imgur.com/uvzPo.png

这是该问题的一个最小的工作示例。按住鼠标右键可以绘制矩形。一旦使用左侧的按钮绘制 NetCDF 数据,矩形的绘制就会变得非常慢。我使用 ImportanceOfBeingErnest 提供的示例尝试了一些 blitting 的操作,但经过多次尝试,我仍然无法让它工作。

为了使最小工作示例工作,您必须在plot_Data()函数下指定NetCDF文件的路径。我提供了 NetCDF 文件,可以在此处下载:

下载NetCDF文件 https://wetransfer.com/downloads/9b81c54c7eadfccec369e61da99ca1e120180308090343/c1580885a301f27b841c5f690d25662b20180308090343/922a84

如何在 onselect 函数中将 self.square 传输到 self.canvas ?

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

我自己找到了解决方案:

为了blit matplotlib补丁,您必须首先将补丁添加到轴上。然后在轴上绘制补丁,然后您可以将补丁传输到画布上。

    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)。这比把所有东西都画完要快得多!

当使用 matplotlib 的 RectangleSelector 小部件并使用 useblit=True 时,它​​将创建另一个背景实例变量,这会干扰您自己的背景实例变量。要解决此问题,您必须将 RectangleSelector 小部件的背景实例变量设置为等于您自己的背景实例变量。但是,这只能在 RectangleSelector 小部件不再处于活动状态后完成。否则它会将一些绘制动画保存到背景中。因此,一旦 RectangleSelector 变为非活动状态,您可以使用以下方法更新其背景:self.rectangleSelector.background = self.background

下面给出了必须编辑的代码。wx.CallLater(0, lambda: self.tbd(square))用于更新 RectangleSelector 小部件的背景实例变量只有当它已变得不活跃。

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        
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 matplotlib blitting 将 matplot.patches 添加到 wxPython 中的 matplotlib 图? 的相关文章

  • 在 pandas 中获取组名称的有效方法

    我有一个包含大约 300 000 行的 csv 文件 我将其设置为按特定列分组 每个组大约有 140 名成员 总共 2138 个组 我正在尝试生成组名称的 numpy 数组 到目前为止 我已经使用 for 循环来生成名称 但处理所有内容都需
  • 相当于“setup.py”中的“--find-links”

    相当于什么 find links f标记为pip in setup py I know dependency links存在 但这需要指向一个特定的文件 我想要类似的东西 f它可以指向一个链接列表 可以根据版本和操作系统从中选择包 In a
  • 旋转 3d 图形的 z 轴

    How do I rotate the z axis of a matplotlib figure so that it appears as the horizontal axis I have created the following
  • 如何使用 rxpy/rxjs 延迟事件发射?

    我有两个事件流 一个来自电感环路 另一个来自网络摄像机 汽车将驶过环路 然后撞上相机 如果事件彼此相差在 N 毫秒内 汽车总是会首先进入循环 我想将它们组合起来 但我也希望每个流中不匹配的事件 硬件可能会失败 全部合并到单个流中 像这样的事
  • 如何将嵌套的Python字典转换为简单的命名空间?

    假设我有一个深度为 N 的嵌套字典 如何将每个内部嵌套字典转换为简单的命名空间 example input key0a test key0b key1a key2a keyNx key2b test key1b test example o
  • 在Python 3中将二进制字符串转换为字节数组

    尽管有很多相关的问题 但我找不到任何符合我的问题的问题 我想更改二进制字符串 例如 0110100001101001 转换成字节数组 同一个例子 b hi 我试过这个 bytes int i for i in 011010000110100
  • Python 3 在除两个大数时给出错误的输出?

    a 15511210043330985984000000 25 b 479001600 12 c 6227020800 13 关于划分ans int a b c or ans int a b c we get ans等于5200299代替5
  • 如何使用 matplotlib 将 3d 数据单位转换为显示单位?

    这可能有点疯狂 但我正在尝试使用 matplotlib v1 1 0 创建 3d 散点图的可点击图像图 我已经阅读了如何对二维图进行操作 参见这个博客 http hackmap blogspot com 2008 06 pylab matp
  • “char”/“character”类型的类型提示

    char 或 character 没有内置的原始类型 因此显然必须使用长度为 1 的字符串 但是为了暗示这一点并暗示它应该被视为一个字符 如何通过类型提示来实现这一点 grade chr A 一种方法可能是使用内置的 chr 函数来表示这一
  • 为什么 python 对于共享锁抛出“multiprocessing.managers.RemoteError”?

    我正在使用 python 3 6 7 和 Ubuntu 18 04 运行以下脚本后 每个进程都有自己的共享锁 from multiprocessing import Process Manager def foo l1 with l1 pr
  • 无法截取宽度为 0 的屏幕截图

    我正在尝试截取 Bootstrap 模态内元素的屏幕截图 经过一番努力 我终于想出了这段代码 driver get https enlinea sunedu gob pe driver find element by xpath div c
  • 在 matplotlib 中使用一组标量值对球体表面着色

    我对 matplotlib 相当陌生 这也是我的第一个问题 我试图代表脑电图记录的头皮表面电位 到目前为止 我已经有了一个球体投影的二维图形 它是使用轮廓生成的 并且几乎可以归结为普通的热图 有什么方法可以在半个球体上完成此操作吗 即生成一
  • Matplotlib pyplot - 刻度控制和显示日期

    My matplotlib pyplot有太多xticks 目前显示 15 年期间的每年和每月 例如 2001 01 但我只希望 x 轴显示年份 例如 2001 年 输出将是一个折线图 其中 x 轴显示日期 y 轴显示销售和租金价格 Def
  • python 3.4 计算 .txt 文件中的出现次数

    我正在为我正在上课的课程编写一个 简单 的小程序 这应该询问我要搜索哪个团队 然后返回它出现在 txt 文件列表中的次数 它像它应该的那样请求输入 并且看起来运行得很好 它现在已经运行了一个小时 我完全没有收到任何错误 它似乎陷入了循环 预
  • 如何跨多个文本文件查找字典中键的频率?

    我应该计算文档 individual articles 中所有文件中字典 d 的所有键值的频率 这里 文档 individual articles 大约有20000个txt文件 文件名为1 2 3 4 例如 假设 d Britain 5 7
  • 如何使用 Box API 和 Python 下载文件

    目前 我的代码的上传部分正在运行 我该如何将其转换为从 box 文件夹下载相应文件的程序 这是上传程序 import requests import json the user acces token access token UfUNeH
  • 无法包含外部 pandas 文档 Pycharm v--2018.1.2

    我无法包含外部 pandas 文档Pycharm v 2018 1 2 例如 numpy gt http docs scipy org doc numpy reference generated module name element na
  • ipython3 笔记本垂直边距/标记线为 80 个字符

    如何使 ipython3 笔记本在 80 个字符处显示垂直边距 标记线 如何获取 ipython3 笔记本中的 i bar 位置 例如第 30 行第 56 个字符 这些功能有助于编写符合 PEP8 的代码 Spyder 中提供了这些功能 更
  • 在wxpython中使用wx.TextCtrl并在按钮单击后显示数据的简单示例 - wx新手

    我正在学习 python 并尝试使用 wxpython 进行 UI 开发 也没有 UI exp 我已经能够创建一个带有面板 按钮和文本输入框的框架 我希望能够在文本框中输入文本 并让程序在单击按钮后对输入框中的文本执行操作 我可以获得一些关
  • 使用 python 将文本发送到带有逗号分隔符的列

    如何使用分隔符 在 Excel 中将一列分成两列 并使用 python 命名标题 这是我的代码 import openpyxl w openpyxl load workbook DDdata xlsx active w active a a

随机推荐