1

我知道人们提出了几个关于非响应式 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 帧作为参数传递给需要腌制的东西时存在问题,?它不能因为它不是线程安全的?它给出了一个以某种方式指向 Tkinter 的 pickle 错误。
但是,我不确定如何重新组织此代码,以便唯一被腌制的部分是数据部分,因为线程或进程必须访问 Tkinter 帧才能通过refreshFigure().

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

4

1 回答 1

2

The segment target=Deconvolution(tab).deconvolute() will actually be evaluated instead of passed to a subprocess. You could replace this with a wrapper function

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

I'm not sure if your queue is actually be used at all but I believe that would be more appropriate for a worker Pool scenario.

Edit:

Oh, and you would call it like so

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

Edit again:

You could just define that as a lambda function too unless you to to add more complexity

mp_deconv = lambda x: Deconvolution(tab).deconvolute()
process_list.append(mp.Process(target=mp_deconv, args=(tab)))
于 2013-02-04T06:55:49.513 回答