2013-02-04 33 views
1

我知道有人问过关于非响应式GUI的问题,最终的答案是Tkinter不是线程安全的。但是,我的理解是可以利用队列来克服这个问题。因此,我一直在考虑将多处理模块与队列一起使用,以便我的代码可以用于超线程和多核系统。
我想要做的是尝试在每次按下按钮时在不同标签中对多个导入光谱进行非常复杂的最小二乘拟合。 问题是,我的代码仍然挂在我通过GUI中的按钮初始化的漫长过程中。我已经将代码敲入了一些仍然可能运行的代码,并且包含了我原始程序的大部分对象,但仍然存在无法响应的问题。 我相信我的问题是在我的程序的多处理部分。面向对象的体系结构和酸洗问题以及Tkinter/matplotlib中的多处理GUI

因此,我的问题是关于代码的多部分,如果有更好的方式来组织process_spectra()这里显示功能:

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 

目前看来,这不是实际上使反卷积过程进入不同的线程。我希望process_spectra函数能够同时处理具有去卷积函数的所有光谱,同时仍能够与光谱和GUI中的变化交互并看到变化。

下面是完整的代码可以运行一个.py文件直接复制我的问题:

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<lowestNM and m<lowestNM): break 
       elif(n<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>0): 
      self.model += self.model_without_background 
     for x in range(0, len(self.spectra.X)): 
      if(self.background_model[x]>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() 

编辑: 我编辑这个问题,因为我意识到,我的问题没有考虑真正的问题。我认为我提供的代码在将Tkinter框架作为参数传递给需要被腌制的东西时存在问题?它不能因为它不是线程安全的?它以某种方式给出了Tkinter的泡菜错误。
但是,我不确定如何重新组织这段代码,使得唯一被pickle的部分是数据部分,因为线程或进程必须访问Tkinter框架才能通过refreshFigure()进行更新。

有没有人有关于如何做到这一点的任何想法?我研究过它,但每个人的例子通常都很简单,只有一个数字,或者只有在完成该过程后才刷新。

回答

2

target=Deconvolution(tab).deconvolute()实际上将被评估,而不是传递给子过程。你可以用一个包装函数

def mp_deconvolute(tab): 
    return Deconvolution(tab).deconvolute() 

替换这个我不知道,如果你的queue实际上是在所有使用,但我相信这将是更适合于工人Pool场景。

编辑:

哦,你会再次调用它像这样

process_list.append(mp.Process(target=mp_deconvolute, args=(tab))) 

编辑:

你可以只定义为lambda函数太除非你增加更多复杂性

mp_deconv = lambda x: Deconvolution(tab).deconvolute() 
process_list.append(mp.Process(target=mp_deconv, args=(tab))) 
+0

那么,如果以这种方式添加lambda函数给我一个泡菜错误,那么它是我的代码的糟糕体系结构的反映? '酸洗错误:无法在0x03ECDA70>处腌渍<功能>:它不是__main__。 ' – chase

+1

Lambdas不能被酸洗 - 在这种情况下使用指定的功能将会很好。你显然可以使用'marshal'来序列化lambdas和它们的关闭,但我从来没有尝试过。 –

+0

好吧,我没有意识到lamdas和命名函数除了语法之外还有其他任何真正的区别。我尝试了命名函数,但仍然以错误结束。但是,这对我有帮助,因为我认为我在代码中发现了其他几个错误。 – chase