【问题标题】:Automated Updating Matplotlib Plot in PySimpleGUI Window在 PySimpleGUI 窗口中自动更新 Matplotlib 图
【发布时间】:2020-11-19 04:25:52
【问题描述】:

我正在创建一个 GUI,以允许用户查看光谱仪的“实时视图”,其中数据从光谱仪获取并绘制在 Matplotlib 中以显示在 GUI 窗口中。 GUI 还有一些其他按钮,允许用户使用其他功能(不相关但只是背景)。

我已经让实时视图在 matplotlib 中使用 while 循环并清除数据以重新绘制:

while True:
    data = ccs.take_data(num_avg=3) # spectrometer function
    norm = (data[0]-dark[0])/(light[0]-dark[0]) # some calcs.
    plt.plot(data[1],norm)
    plt.axis([400,740,0,1.1])  
    plt.grid(color='w', linestyle='--')       
    plt.xlabel('Wavelength [nm]')
    plt.ylabel('Normalized Intesity')          
    plt.pause(0.1)
    plt.cla()

下一步是在 PySimpleGUI 中显示此图。比expexted更难...如果用户按下“更新”按钮,我可以使用 PySimpleGUI 中的一些演示代码来显示和更新单个图形:

from instrumental.drivers.spectrometers import thorlabs_ccs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt


def fig_maker(ccs, dark, sub):
    plt.clf()
    plt.close()
    data = ccs.take_data(num_avg=3)
    norm = (data[0]-dark[0])/(sub[0]-dark[0])
    plt.plot(data[1],norm,c='r')
    plt.axis([400,750,0,1.1])  
    plt.grid(color='w', linestyle='--')       
    plt.xlabel('Wavelength [nm]')
    plt.ylabel('Normalized Intesity')  

return plt.gcf() 


def draw_figure(canvas, figure, loc=(0, 0)):
    figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
    figure_canvas_agg.draw()
    figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
    return figure_canvas_agg


def delete_fig_agg(fig_agg):
    fig_agg.get_tk_widget().forget()
    plt.close('all')


if __name__ == '__main__':
    
    ... some code ...

    # define the window layout
    layout = [[sg.Button('update')],
              [sg.Text('Plot test', font='Any 18')],             
              [sg.Canvas(size=(500,500), key='canvas')] ]

    # create the form and show it without the plot
    window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
                       layout, finalize=True)

    fig_agg = None
    while True:
        event, values = window.read()
        if event is None:  # if user closes window
            break         
        if event == "update":
            if fig_agg is not None:
                delete_fig_agg(fig_agg)
            fig = fig_maker(ccs,dark,sub)
            fig_agg = draw_figure(window['canvas'].TKCanvas, fig) 
    window.close()            

现在是有趣的部分(我似乎无法让它工作)。我希望情节总是像我使用 matplotlib 那样更新,这样用户就不必按“更新”。使用PySimpleGUI long_task threaded example 是我的程序开始失败的地方。除了在 Python 关闭脚本之前打印到 Debug I/O 声明 *** Faking Timeout *** 之外,我实际上没有抛出任何错误。

我什至只是尝试做一个 10 次迭代的 for 循环,而不是连续的 while 循环:

from instrumental.drivers.spectrometers import thorlabs_ccs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt


def long_function_thread(window, ccs, dark, sub):
    for i in range(10):
        fig = fig_maker(ccs, dark, sub)
        fig_agg = draw_figure(window['canvas'].TKCanvas, fig) 
        window.write_event_value('-THREAD PROGRESS-', i)
        time.sleep(1)
        delete_fig_agg(fig_agg)
        time.sleep(0.1)

    window.write_event_value('-THREAD DONE-', '')


def long_function(window, ccs, dark, sub):
    print('In long_function')
    threading.Thread(target=long_function_thread, args=(window, ccs, dark, sub), daemon=True).start()


def fig_maker(ccs, dark, sub):
    plt.clf()
    plt.close()
    data = ccs.take_data(num_avg=3)
    norm = (data[0]-dark[0])/(sub[0]-dark[0])
    plt.plot(data[1],norm,c='r')
    plt.axis([400,750,0,1.1])  
    plt.grid(color='w', linestyle='--')       
    plt.xlabel('Wavelength [nm]')
    plt.ylabel('Normalized Intesity')  
    
    return plt.gcf() 


def draw_figure(canvas, figure, loc=(0, 0)):
    figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
    figure_canvas_agg.draw()
    figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
    return figure_canvas_agg


def delete_fig_agg(fig_agg):
    fig_agg.get_tk_widget().forget()
    plt.close('all')


if __name__ == '__main__':
    
     ... some code ...

    # define the window layout
    layout = [[sg.Button('Go')],
              [sg.Text('Plot test', font='Any 18')],            
              [sg.Canvas(size=(500,500), key='canvas')] ]

    # create the form and show it without the plot
    window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
                       layout, finalize=True)

    fig_agg = None
    while True:
        event, values = window.read()
        if event is None or event == 'Exit':
            break
        if event == 'Go':
            print('Calling plotter')
            long_function(window, ccs, dark, sub)
            print('Long function has returned from starting')            
        elif event == '-THREAD DONE-':
            print('Your long operation completed')

window.close()

关于长描述和代码转储的应用,但我认为这是最简单的解释方式。任何有关此问题的帮助或链接将不胜感激。

如果有人想尝试运行我的脚本,这应该只是生成一个随机图

def random_fig_maker():
   plt.scatter(np.random.rand(1,10),np.random.rand(1,10))
   return plt.gcf()

