我知道有几个问题是在人们询问无响应的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()
更新它们。
有没有人对如何做到这一点有任何想法?我已经对它进行了研究,但每个人的例子通常很简单,只有一个数字,或者只是在完成这个过程后才刷新。
答案 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)))