Как загрузить тяжелую модель при использовании шаблона проектирования MVC - PullRequest
0 голосов
/ 10 июня 2019

Я создаю приложение с помощью wxPython и модели Deep Learning, которую я создал с помощью Tensorflow.Шаблон дизайна, который я использую, это MVC.Моя проблема в том, что модель глубокого обучения очень тяжелая и требует очень много времени для загрузки (например, ~ 2 минуты), а между тем графический интерфейс зависает.Я создал пример кода, который описывает процесс.Вот как выглядит GUI при загрузке:

введите описание изображения здесь

, и вот как выглядит GUI после загрузки: введите описание изображенияздесь

Вопрос в том, как заставить приложение работать во время загрузки модели?Я также хотел бы добавить строку состояния в GUI, указывающую, что модель загружается или уже загружена.

Я добавляю пример кода, который показывает, как создается мое приложение.

import wx
import time

class Model:
    def __init__(self):
        '''This part is simulating the loading of tensorflow'''
        x = 0
        while x < 15:
            time.sleep(1)
            print(x)
            x += 1

class View(wx.Frame):
    def __init__(self, parent, title):
        super(View, self).__init__(parent, title=title, size=(400, 400))
        self.InitUI()

    def InitUI(self):
        # Defines the GUI controls
        masterPanel = wx.Panel(self)
        masterPanel.SetBackgroundColour("gold")
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
        id = wx.StaticText(self, label="ID:")
        firstName = wx.StaticText(self, label="First name:")
        lastName = wx.StaticText(self, label="Last name:")
        self.idTc = wx.TextCtrl(self)
        self.firstNameTc = wx.TextCtrl(self)
        self.lastNameTc = wx.TextCtrl(self)
        self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                         firstName, (self.firstNameTc, 1, wx.EXPAND),
                         lastName, (self.lastNameTc, 1, wx.EXPAND)])
        self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
   border=15)
        self.SetSizer(self.vbox)
        self.vbox.Fit(self)
        self.Layout()

class Controller:
    def __init__(self):
        self.view = View(None, title='Test')
        self.view.Show()
        self.model = Model()

def main():
    app = wx.App()
    controller = Controller()
    app.MainLoop()

if __name__ == '__main__':
    main()

Ответы [ 3 ]

1 голос
/ 11 июня 2019

Вы можете использовать модуль _thread и модуль PyPubSub, чтобы сохранить основной поток полностью работоспособным во время загрузки модели.

Однако имейте в виду, что если в графическом интерфейсе пользователя wx.Button привязано к method A, а method A требует загрузки полной модели для правильной работы, то пользователи смогут щелкнуть wx.Button и программа, вероятно, зависнет, потому что модель все еще не полностью загружена. Если это так, вы можете использовать методы Disable() (во время загрузки модели) и Enable() (после загрузки модели), чтобы предотвратить это.

Код с комментариями (####).

import wx
import time
import _thread
from pubsub import pub

class Model:
    def __init__(self):
        '''This part is simulating the loading of tensorflow'''
        x = 0
        while x < 15:
            time.sleep(1)
            print(x)
            #### This is how you broadcast the 'Loading' message 
            #### from a different thread.
            wx.CallAfter(pub.sendMessage, 'Loading', x=x)
            x += 1
        #### The same as before
        wx.CallAfter(pub.sendMessage, 'Loading', x=x)

class View(wx.Frame):
    def __init__(self, parent, title):
        super(View, self).__init__(parent, title=title, size=(400, 400))
        self.InitUI()

    def InitUI(self):
        # Defines the GUI controls
        #### It needed to set the size of the panel to cover the frame
        #### because it was not covering the entire frame before
        masterPanel = wx.Panel(self, size=(400, 400))
        masterPanel.SetBackgroundColour("gold")
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
        id = wx.StaticText(self, label="ID:")
        firstName = wx.StaticText(self, label="First name:")
        lastName = wx.StaticText(self, label="Last name:")
        self.idTc = wx.TextCtrl(self)
        self.firstNameTc = wx.TextCtrl(self)
        self.lastNameTc = wx.TextCtrl(self)
        self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                         firstName, (self.firstNameTc, 1, wx.EXPAND),
                         lastName, (self.lastNameTc, 1, wx.EXPAND)])
        self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
   border=15)
        self.SetSizer(self.vbox)
        self.vbox.Fit(self)
        self.Layout()

        #### Create status bar to show loading progress. 
        self.statusbar = self.CreateStatusBar(1)
        self.statusbar.SetStatusText('Loading model')
        #### Set the size of the window because the status bar steals space
        #### in the height direction.
        self.SetSize(wx.DefaultCoord, 160)
        #### Subscribe the class to the message 'Loading'. This means that every
        #### time the meassage 'Loading' is broadcast the method 
        #### ShowLoadProgress will be executed.
        pub.subscribe(self.ShowLoadProgress, 'Loading')
        #### Start the thread that will load the model
        _thread.start_new_thread(self.LoadModel, ('test',))

    def LoadModel(self, test):
        """
        Load the Model
        """
        #### Change depending on how exactly are you going to create/load the 
        #### model
        self.model = Model()

    def ShowLoadProgress(self, x):
        """
        Show the loading progress 
        """
        if x < 15:
            self.statusbar.SetStatusText('Loading progress: ' + str(x))
        else:
            self.statusbar.SetStatusText('All loaded')

class Controller:
    def __init__(self):
        self.view = View(None, title='Test')
        self.view.Show()
        #### The line below is not needed now because the model is 
        #### loaded now from the thread started in View.InitUI
        #self.model = Model()

def main():
    app = wx.App()
    controller = Controller()
    app.MainLoop()

if __name__ == '__main__':
    main()

Если вы загружаете модель из метода внутри class View, тогда вам не понадобится модуль PyPubSub, потому что вы можете просто вызвать wx.CallAfter(self.ShowLoadProgress, x)

1 голос
/ 11 июня 2019

Просто для удовольствия, и потому что я предпочитаю ответ, который kbr85 дал моему простому первому ответу, вот вариант с резьбой с gauge в statusbar и курсор Занят, хотя моя программа скриншотов не подняла его.
Там есть кнопка Stop и statusbar удаляется после завершения загрузки.
Вместо использования pubsub я использовал wxpython event для связи.

import wx
import time
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
load_status=["Model Loading","Model Loaded","Model Cancelled"]

class Model(Thread):
    def __init__(self,parent):
        Thread.__init__(self)
        '''This thread simulates the loading of tensorflow'''
        self.stopthread = 0
        self.target = parent
        self.start()

    def run(self):
        while not self.stopthread:
            for i in range(20):
                if self.stopthread:
                    break
                time.sleep(0.5)
                evt = progress_event(count=i, status=self.stopthread)
                wx.PostEvent(self.target, evt)
            if self.stopthread == 0:
                self.stopthread = 1
        evt = progress_event(count=i, status=self.stopthread)
        wx.PostEvent(self.target, evt)

    def terminate(self):
        self.stopthread = 2