【问题讨论】:

    标签: python-3.x python-2.7 matplotlib


    【解决方案1】:

    您需要使用两个额外的 PySimpleGUI 功能:window.Refresh()window.write_event_value()。当您删除 figg_agg 并且新绘图准备就绪时,请致电 window.Refresh()。这将重绘窗口,但也引入了一个问题:主事件 (while) 循环将永远运行。为了解决这个问题,您还需要将window.write_event_value('-THREAD-', 'some message.') 添加到从事件循环中调用的函数之一。这将作为事件循环继续运行的外部触发器,但这也会使窗口保持响应,因此您可以更改一些其他窗口元素(这里我使用了单选开关)来停止循环。

    对于奖励积分,您还可以将“触发功能”作为单独的线程运行。然后,该函数中的time.sleep() 不会影响 GUI 响应能力。因此,我会使用一些只返回一些列表或元组的数据收集函数作为重新启动循环的触发器。在这种情况下,matplotlib 对从外部线程调用不满意,所以我只是在事件循环中添加了延迟以保持绘图可见。

    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import PySimpleGUI as sg
    import matplotlib, time, threading
    matplotlib.use('TkAgg')
    import matplotlib.pyplot as plt
    import numpy as np
    
    
    def fig_maker(window): # this should be called as a thread, then time.sleep() here would not freeze the GUI
        plt.scatter(np.random.rand(1,10),np.random.rand(1,10))
        window.write_event_value('-THREAD-', 'done.')
        time.sleep(1)
        return plt.gcf()
    
    
    def draw_figure(canvas, figure, loc=(0, 0)):
        figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
        figure_canvas_agg.draw()
        figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
        return figure_canvas_agg
    
    
    def delete_fig_agg(fig_agg):
        fig_agg.get_tk_widget().forget()
        plt.close('all')
    
    
    if __name__ == '__main__':
        # define the window layout
        layout = [[sg.Button('update'), sg.Button('Stop', key="-STOP-"), sg.Button('Exit', key="-EXIT-")],
                  [sg.Radio('Keep looping', "RADIO1", default=True, size=(12,3),key="-LOOP-"),sg.Radio('Stop looping', "RADIO1", size=(12,3), key='-NOLOOP-')],
                  [sg.Text('Plot test', font='Any 18')],             
                  [sg.Canvas(size=(500,500), key='canvas')]]
    
        # create the form and show it without the plot
        window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
                           layout, finalize=True)
    
        fig_agg = None
        while True:
            event, values = window.read()
            if event is None:  # if user closes window
                break
            
            if event == "update":
                if fig_agg is not None:
                        delete_fig_agg(fig_agg)
                fig = fig_maker(window)
                fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
    
            if event == "-THREAD-":
                print('Acquisition: ', values[event])
                time.sleep(1)
                if values['-LOOP-'] == True:
                    if fig_agg is not None:
                        delete_fig_agg(fig_agg)
                    fig = fig_maker(window)
                    fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
                    window.Refresh()
            
            if event == "-STOP-":
                window['-NOLOOP-'].update(True)
            
            if event == "-EXIT-":
                break
                
        
        window.close()            
    

    【讨论】:

      【解决方案2】:

      它没有完全连接,但我遇到了类似的问题。这有帮助吗....

      import PySimpleGUI as sg
      import matplotlib.pyplot as plt
      from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
      import numpy as np
      
      class updateable_matplotlib_plot():
          def __init__(self, canvas) -> None:
              self.fig_agg = None
              self.figure = None
              self.canvas = canvas
      
          def plot(self, data):
              self.data = data
              self.figure_controller()
              self.figure_drawer()
      
          #put all of your normal matplotlib stuff in here
          def figure_controller(self):
              #first run....
              if self.figure is None:
                  self.figure = plt.figure()
                  self.axes = self.figure.add_subplot(111)
                  self.line, = self.axes.plot(self.data)
                  self.axes.set_title("Example of a Matplotlib plot updating in PySimpleGUI")
              #all other runs
              else:            
                  self.line.set_ydata(self.data)#update data            
                  self.axes.relim() #scale the y scale
                  self.axes.autoscale_view() #scale the y scale
      
          #finally draw the figure on a canvas
          def figure_drawer(self):
              if self.fig_agg is not None: self.fig_agg.get_tk_widget().forget()
              self.fig_agg = FigureCanvasTkAgg(self.figure, self.canvas.TKCanvas)
              self.fig_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
              self.fig_agg.draw()
      
      def getGUI():
          # All the stuff inside your window.
          layout = [  [sg.Canvas(size=(500,500), key='canvas')],
                      [sg.Button('Update', key='update'), sg.Button('Close')] ]
      
          # Create the Window
          window = sg.Window('Updating a plot example....', layout)
          return window
      
      
      if __name__ == '__main__':
          window = getGUI()
          spectraPlot = updateable_matplotlib_plot(window['canvas']) #what canvas are you plotting it on
          window.finalize() #show the window
          spectraPlot.plot(np.zeros(1024)) # plot an empty plot    
          while True:
              event, values = window.read()
              if event == "update":
                   some_spectrum = np.random.random(1024) # data to be plotted
                   spectraPlot.plot(some_spectrum) #plot the data           
              if event == sg.WIN_CLOSED or event == 'Close': break # if user closes window or clicks cancel
      
          window.close()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-25
        • 1970-01-01
        • 1970-01-01
        • 2020-08-31
        • 2021-11-01
        • 2022-11-11
        • 2021-06-13
        相关资源
        最近更新 更多