在kivy中使用matplotlib加速绘图

qyzbxkaa  于 2023-05-18  发布在  其他
关注(0)|答案(1)|浏览(156)

我试图在matplotlib中真实的优化绘图。我尝试通过plt.plotplt.figure实现。在plt.plot的情况下,由于返回FigureCanvasKivyAgg(plt.gcf()),图形被更新,这种方法处理得很糟糕,在100点之后,fps从30下降到10。使用plt.figure(使用plt.canvas.draw),情况稍微好一些,初始fps大约是70…80,大约500…600点后下降到15。

import matplotlib
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from garden_matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.label import Label

import matplotlib.animation as animation

#-------------------------------------------------------------------------#

n = 0
x = 0
y = 0
s=0
lst = [[x],
       [y]]
plt_limit_x=200
plt_limit_y=1000
fig=plt.figure()
#--------------------------------MENUSCREEN------------------_-------------#
class MenuScreen(Screen):
    pass
    box = ObjectProperty(None)
    
#------------------------------------PLOT----------------------------------#
class MyFigure(FigureCanvasKivyAgg): #Plot
    def __init__(self, **kwargs):
        global fig
        global x
        global y
        y=x+x
        x+=1
        lst[0].append(x)
        lst[1].append(y)
        self.ax=fig.add_subplot(111)
        self.line = self.ax.plot(lst[0], lst[1])
        super(MyFigure, self).__init__(fig, **kwargs)
        fig.canvas.draw()
        
    def start_updating(self):
        Clock.schedule_interval(self.Update, 1/30)

    def stop_updating(self):
        Clock.unschedule(self.Update)

    def Update(self, *args):
        global fig, x, y, lst, n
        self.line.clear()
        n=n+1
        y=x+x
        x+=1
        lst[0].append(x)
        lst[1].append(y)
        self.line = self.ax.plot(lst[0], lst[1], color='black')
        
        return fig.canvas.draw()  

#------------------------------------MAINAPP----------------------------------#
class MainApp(MDApp):
     
    def build(self):
        sm = ScreenManager()
        sm.add_widget(MenuScreen(name='menu'))
        return sm

    def on_start(self):
        self.fps_monitor_start()   

#---------------------------------END-------------------------------------#
if __name__=='__main__':
    app = MainApp()
    app.run()

我决定尝试实现matplotlib.animation,遵循我写的plt.show()派生的例子,一切都很正常。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x=0
y=0
data = ([x],[y])
def update_line(*args, **kwargs):
    global x
    global y
    
    x=x+1
    y=y+2
    data[0].append(x)
    data[1].append(y)
    l.set_data(data)
    #print (l)
    return l,

fig1 = plt.figure()

    
l, = plt.plot([], [], 'r-')
plt.xlim(0, 10000)
plt.ylim(0, 10000)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, 10000, fargs=(data, l),
    interval=1, blit=True)
plt.show()

但是当我试图将它翻译成kivy时,图不想被更新,有一个假设问题出在init方法,但我不知道没有它如何实现程序。

import matplotlib
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from garden_matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.label import Label

import matplotlib.animation as animation

#-------------------------------------------------------------------------#

n = 0
x = 0
y = 0
s=0
lst = [[x],
       [y]]
fig=plt.figure()
data = ([x],[y])
l, = plt.plot([], [], 'r-')

#--------------------------------MENUSCREEN------------------_-------------#

class MenuScreen(Screen):
    pass
    box = ObjectProperty(None)
    

#------------------------------------PLOT----------------------------------#

class MyFigure(FigureCanvasKivyAgg):
    def __init__(self, **kwargs):
        global l
        plt.xlim(0, 10000)
        plt.ylim(0, 10000)
        plt.xlabel('x')
        plt.title('test')
        super(MyFigure, self).__init__(fig, **kwargs)
        
    def start_updating(self):
        Clock.schedule_interval(self.Update, 1/30)

    def stop_updating(self):
        Clock.unschedule(self.Update)

    def Update(self, *args, **kwargs):
        global x, y, l #data
        x=x+1
        y=y+2
        data[0].append(x)
        data[1].append(y)
        #print (data)
        l.set_data(data)
        print (l)
        return l,
    line_ani = animation.FuncAnimation(fig,
                                       Update,
                                       10000,
                                       fargs=(data, l),
                                       interval=1,
                                       blit=True)     

    fig.canvas.draw()

#------------------------------------MAINAPP----------------------------------#
class MainApp(MDApp):
     
    def build(self):
        sm = ScreenManager()
        sm.add_widget(MenuScreen(name='menu'))
        return sm

    def on_start(self):
        self.fps_monitor_start()   
#---------------------------------END-------------------------------------#
if __name__=='__main__':
    app = MainApp()
    app.run()

请帮助优化程序。可以使用fig.canvas.draw()来实现这一点。但在理想的情况下,人们希望使用matplotlib.animation。
UPD,我完全忘了,这是主要的.kv文件代码

<Manager>:
    transition: SlideTransition()
    MenuScreen:
        name: 'MenuScreen'

<MenuScreen>:
    box: box
    BoxLayout:
        orientation: 'horizontal'
        GridLayout:
            cols: 1
            size_hint: .2, 1
            Button:
                text: 'Start measurement'
                on_press: myfig.start_updating()
            Button:
                text: 'Stop measurement'
                on_press: myfig.stop_updating()
            Button:
                text: 'Quit'
                on_press: app.root_window.close ()
        BoxLayout:
            size_hint: .8, 1
            id:box
            MyFigure:
                id:myfig

UPD.在使用Tkinter Embedding a matplotlib animation into a tkinter frame时发现类似问题,转换代码后添加init_plot函数,