class View(wx.Frame):
    def __init__(self, parent, title):
        super(View, self).__init__(parent, title=title, size=(400, 400))
        self.InitUI()

    def InitUI(self):
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
        id = wx.StaticText(self, label="ID:")
        firstName = wx.StaticText(self, label="First name:")
        lastName = wx.StaticText(self, label="Last name:")
        self.idTc = wx.TextCtrl(self)
        self.firstNameTc = wx.TextCtrl(self)
        self.lastNameTc = wx.TextCtrl(self)
        self.stop = wx.Button(self, -1, "Stop Load")

        self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                         firstName, (self.firstNameTc, 1, wx.EXPAND),
                         lastName, (self.lastNameTc, 1, wx.EXPAND),
                         (self.stop,1,wx.EXPAND)])

        self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,border=15)
        #Bind to the progress event issued by the thread
        self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
        #Bind to Stop button
        self.Bind(wx.EVT_BUTTON, self.OnStop)
        #Bind to Exit on frame close
        self.Bind(wx.EVT_CLOSE, self.OnExit)
        self.SetSizer(self.vbox)
        self.Layout()

        self.statusbar = self.CreateStatusBar(2)
        self.text = wx.StaticText(self.statusbar,-1,("No Model loaded"))
        self.progress = wx.Gauge(self.statusbar, range=20)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.text, 0, wx.ALIGN_TOP|wx.ALL, 5)
        sizer.Add(self.progress, 1, wx.ALIGN_TOP|wx.ALL, 5)
        self.statusbar.SetSizer(sizer)
        wx.BeginBusyCursor()
        self.loadthread = Model(self)


    def OnProgress(self, event):
        self.text.SetLabel(load_status[event.status])
        #self.progress.SetValue(event.count)
        #or for indeterminate progress
        self.progress.Pulse()
        if event.status != 0:
            self.statusbar.Hide()
            wx.EndBusyCursor()
            self.Layout()

    def OnStop(self, event):
        if self.loadthread.isAlive():
            self.loadthread.terminate() # Shutdown the thread
            self.loadthread.join() # Wait for it to finish

    def OnExit(self, event):
        if self.loadthread.isAlive():
            self.loadthread.terminate() # Shutdown the thread
            self.loadthread.join() # Wait for it to finish
        self.Destroy()

class Controller:
    def __init__(self):
        self.view = View(None, title='Test')
        self.view.Show()

def main():
    app = wx.App()
    controller = Controller()
    app.MainLoop()

if __name__ == '__main__':
    main()

enter image description here

0 голосов
/ 11 июня 2019

Вы должны вызвать wx.GetApp().Yield() после команды self.view.Show(), которая на мгновение освобождает управление до MainLoop.
Если код загрузки модели выполняется с шагом, вы можете периодически вызывать Yield во время загрузки.
Ниже приведен простой способ информирования пользователя о том, что что-то происходит. Если вы хотите отменить загрузку модели, вам придется заключить ее в диалоговое окно, предполагая, что она загружается постепенно.

import wx
import time

class Model:
    def __init__(self):
        '''This part is simulating the loading of tensorflow'''
        x = 0
        #If the model load is perform in increments you could call wx.Yield
        # between the increments.
        while x < 15:
            time.sleep(1)
            wx.GetApp().Yield()
            print(x)
            x += 1

class View(wx.Frame):
    def __init__(self, parent, title):
        super(View, self).__init__(parent, title=title, size=(400, 400))
        self.InitUI()

    def InitUI(self):
        # Defines the GUI controls
        #masterPanel = wx.Panel(self)
        #masterPanel.SetBackgroundColour("gold")
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
        id = wx.StaticText(self, label="ID:")
        firstName = wx.StaticText(self, label="First name:")
        lastName = wx.StaticText(self, label="Last name:")
        self.idTc = wx.TextCtrl(self)
        self.firstNameTc = wx.TextCtrl(self)
        self.lastNameTc = wx.TextCtrl(self)
        self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                         firstName, (self.firstNameTc, 1, wx.EXPAND),
                         lastName, (self.lastNameTc, 1, wx.EXPAND)])
        self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
   border=15)
        self.CreateStatusBar() # A Statusbar in the bottom of the window
        self.StatusBar.SetStatusText("No model loaded", 0)
        self.SetSizer(self.vbox)
        self.vbox.Fit(self)
        self.Layout()

class Controller:
    def __init__(self):
        self.view = View(None, title='Test')
        self.view.Show()
        self.view.SetStatusText("Model loading", 0)
        wait = wx.BusyInfo("Please wait, loading model...")
        #Optionally add parent to centre message on self.view
        #wait = wx.BusyInfo("Please wait, loading model...", parent=self.view)
        wx.GetApp().Yield()
        self.model = Model()
        self.view.SetStatusText("Model loaded", 0)
        del wait

def main():
    app = wx.App()
    controller = Controller()
    app.MainLoop()

if __name__ == '__main__':
    main()
...