面向对象的体系结构和酸洗问题以及Tkinter / matplotlib GUI中的多处理

时间:2013-02-04 06:05:28

标签: oop architecture tkinter multiprocessing pickle

我知道有几个问题是在人们询问无响应的GUI时创建的,最终的答案是Tkinter不是线程安全的。但是,我的理解是可以利用队列来克服这个问题。因此,我一直在研究将多处理模块与队列一起使用,以便我的代码可用于超线程和多核系统。
我想做的是每当按下按钮时,尝试在不同的标签中对多个导入的光谱进行非常复杂的最小二乘拟合。 问题是我的代码仍然挂在我通过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 Frame作为参数传递给需要腌制的东西,?它不能,因为它不是线程安全?它给出了一个以某种方式指向Tkinter的泡菜错误 但是,我不确定如何重新组织此代码,使得被挑选的唯一部分是数据部分,因为线程或进程必须访问Tkinter帧才能通过refreshFigure()更新它们。

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

1 个答案:

答案 0 :(得分: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)))