2

I was wondering if anyone had any good solutions to the pickling error I am having at the moment. I am trying to set my code up to open several different processes in parallel, each with a fitting process to be display on a matplotlib canvas in real time. Within my main application, I have a button which activates this function:

def process_data(self):
        process_list = []
        for tab in self.tab_list:
            process_list.append(mp.Process(target=process_and_fit, args=(tab,)))
            process_list[-1].start()
            process_list[-1].join()
        return

As you may notice, a 'tab' (PyQt4.QtGui.QTabWidget object) is passed to the function process_and_fit, which I have noticed is not able to be pickled readily (link here) . However, I am not certain how to change the code to get rid of the frame being passed since it needs to be called in the process_and_fit function indirectly. By indirectly I mean something like this: (psuedo code again)

def process_and_fit(tab): # this just sets up and starts the fitting process
        result = lmfit.Minimizer(residual, parameters, fcn_args=(tab,))
        result.prepare_fit()
        result.leastsq()

def residual(params, tab):
    residual_array = Y - model
    tab.refreshFigure()
    return residual_array

class tab(QtGui.QTabWidget):
    def __init__(self, parent, spectra):
       # stuff to initialize the tab widget and hold all of the matplotlib lines and canvases

    # This just refreshes the GUI stuff everytime that the parameters are fit in the least squares method
    def refreshFigure(self):     
        self.line.set_data(self.spectra.X, self.spectra.model)
        self.plot.draw_artist(self.line)
        self.plot.figure.canvas.blit(self.plot.bbox)

Does anyone know how to get around this pickling error since the tab associated with a process should have only one set of data associated with it? I looked at Steven Bethard's approach but I really didn't understand where to put the code or how to utilize it. (I am a chemical engineer, not a computer scientist so there's a lot that I don't understand)

Any help is greatly appreciated.

EDIT: I added the links in that I forgot about, as requested.

Community
  • 1
  • 1
chase
  • 3,592
  • 8
  • 37
  • 58

1 Answers1

2

The main issue is that you can't make UI changes from a separate process from the main UI thread (the one that all of your Qt calls are in). You need to use a mp.Pipe or mp.Queue to communicate back to the main process.

def process_data(self):
    for tab in self.tab_list:
        consumer, producer = mp.Pipe()
        process_list.append(mp.Process(target=process_and_fit, args=(producer,)))
        process_list[-1].start()
        while (true):
            message = consumer.recv()  # blocks
            if message == 'done':
                break
            # tab.spectra.X, tab.spectra.model = message
            tab.refreshFigure()
        process_list[-1].join()
    return

def process_and_fit(pipe_conn):
    ...
    pipe_conn.send('done')

def residual(params, pipe_conn):
    residual_array = Y - model
    pipe_conn.send('refresh')  # or replace 'refresh' with (X, model)
    return residual_array

One more thing to note: blocking for the consumer.recv() will probably hang the GUI thread. There are plenty of resources to mitigate this, the question "subprocess Popen blocking PyQt GUI" will help, since you should probably switch to QThreads. (Qthread: PySide, PyQt)

The advantage of using QThreads instead of Python threads is that with QThreads, since you're already in Qt's main event loop, you can have asynchronous (non-blocking) callbacks to update the UI.

Community
  • 1
  • 1
forivall
  • 9,504
  • 2
  • 33
  • 58
  • Thank you for this answer. It hasn't worked out directly, but I'll keep reading about these `mp.Pipe` objects and see what I can come up with. I think there may be a problem since I have created my own tab object with a whole lot of GUI information as well as my main GUI app which just makes the tabs on the fly. – chase Feb 06 '13 at 23:43
  • 1
    Yup, as long as you understand the gist of it. IPC(Inter-Process Communication) is one of the more Computer Science-y things that take some work if you haven't done it before. Python has magic, but it can't hide everything. – forivall Feb 07 '13 at 00:03
  • As of now, the program is just reverting to 'not responding' without any errors with this update. I'm assuming the `"refresh"` string that is sent is supposed to be the arrays of the stuff (`self.spectra.X, self.spectra.model`) that I am refreshing for the `matplotlib` line needed in the `refreshFigure()` function. However, I don't understand how the `consumer.recv()` is supposed to act on the system in any way. How is the `tab.refreshFigure()` being executed any more than once for every tab? – chase Feb 07 '13 at 00:09
  • 1
    Oh, I didn't realize that `residual` gets called multiple times -- I'll amend my answer – forivall Feb 07 '13 at 00:11
  • Oh I'm sorry I wasn't too clear about that. `residual` is iteratively attempting to fit a complex function held in another class (called `spectra`) by changing `spectra.params`. Those parameters are then used in `tab.refreshFigure()` to update several matplotlib lines (held in `tab.__init__()`). (`tab` has a `spectra` object) My hope was to put the arrays into the spectra object everytime the residual function iterated. Then, since the tab object should have the spectra object inside of it, it could just call `refreshFigure()` and the parameters would already be within the tab object. – chase Feb 07 '13 at 00:23
  • 1
    Ok. Yup, replace the `"refresh"` with the data that needs to be sent back, and send that to your tab. – forivall Feb 07 '13 at 00:27
  • I tried it but it's still immediatly going to not responding, which occurs at `process_list[-1].start()`. I tried putting `print "something"` statements at the beginning of the `process_and_fit()` function but those did not occur. I'll continue to work on it and maybe I will see something. – chase Feb 07 '13 at 00:42