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()

これを行う方法に関するアイデアはありますか?私はそれを調査しましたが、すべての例は通常、図が 1 つしかない単純なものであるか、プロセスが完了した後にのみ更新されます。

4

1 に答える 1

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)))

もう一度編集します。

複雑さを増やさない限り、これをラムダ関数として定義することもできます。

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