import matplotlib
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from garden_matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.label import Label

import matplotlib.animation as animation

#-------------------------------------------------------------------------#

n = 0
x = 0
y = 0
s=0
lst = [[x],
       [y]]
fig=plt.figure()
data = ([x],[y])
l, = plt.plot([], [], 'r-')

#--------------------------------MENUSCREEN------------------_-------------#

class MenuScreen(Screen):
    pass
    box = ObjectProperty(None)
    

#------------------------------------PLOT----------------------------------#

class MyFigure(FigureCanvasKivyAgg):
    def __init__(self, **kwargs):
        super(MyFigure, self).__init__(fig, **kwargs)
        self.init_plot()
        
    def start_updating(self):
        Clock.schedule_interval(self.Update, 1/30)

    def stop_updating(self):
        Clock.unschedule(self.Update)
   

#fig.canvas.draw()
    

    def Update(self, *args, **kwargs):
        global x, y, l #data
        x=x+1
        y=y+2
        data[0].append(x)
        data[1].append(y)
        #print (data)
        l.set_data(data)
        print (l)
        return l,

    def init_plot(self):
        global fig
        global l
        plt.xlim(0, 10000)
        plt.ylim(0, 10000)
        plt.xlabel('x')
        plt.title('test')
        line_ani = animation.FuncAnimation(fig,
                                        self.Update,
                                        10000,
                                        fargs=(data, l),
                                        interval=1,
                                        blit=True)  
#------------------------------------MAINAPP----------------------------------#
class MainApp(MDApp):
     
    def build(self):
        sm = ScreenManager()
        sm.add_widget(MenuScreen(name='menu'))
        return sm

    def on_start(self):
        self.fps_monitor_start()   
#---------------------------------END-------------------------------------#
if __name__=='__main__':
    app = MainApp()
    app.run()

我得到以下错误:

File "C:\Python\Stackoverflow question\13.05.2023\garden_matplotlib\backend_kivy.py", line 1078, in _timer_set_interval
     if self._timer is not None:
 AttributeError: 'TimerKivy' object has no attribute '_timer'. Did you mean: '_on_timer'?

如果我把line_ani放到build函数中,我会得到类似的错误。
我试图在简单的Kivy项目中使用matplotlib.animation,但也得到了同样的错误

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.clock import Clock
import matplotlib.animation as animation

n = 0
x = 0
y = 0
s=0
lst = [[x],
       [y]]
fig=plt.figure()
data = ([x],[y])
l, = plt.plot([], [], 'r-')
canvas = FigureCanvasKivyAgg(fig)

class MainApp(App):

    def init_plot(self):
        global fig, l
        plt.xlim(0, 10000)
        plt.ylim(0, 10000)
        plt.xlabel('x')
        plt.title('test')

    def on_press_button(self, instance):
        Clock.schedule_interval(self.Update, 1/30)

    def Update(self, *args, **kwargs):
        global x, y, l #data
        x=x+1
        y=y+2
        data[0].append(x)
        data[1].append(y)
        #print (data)
        l.set_data(data)
        print (l)
        return l,
    
    def build(self):
        main_layout = BoxLayout(orientation='horizontal')
        grid_layout=GridLayout(cols=1,
                               row_force_default=True,
                               row_default_height=100,
                               size_hint=(.2,1)
                               )
        main_layout.add_widget(grid_layout)
        grid_layout.add_widget(Label(text='Hello from Kivy'))

        button1=grid_layout.add_widget(Button(text='Hello 1', on_press=self.on_press_button))
        global canvas
        main_layout.add_widget(canvas)

        print ('build called')
        return main_layout
    line_ani = animation.FuncAnimation(fig,
                                        Update,
                                        10000,
                                        fargs=(data, l),
                                        interval=1,
                                        blit=True)

if __name__ == '__main__':
    app = MainApp()
    app.run()
klsxnrf1

klsxnrf11#

问题解决了,重点是,每次更新,都会建立新的图表。我用print (self.ax.get_lines())弄明白了。实际上这就是解决办法。我们没有用self.line = self.ax.plot(lst[0], lst[1], color='black')在循环中构建新的图(我的意思是schedule_interval),而是设置__init__self.line = self.ax.plot (lst[0], lst[1], color='black')。请注意,line后面有逗号。这对于返回Line2D对象列表是必需的。然后,我们在循环中绘制图,如第一个选项:结果,获得了稳定的60 fps。
下面的bug问题仍然是开放的。对我来说,看起来没有办法用matplotlib.animationKivy中工作。

File "C:\Python\Stackoverflow question\13.05.2023\garden_matplotlib\backend_kivy.py", line 1078, in _timer_set_interval
     if self._timer is not None:
 AttributeError: 'TimerKivy' object has no attribute '_timer'. Did you mean: '_on_timer'?

UPD。绘制方法fig.canvas.draw_idle()在我的例子中工作得更快,稳定70 FPS。
UPD我们最后知道什么

  • 我们不能在循环中使用clear()cla()clf()方法,因为它们非常慢。
  • plt.plotFigureCanvasKivyAgg(plt.gcf())是慢的。
  • 我们需要使用FigureCanvasKivyAgg(plt.fig())
  • 如果我们使用draw()方法,它每次都会返回整个图,每次都会在每个循环中返回一个新的行。
  • 解决方案是在__init__函数中使用self.ax=fig.add_subplot()self.line,= self.ax.plot()并加上逗号。为了更新图表,我们使用self.line.set_data([],[])在每个循环中添加数据。
  • 要在循环结束时绘制图表,请使用fig.canvas.draw_idle(),它比fig.canvas.draw()快一些(大约10-20%)。

相关问题