How to load a heavy model when using MVC design pattern

前端 未结 3 1228
终归单人心
终归单人心 2021-01-17 06:34

I\'m building an application with wxPython and a Deep Learning model that I created using Tensorflow. The design pattern that I\'m using is MVC. My problem is that the deep

相关标签:
3条回答
  • 2021-01-17 06:51

    You could use the _thread module and the PyPubSub module to keep the main thread completely functional during the loading of the model.

    However, keep in mind that if you have a wx.Button in the GUI bound to method A and method A requires the full model to be loaded to properly run, then users will be able to click the wx.Button and the program will probably hang because the model is still not fully loaded. If this is the case, you could use the methods Disable() (while the model is being loaded) and Enable() (after the model is loaded) to prevent this.

    Code with comments (####).

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

    If you load the model from a method inside class View then you will not need the PyPubSub module because you could just call wx.CallAfter(self.ShowLoadProgress, x)

    0 讨论(0)
  • 2021-01-17 07:01

    Just for fun and because I prefer the answer that kbr85 gave to my simplistic first answer, here's a Threaded variant with a gauge in the statusbar and a Busy cursor, although my screenshot program didn't pick it up.
    There's a Stop button and the statusbar is removed once the load finishes.
    Rather than use pubsub, I've used a wxpython event to communicate.

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

    0 讨论(0)
  • 2021-01-17 07:11

    You should call wx.GetApp().Yield() after the self.view.Show() command, which releases control momentarily to the MainLoop.
    If the model load code is performed in increments, you can call Yield periodically during the load as well.
    Below is a simple method of informing the user that something is going on. If you wanted the option of cancelling the model load, you would have to wrap it in a dialog, assuming that it is loaded incrementally.

    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()
    
    0 讨论(0)
提交回复
热议问题