大型图像堆栈处理后 matplotlib wxPython 后端崩溃

matplotlib wxPython backend crashing after large image stack processing

我在处理大型图像堆栈时遇到了 wxAssertionError(如下所示)。让我用一个例子来解释。

我用 wxPython 制作了一个界面,只有一个面板、一个按钮和一个仪表条。

一旦用户点击按钮,代码就会读取一堆 8 位二进制图像 (1500 x 100 x 50),在面板上显示第一张图像并开始处理堆栈。可以下载示例堆栈here,点击'open->download'并将其放入与下面代码相同的文件夹中,如果您无法访问堆栈,请在评论中告诉我)。处理步骤是一个循环,其中堆栈中的每个图像都被标记并绘制在图形中(而不是在面板中)。在每次迭代中保存并关闭此图。代码如下:

# Label and save big image stack
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
class CanvasPanelA1(wx.Panel):
    def __init__(self, parent, ID):
        wx.Panel.__init__(self, parent, ID, style=wx.SUNKEN_BORDER)

        self.figure = Figure()
        self.figure.set_facecolor("BLACK")
        self.canvas = FigureCanvas(self, -1, self.figure)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
        self.SetSizer(self.sizer)
        self.Fit()

    def OnPlot(self, event):
        self.frame = frame
        from numpy import amin, amax
        self.vmin = amin(frame.video)
        self.vmax = amax(frame.video)
        self.nframes = frame.video.shape[0]
        self.axes = self.figure.add_subplot(111)
        self.axes.imshow(frame.video[0],cmap='gray',vmin=self.vmin,vmax=self.vmax)
        self.axes.axis('off')
        self.canvas.draw()
        self.SetSizer(self.sizer)
        frame.Layout()

class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        self.panel = CanvasPanelA1(self, wx.ID_ANY)
        self.panel.SetMinSize((200,200))
        self.button_RUN = wx.Button(self, label="Run")
        self.gauge = wx.Gauge(self, range = 100, size = (250, 25), style = wx.GA_HORIZONTAL)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.panel, 3, wx.EXPAND)

        mainSizer.Add(self.button_RUN,0,flag=wx.CENTER|wx.ALL)
        mainSizer.Add(self.gauge,0,flag=wx.CENTER|wx.EXPAND)
        self.button_RUN.Bind(wx.EVT_BUTTON, self.OnRun)
        self.SetSizer(mainSizer)
        self.SetAutoLayout(True)
        self.Layout()
    def OnRun(self,event):
        from skimage.color import label2rgb 
        from skimage.measure import label
        import matplotlib as mpl
        import matplotlib.pyplot as plt
        from skimage import io
        from skimage.util import img_as_ubyte
        import os
        #read video
        video_name = './video1.tif'
        self.video = io.imread(video_name, as_gray=False, plugin='tifffile')
        self.video = img_as_ubyte(self.video)
        self.nframes = self.video.shape[0]
        self.gauge.SetRange(self.nframes-1)

        #show one image on panel
        self.panel.OnPlot(wx.EVT_DISPLAY_CHANGED)
        self.panel.Refresh()
        # Create Output Directory
        dirname = 'Outputs'
        try:
            os.mkdir(os.path.join('.',dirname))
            print("Directory " , dirname ,  " Created ") 
        except FileExistsError:
            print("Directory " , dirname ,  " already exists")

        plt.ioff()
        for fr in range(self.nframes):
            print('frame=',fr)
            label_image = label(self.video[fr], background=0) 
            image_label_overlay = label2rgb(label_image,bg_label=0)
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.imshow(image_label_overlay)
            plt.axis('off')
            imname = 'imgout'+str(fr).zfill(len(str(self.nframes)))+'.tif'
            output_path = os.path.join('.',dirname,imname)
            plt.savefig(output_path,bbox_inches='tight',facecolor='black',edgecolor='none')
            plt.close(fig)
            self.gauge.SetValue(fr)
        plt.ion()

app = wx.App()
frame = MyFrame(None, -1, "Label videos")
frame.Show()
app.MainLoop()  

到目前为止,此代码适用于较小的堆栈,但一旦我提供大于 ~1250 帧的图像堆栈,它就会出现以下错误:

File "C:\Users\...\lib\site-packages\matplotlib\backends\backend_wx.py", line 1413, in _init_toolbar    
self.Realize()    
**wxAssertionError**: C++ assertion "Assert failure" failed at ..\..\src\msw\toolbar.cpp(938) in wxToolBar::Realize(): Could not add bitmap to toolbar

因为这个错误似乎与工具栏有关而且我不使用它,所以我尝试使用 mpl.rcParams['toolbar'] = 'None' 禁用它,放在循环之前,但随后我得到另一个 AssertionError:

File "C:\Users\...\lib\site-packages\matplotlib\backends\backend_wx.py", line 1585, in __init__
self.SetFieldsCount(2)
**wxAssertionError**: C++ assertion "m_hdc" failed at ..\..\src\msw\textmeasure.cpp(64) in wxTextMeasure::BeginMeasuring(): Must not be used with non-native wxDCs

我也尝试过使用 wx.CallAfter(self.gauge.SetValue, fr) 来避免在绘制和保存图像时更新主界面,但效果不佳。

有人知道是什么原因造成的吗?

以下是我的软件包版本:
康达 4.4.1
python3.6.8
wxPython 4.0.6
间谍 3.3.6
scikit-image 0.15.0(编辑:也尝试使用最新版本 = 0.16.2,仍然没有成功)
matplotlib 3.1.1
麻木 1.16.4

我发布这个问题已经 4 个月了,显然没有人知道答案。

我想说在处理大量数据时,matplotlib wxpython 后端存在一些错误。

好的,我想我已经根据 this answer 解决了类似的问题。 似乎不应该在循环内创建新图形,即使在循环内关闭该图形也是如此。无论关闭每个新图形,我的 RAM 在每次循环迭代后都会增加。

解决方案:在循环之前创建图形实例。然后,在循环内绘图(如果需要,请保存),而不是关闭图形,只需使用 plt.clf() 清除它。只关闭循环后的图形,如下例:

fig, ax = plt.subplots(figsize=(10, 6)) #create figure and axes
for fr in range(self.nframes):
    print('frame=',fr)
    label_image = label(self.video[fr], background=0) #some processing
    image_label_overlay = label2rgb(label_image,bg_label=0)   
    ax.imshow(image_label_overlay) #plot the image
    plt.axis('off')
    imname = 'imgout'+str(fr).zfill(len(str(self.nframes)))+'.tif'  
    output_path = os.path.join('.',dirname,imname)
    plt.savefig(output_path,bbox_inches='tight',facecolor='black',edgecolor='none') #save the figure
    self.gauge.SetValue(fr) #update gauge bar
    plt.clf() #clear current figure
plt.close(fig) #closes figure