Note that there are some explanatory texts on larger screens.

plurals
  1. POobject oriented architecture and pickling problems and multiprocessing in Tkinter/matplotlib GUI
    primarykey
    data
    text
    <p>I know that several questions have been created with people asking about non-responsive GUIs and the ultimate answer is that Tkinter is not thread safe. However, it is my understanding that queues can be utilized to overcome this problem. Therefore, I have been looking into using the multiprocessing module with queues such that my code can be utilized on hyperthreaded and multicore systems.<br> What I would like to do is to try and do a very complex least squares fitting of multiple imported spectra in different tabs whenever a button is pressed. The problem is that my code is still hanging up on the long process that I initialize by a button in my GUI. I have knocked the code down to something that still may run and has most of the objects of my original program, yet still suffers from the problem of not being responsive. I believe my problem is in the multiprocessing portion of my program. </p> <p><strong>Therefore my question is regarding the multiprocessing portion of the code and if there is a better way to organize the process_spectra() function shown here:</strong></p> <pre><code>def process_spectra(self): process_list = [] queue = mp.Queue() for tab in self.tab_list: process_list.append(mp.Process(target=Deconvolution(tab).deconvolute(), args=(queue,))) process_list[-1].start() process_list[-1].join() return </code></pre> <p>At the moment it appears that this is not actually making the deconvolution process into a different thread. I would like the process_spectra function to process all of the spectra with the deconvolution function simultaneously while still being able to interact with and see the changes in the spectra and GUI.</p> <p>Here is the full code which can be run as a .py file directly to reproduce my problem:</p> <pre><code>from Tkinter import * import Tkinter import tkFileDialog import matplotlib from matplotlib import * matplotlib.use('TKAgg') from matplotlib import pyplot, figure, backends import numpy as np import lmfit import multiprocessing as mp # lots of different peaks can appear class peak: def __init__(self, n, m): self.n = n self.m = m def location(self, i): location = i*self.m/self.n return location def NM(self): return str(self.n) + str(self.m) # The main function that is given by the user has X and Y data and peak data class Spectra: def __init__(self, spectra_name, X, Y): self.spectra_name = spectra_name self.X = X self.Y = Y self.Y_model = Y*0 self.Y_background_model = Y*0 self.Y_without_background_model = Y*0 self.dYdX = np.diff(self.Y)/np.diff(self.X) self.peak_list = self.initialize_peaks(3, 60) self.params = lmfit.Parameters() def peak_amplitude_dictionary(self): peak_amplitude_dict = {} for peak in self.peak_list: peak_amplitude_dict[peak] = self.params['P' + peak.NM() + '_1_amp'].value return peak_amplitude_dict def peak_percentage_dictionary(self): peak_percentage_dict = {} for peak in self.peak_list: peak_percentage_dict[peak] = self.peak_amplitude_dictionary()[peak]/np.sum(self.peak_amplitude_dictionary().values()) return peak_percentage_dict # Function to create all of the peaks and store them in a list def initialize_peaks(self, lowestNM, highestNM): peaks=[] for n in range(0,highestNM+1): for m in range(0,highestNM+1): if(n&lt;lowestNM and m&lt;lowestNM): break elif(n&lt;m): break else: peaks.append(peak(n,m)) return peaks # This is just a whole bunch of GUI stuff class Spectra_Tab(Frame): def __init__(self, parent, spectra): self.spectra = spectra self.parent = parent Frame.__init__(self, parent) self.tab_name = spectra.spectra_name self.canvas_frame = Frame(self, bd=3, bg= 'WHITE', relief=SUNKEN) self.canvas_frame.pack(side=LEFT, fill=BOTH, padx=0, pady=0, expand=1) self.results_frame = Frame(self, bd=3, bg= 'WHITE', relief=SUNKEN, width=600) self.results_frame.pack(side=RIGHT, fill=BOTH, padx=0, pady=0, expand=1) self.top_canvas_frame = Frame(self.canvas_frame, bd=0, bg= 'WHITE', relief=SUNKEN) self.top_canvas_frame.pack(side=TOP, fill=BOTH, padx=0, pady=0, expand=1) self.original_frame = Frame(self.top_canvas_frame, bd=1, relief=SUNKEN) self.original_frame.pack(side=LEFT, fill=BOTH, padx=0, pady=0, expand=1) self.scrollbar = Scrollbar(self.results_frame) self.scrollbar.pack(side=RIGHT, fill=BOTH,expand=1) self.sidebar = Listbox(self.results_frame) self.sidebar.pack(fill=BOTH, expand=1) self.sidebar.config(yscrollcommand=self.scrollbar.set) self.scrollbar.config(command=self.sidebar.yview) self.original_fig = figure.Figure() self.original_plot = self.original_fig.add_subplot(111) init_values = np.zeros(len(self.spectra.Y)) self.original_line, = self.original_plot.plot(self.spectra.X, self.spectra.Y, 'r-') self.original_background_line, = self.original_plot.plot(self.spectra.X, init_values, 'k-', animated=True) self.original_canvas = backends.backend_tkagg.FigureCanvasTkAgg(self.original_fig, master=self.original_frame) self.original_canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) self.original_canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1) self.original_canvas.show() self.original_canvas.draw() self.original_canvas_BBox = self.original_plot.figure.canvas.copy_from_bbox(self.original_plot.bbox) ax1 = self.original_plot.figure.axes[0] ax1.set_xlim(self.spectra.X.min(), self.spectra.X.max()) ax1.set_ylim(0, self.spectra.Y.max() + .05*self.spectra.Y.max()) self.step=0 self.update() # This just refreshes the GUI stuff everytime that the parameters are fit in the least squares method def refreshFigure(self): self.step=self.step+1 if(self.step==1): self.original_canvas_BBox = self.original_plot.figure.canvas.copy_from_bbox(self.original_plot.bbox) self.original_plot.figure.canvas.restore_region(self.original_canvas_BBox) self.original_background_line.set_data(self.spectra.X, self.spectra.Y_background_model) self.original_plot.draw_artist(self.original_line) self.original_plot.draw_artist(self.original_background_line) self.original_plot.figure.canvas.blit(self.original_plot.bbox) # show percentage of peaks on the side bar self.sidebar.delete(0, Tkinter.END) peak_dict = self.spectra.peak_percentage_dictionary() for peak in sorted(peak_dict.iterkeys()): self.sidebar.insert(0, peak.NM() + ' ' + str(peak_dict[peak]) + '%' ) return # just a tab bar class TabBar(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.tabs = {} self.buttons = {} self.current_tab = None def show(self): self.pack(side=BOTTOM, expand=0, fill=X) def add(self, tab): tab.pack_forget() self.tabs[tab.tab_name] = tab b = Button(self, text=tab.tab_name, relief=RAISED, command=(lambda name=tab.tab_name: self.switch_tab(name))) b.pack(side=LEFT) self.buttons[tab.tab_name] = b def switch_tab(self, name): if self.current_tab: self.buttons[self.current_tab].config(relief=RAISED) self.tabs[self.current_tab].pack_forget() self.tabs[name].pack(side=BOTTOM) self.current_tab = name self.buttons[name].config(relief=SUNKEN) class Deconvolution: def __init__(self, spectra_tab): self.spectra_tab = spectra_tab self.spectra = spectra_tab.spectra self.model = [0 for x in self.spectra.X] self.model_without_background = [0 for x in self.spectra.X] self.residual_array = [0 for x in self.spectra.X] # Amplitudes for backgrounds self.pi_plasmon_amp = np.interp(4.3, self.spectra.X, self.spectra.Y) self.graphite_amp = np.interp(5, self.spectra.X, self.spectra.Y) self.spectra.params.add('PPAmp', value=self.pi_plasmon_amp, vary=True, min=0.0, max=None) self.spectra.params.add('PPCenter', value=4.3, vary=True) self.spectra.params.add('PPFWHM', value=.4, vary=True) self.spectra.params.add('GLAmp', value=self.graphite_amp, vary=True, min=0.0, max=None) self.spectra.params.add('GLCenter', value=5, vary=True) self.spectra.params.add('GLFWHM', value=.4, vary=True) self.background_model = self.pseudoVoigt(self.spectra.X, self.spectra.params['PPAmp'].value, self.spectra.params['PPCenter'].value, self.spectra.params['PPFWHM'].value, 1)+\ self.pseudoVoigt(self.spectra.X, self.spectra.params['GLAmp'].value, self.spectra.params['GLCenter'].value, self.spectra.params['GLFWHM'].value, 1) for peak in self.spectra.peak_list: for i in range(1,4): param_prefix = 'P' + peak.NM() + '_' + str(i) center = peak.location(i) amp = np.interp(center, self.spectra.X, self.spectra.Y - self.background_model) width = 0.02 self.spectra.params.add(param_prefix + '_amp', value = 0.8*amp, vary=False, min=0.0, max=None) self.spectra.params.add(param_prefix + '_center', value = center, vary=False, min=0.0, max=None) self.spectra.params.add(param_prefix + '_width', value = width, vary=False, min=0.0, max=None) self.model_without_background += self.pseudoVoigt(self.spectra.X, self.spectra.params[param_prefix + '_amp'].value, self.spectra.params[param_prefix + '_center'].value, self.spectra.params[param_prefix + '_width'].value, 1) def deconvolute(self): for State in range(0,3): # Make each voigt profile for each tube for peak in self.spectra.peak_list: for i in range(1,4): param_prefix = 'P' + peak.NM() + '_' + str(i) if(State==1): self.spectra.params[param_prefix + '_amp'].vary = True if(State==2): self.spectra.params[param_prefix + '_width'].vary = True result = lmfit.Minimizer(self.residual, self.spectra.params, fcn_args=(State,)) result.prepare_fit() result.leastsq()#lbfgsb() def residual(self, params, State): self.model = self.background_model if(State&gt;0): self.model += self.model_without_background for x in range(0, len(self.spectra.X)): if(self.background_model[x]&gt;self.spectra.Y[x]): self.residual_array[x] = -999999.-9999.*(self.spectra.Y[x]-self.background_model[x]) else: self.residual_array[x] = self.spectra.Y[x]-self.model[x] self.spectra.Y_model = self.model self.spectra.Y_background_model = self.background_model self.spectra.Y_without_background_model = self.model_without_background self.spectra_tab.refreshFigure() return self.residual_array def pseudoVoigt(self, x, amp, center, width, shapeFactor): LorentzPortion = (width**2/((x-center)**2+width**2)) GaussianPortion = 1/(np.sqrt(2*np.pi*width**2))*np.e**(-(x-center)**2/(2*width**2)) try: Voigt = amp*(shapeFactor*LorentzPortion+(1-shapeFactor)*GaussianPortion) except ZeroDivisionError: width = width+0.01 LorentzPortion = (width**2/((x-center)**2+width**2)) GaussianPortion = 1/(np.sqrt(2*np.pi*width**2))*np.e**(-(x-center)**2/(2*width**2)) Voigt = amp*(shapeFactor*LorentzPortion+(1-shapeFactor)*GaussianPortion) return Voigt class MainWindow(Tk): def __init__(self, parent): Tk.__init__(self, parent) self.parent = parent self.wm_state('zoomed') self.spectra_list = [] self.tab_list = [] self.button_frame = Frame(self, bd=3, relief=SUNKEN) self.button_frame.pack(side=TOP, fill=BOTH) self.tab_frame = Frame(self, bd=3, relief=SUNKEN) self.tab_frame.pack(side=BOTTOM, fill=BOTH, expand=1) open_spectra_button = Button(self.button_frame, text='open spectra', command=self.open_spectra) open_spectra_button.pack(side=LEFT, fill=Y) process_spectra_button = Button(self.button_frame, text='process spectra', command=self.process_spectra) process_spectra_button.pack(side=LEFT, fill=Y) self.tab_bar = TabBar(self.tab_frame) self.tab_bar.show() self.resizable(True,False) self.update() def open_spectra(self): # This will prompt user for file input later, but here is an example file_name_list = ['spectra_1', 'spectra_2'] for file_name in file_name_list: # Just make up functions that may be imported X_values = np.arange(1240.0/1350.0, 1240./200., 0.01) if(file_name=='spectra_1'): Y_values = np.array(np.e**.2*X_values + np.sin(10*X_values)+np.cos(4*X_values)) if(file_name=='spectra_2'): Y_values = np.array(np.e**.2*X_values + np.sin(10*X_values)+np.cos(3*X_values)+.3*np.cos(.5*X_values)) self.spectra_list.append(Spectra(file_name, X_values, Y_values)) self.tab_list.append(Spectra_Tab(self.tab_frame, self.spectra_list[-1])) self.tab_bar.add(self.tab_list[-1]) self.tab_bar.switch_tab(self.spectra_list[0].spectra_name) self.tab_bar.show() return def process_spectra(self): process_list = [] queue = mp.Queue() for tab in self.tab_list: process_list.append(mp.Process(target=Deconvolution(tab).deconvolute(), args=(queue,))) process_list[-1].start() process_list[-1].join() return if __name__ == "__main__": root = MainWindow(None) root.mainloop() </code></pre> <p><strong>EDIT:</strong> I am editing this question because I realized that my question did not regard the real problem. I think the code I have supplied has problems with having a Tkinter Frame passed as a parameter to something that needs to be pickled, ? and it can't because it's not thread safe?? It gives a pickle error that points to Tkinter in some way.<br> However, I am not sure how to reorganize this code such that the only part that is pickled is the data part since the threads or processes must access the Tkinter frames in order to update them via <code>refreshFigure()</code>. </p> <p>Does anyone have any ideas regarding how to do this? I have researched it but everyone's examples are usually simple with only one figure or that only refreshes after the process is completed.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload