chaco - making several Containers show separate plots - traits

I have written a chaco plotting class that plots some data and allows the user to interact with it. I then wanted to make a TraitsUI GUI that has several different instances of this chaco plot so that the user can have several of the plots and interact with them independently.
However, when I try and implement this I seem to get that each of the separate instances of my chaco plot are displaying all the data from all the plots. I have made a very simple GUI below that reproduces the problem.
In the example below I would like each tab to show a container with a single line plot. However, each container seems to plot all the plots that have been plotted in any of the containers. From the documentation here chaco container docs, I think what I have done should work.
I have also tried using the ListEditor view, but this has the same problem.
Am I misunderstanding something about chaco Containers? How can I get each container instance to act independently? Any help would be appreciated.
Thanks!
import enthought.chaco.api as chaco
import enthought.traits.api as traits
import enthought.traits.ui.api as traitsui
from enthought.enable.api import ComponentEditor
import scipy
class BasicPlot(traits.HasTraits):
container = chaco.Plot(padding=(120,20,20,40), bgcolor="white",
use_backbuffer = True,
border_visible = True,
fill_padding = True)
traits_view = traitsui.View(traitsui.Item('container', editor = ComponentEditor(), show_label = False),
width = 500, height = 500,
resizable = True, title = "My line plot")
def __init__(self, n, *args, **kw):
super(BasicPlot, self).__init__(*args, **kw)
xs = scipy.linspace(0, 6.3, 1000)
ys = scipy.sin(n*xs)
plot = chaco.create_line_plot([xs,ys])
self.container.add(plot)
chaco.add_default_grids(plot)
chaco.add_default_axes(plot)
class tabbedPlots(traits.HasTraits):
bp1 = BasicPlot(1)
bp2 = BasicPlot(2)
bpGroup = traitsui.Group(traitsui.Item("bp1", editor = traitsui.InstanceEditor(), style="custom", show_label=False),
traitsui.Item("bp2", editor = traitsui.InstanceEditor(), style="custom", show_label=False), layout="tabbed")
traits_view = traitsui.View(bpGroup,title = "Log File Plots")
class tabbedPlotsList(traits.HasTraits):
bps = traits.List(BasicPlot)
bpGroup = traitsui.Group(
traitsui.Item('bps',style="custom",
editor=traitsui.ListEditor(use_notebook=True, deletable=True,export = 'DockWindowShell', page_name=".name")
,label="logFilePlots", show_label=False)
)
traits_view = traitsui.View(bpGroup,title = "Log File Plots")
def __init__(self, **traitsDict):
super(tabbedPlotsList, **traitsDict)
self.bps = [BasicPlot(n) for n in range(0,8)]
if __name__=="__main__":
gui = tabbedPlots()
gui.configure_traits()
gui2 = tabbedPlotsList()
gui2.configure_traits()

I found the fix to this.
def __init__(self, n, *args, **kw):
super(BasicPlot, self).__init__(*args, **kw)
self.container = chaco.Plot(padding=(120,20,20,40), bgcolor="white",
use_backbuffer = True,
border_visible = True,
fill_padding = True)
xs = scipy.linspace(0, 6.3, 1000)
ys = scipy.sin(n*xs)
plot = chaco.create_line_plot([xs,ys])
self.container.add(plot)
chaco.add_default_grids(plot)
chaco.add_default_axes(plot)
To make it work as desired the container cannot be a class attribute. Instead it must be defined inside the init as self.container(...). (This makes sense)
If this change is made you get the desired functionality.

Related

Tkinter: How can I check if any of the widgets of a specific frame have changed?

What is the best way to check if any widget (or variable linked to it) of a given frame(frm1) has changed and take an action. For example activate a button.
I would like that when something was typed in the entries or changed the combobox or checkbox, the 'changed_content' function would be executed
from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo
class Defaultframe(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry('500x300')
self.title('Tkinter')
self.text1 = StringVar()
self.text2 = StringVar()
self.text3 = StringVar()
self.var1 = IntVar()
self.var2 = IntVar()
self.set_widgets()
return
def changed_content(self):
showinfo('Information', 'The content has been changed')
self.btn2.configure(state='normal')
return
def set_widgets(self):
#Frame1
self.frm1 = ttk.Frame(self).pack(side=TOP)
self.lbl = ttk.Label(self.frm1, text='Text1').pack(pady=5)
self.ent1 = ttk.Entry(self.frm1, textvariable=self.text1).pack(pady=5)
self.lbl = ttk.Label(self.frm1, text='Text2').pack(pady=5)
self.my_ent = ttk.Entry(self.frm1, textvariable=self.text2).pack(pady=5)
self.cbb = ttk.Combobox(self.frm1,
values=[0, 30, 60, 90, 120, 150, 180],
state='readonly',
textvariable=self.var2)
self.cbb.pack(pady=5)
self.cbb.current(3)
self.ckb = ttk.Checkbutton(self.frm1, text='Hello', variable=self.var1, onvalue=1, offvalue=0).pack(pady=5)
#---
#Frame2
self.frm2 = ttk.Frame(self).pack(side=BOTTOM, fill=X)
ttk.Separator(self.frm2, orient=HORIZONTAL).pack(side=TOP, expand=1, fill=X)
self.my_ent3 = ttk.Entry(self.frm2, textvariable=self.text3).pack(side=LEFT, padx=1)
self.btn1 = ttk.Button(self.frm2, text='Cancel').pack(side=RIGHT, padx=1)
self.btn2 = ttk.Button(self.frm2, text='Save')
self.btn2.pack(side=RIGHT, padx=1)
self.btn2.configure(state=DISABLED)
#---
if __name__== '__main__':
app = Defaultframe()
app.mainloop()
Solving for any widget is tough - you'll have to write code specifically for a canvas or a scrollbar or any other widget that isn't associated with a tkinter variable.
For widgets that are associated with a tkinter variable you can apply a trace that will call a function whenever the value changes.
In your code it might look something like this:
class Defaultframe(Tk):
def __init__(self):
...
self._watch_variables(self.text1, self.text2, self.text3, self.var1, self.var2)
def _watch_variables(self, *vars):
for var in vars:
var.trace_add("write", self._handle_trace)
def _handle_trace(self, *args):
self.changed_content()

Is there a way to have multiple rectangles displayed at the same time using visual.Rect in PsychoPy?

I'm trying to create stimuli that consist of 100 small lines in the centre of the screen, with orientations sampled from a Gaussian distribution (please see the image link below):
Orientation stimuli
I've managed to achieve something that almost fits the bill, but this code only works in isolation:
from psychopy import visual, core, event
import numpy as np
from numpy.random import random
import random
Lines = visual.Rect(
win=win, name='Lines',
width=(0.015, 0.0025)[0], height=(0.015, 0.0025)[1],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1, depth=-2.0, interpolate=True)
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 315
sigma = 15
for i in range(100):
Lines.pos = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
Lines.ori = random.gauss(mu, sigma)
I've tried to manipulate this code so that I can integrate it into the full experiment I'm designing in PsychoPy's experiment builder. I run the below code in the experiment builder's coding window calling 'gdist' and 'loc' as values for the 'Orientation' and 'Position' of the rectangles, respectively:
import random
gdist =[]
loc = []
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 90
sigma = 20
for i in range(100):
rloc = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
loc.append(rloc)
gauss = random.gauss(mu, sigma)
gdist.append(gauss)
When I attempt to run the experiment, I get an error return and the experiment fails to start:
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 238, in __del__
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\pyglet\gl\lib.py", line 97, in errcheck
ImportError: sys.meta_path is None, Python is likely shutting down
I'm assuming this has something to do with pyglet not liking the idea of there being 100 rectangles all at once (side note: the script works fine if range(1)). If anyone has any suggestions for how I might fix or work around this problem, I'd be eternally grateful.
i don't see any problem with this idea, except you better use visual.Line instead of Rect, and your units of measure are not described; the key to preserving video memory is BufferImageStim, btw
from psychopy import visual, core, event, monitors
from psychopy.iohub.client import launchHubServer
import random
import numpy as np
MU = 315; SIGMA = 15
num_lines = 100
io = launchHubServer(iohub_config_name='iohub_config.yaml')
display = io.devices.display
mon = monitors.Monitor(name = display.getPsychopyMonitorName())
win = visual.Window([640, 480], units='pix', viewScale = 1.0,
monitor = mon, winType='pyglet',
fullScr = False, waitBlanking = True, useFBO = True, useLights = False,
allowStencil=False, allowGui = True,
screen = display.getIndex(), colorSpace = 'rgb255', color = [128,128,128],
name = 'my_win01')
rects = []
lines_hpos = np.random.uniform(-0.49, 0.49, num_lines)
for i in range(num_lines):
line_rect = visual.Rect(win=win, size=(0.001, 1.0), units='norm',
pos=(0,0), lineWidth=1, lineColor=[1,1,1], fillColor=[1,1,1], opacity=1, depth=-2.0,
name='lines_rect', interpolate=True, autoLog=False, autoDraw=False)
line_rect.pos = [lines_hpos[i], np.random.uniform(-0.49,0.49)]
line_rect.ori = random.gauss(MU, SIGMA)
rects.append(line_rect)
rect_buffer = visual.BufferImageStim(win, buffer='back', stim=rects, sqPower2=False, interpolate=False, name='rect-buffer', autoLog=True)
rect_buffer.draw()
win.flip()
event.waitKeys()

How to use hover events in mpl_connect in matplotlib

I'm working on line plotting a metric for a course module as well as each of its questions within a Jupyter Notebook using %matplotlib notebook. That part is no problem. A module has typically 20-35 questions, so it results in a lot of lines on a chart. Therefore, I am plotting the metric for each question in a low alpha and I want to change the alpha and display the question name when I hover over the line, then reverse those when no longer hovering over the line.
The thing is, I've tried every test version of interactivity from the matplotlib documentation on event handling, as well as those in this question. It seems like the mpl_connect event is never firing, whether I use click or hover.
Here's a test version with a reduced dataset using the solution to the question linked above. Am I missing something necessary to get events to fire?
def update_annot(ind):
x,y = line.get_data()
annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
" ".join([names[n] for n in ind["ind"]]))
annot.set_text(text)
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = line.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
module = 'bd2bc472-ee0d-466f-8557-788cc6de3018'
module_metrics[module] = {
'q_count': 31,
'sequence_pks': [0.5274546300604932,0.5262044653349001,0.5360993905297703,0.5292329279700655,0.5268691588785047,0.5319099014547161,0.5305164319248826,0.5268235294117647,0.573648805381582,0.5647933116581514,0.5669839795681448,0.5646591970121382,0.5663157894736842,0.5646976090014064,0.5659005628517824,0.5693634879925391,0.5728268468888371,0.5668834184858337,0.5687237026647967,0.5795640965549567,0.5877684407096172,0.585690904839841,0.5766899766899767,0.5971341320178529,0.6059972105997211,0.6055516678329834,0.6209865053513262,0.6203121360354065,0.6153666510976179,0.6236909471724459,0.6387654898293196],
'q_pks': {
'0da04f02-4aad-4ac8-91a5-214862b5c0d0': [0.6686046511627907,0.6282051282051282,0.76,0.6746987951807228,0.7092198581560284,0.71875,0.6585365853658537,0.7070063694267515,0.7171052631578947,0.7346938775510204,0.7737226277372263,0.7380952380952381,0.6774193548387096,0.7142857142857143,0.7,0.6962962962962963,0.723404255319149,0.6737588652482269,0.7232704402515723,0.7142857142857143,0.7164179104477612,0.7317073170731707,0.6333333333333333,0.75,0.7217391304347827,0.7017543859649122,0.7333333333333333,0.7641509433962265,0.6869565217391305,0.75,0.794392523364486],
'10bd29aa-3a26-49e6-bc2c-50fd503d7ab5': [0.64375,0.6014492753623188,0.5968992248062015,0.5059523809523809,0.5637583892617449,0.5389221556886228,0.5576923076923077,0.51875,0.4931506849315068,0.5579710144927537,0.577922077922078,0.5467625899280576,0.5362318840579711,0.6095890410958904,0.5793103448275863,0.5159235668789809,0.6196319018404908,0.6143790849673203,0.5035971223021583,0.5897435897435898,0.5857142857142857,0.5851851851851851,0.6164383561643836,0.6054421768707483,0.5714285714285714,0.627906976744186,0.5826771653543307,0.6504065040650406,0.5864661654135338,0.6333333333333333,0.6851851851851852]
}}
suptitle_size = 24
title_size = 18
tick_size = 12
axis_label_size = 15
legend_size = 14
fig, ax = plt.subplots(figsize=(15,8))
fig.suptitle('PK by Sequence Order', fontsize=suptitle_size)
module_name = 'Test'
q_count = module_metrics[module]['q_count']
y_ticks = [0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
x_ticks = np.array([x for x in range(0,q_count)])
x_labels = x_ticks + 1
# Plot it
ax.set_title(module_name, fontsize=title_size)
ax.set_xticks(x_ticks)
ax.set_yticks(y_ticks)
ax.set_xticklabels(x_labels, fontsize=tick_size)
ax.set_yticklabels(y_ticks, fontsize=tick_size)
ax.set_xlabel('Sequence', fontsize=axis_label_size)
ax.set_xlim(-0.5,q_count-0.5)
ax.set_ylim(0,1)
ax.grid(which='major',axis='y')
# Output module PK by sequence
ax.plot(module_metrics[module]['sequence_pks'])
# Output PK by sequence for each question
for qid in module_metrics[module]['q_pks']:
ax.plot(module_metrics[module]['q_pks'][qid], alpha=0.15, label=qid)
annot = ax.annotate("", xy=(0,0), xytext=(-20,20),textcoords="offset points", bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
mpl_id = fig.canvas.mpl_connect('motion_notify_event', hover)
Since there are dozens of modules, I created an ipywidgets dropdown to select the module, which then runs a function to output the chart. Nonetheless, whether running it hardcoded as here or from within the function, mpl_connect never seems to fire.
Here's what this one looks like when run

How to create a dropdown from a gtk.Entry's icon?

I have a gtk.Entry with an icon after the text, intending to be a text search field:
What I'm trying to do is to display a dropdown (i.e. a gtk.ComboBox) when the user clicks on the icon, to choose the type of search. A mock of that feature would be:
I have tried several things without any success. For example, trying to pack an empty gtk.ComboBox only showing an arrow right after the Entry, and stuffing it only on icon-press, which creates the illusion, but it has two drawbacks: a) when I stuff the ComboBox, the toolbar grows, and b) when I clear() the ListStore, the ComboBox retains its width and leaves an ugly grey box.
At this point I guess that I need to create a CellRenderer on icon-press that pops down the icon of the Entry, and I tried without a lot of success to understand the code of gtk.ComboBoxEntry (in gtkcomboboxentry.c), but as far as I understood it uses a vertical Container on the whole piece together with a CellRenderer.
Also GTK+3 doesn't have any ideas on this respect.
Any ideas, or some guidance in how to create this in PyGTK?
I was looking for something similar, so I came up with the code below. I haven't really worried about the aesthetics. I did pass a list of tuples to the MyPopup class, with the idea of passing handlers for each of the menu items in the dropdown. Note that the item.show() is necessary, even though there is a show_all():
from gi.repository import Gtk
class MyPopup(Gtk.MenuButton):
def __init__(self, btndefs):
super(MyPopup, self).__init__()
self.menu = Gtk.Menu()
self.set_popup(self.menu)
#self.set_label(">")
self.set_direction(Gtk.ArrowType.RIGHT)
for btndef in btndefs:
item = Gtk.MenuItem()
item.set_label(btndef[0])
item.show()
self.menu.append(item)
class MainWindow(Gtk.Window):
def __init__(self):
super(MainWindow, self).__init__()
self.set_size_request(100, -1)
self.connect("destroy", lambda x: Gtk.main_quit())
self.hbox = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
self.entry = Gtk.Entry()
self.popup = MyPopup( (("String",),
("String no case",),
("Hexadecimal",),
("Regexp",)) )
self.hbox.pack_start(self.entry, True, True, 0)
self.hbox.pack_start(self.popup, False, True, 0)
self.add(self.hbox)
self.show_all()
def run(self):
Gtk.main()
def main():
mw = MainWindow()
mw.run()
return 0
if __name__ == '__main__':
main()
yup its year late, but lets not make next person stumbled here to be sad like me.
this is the example using Gtk.Menu() popup, you can also similar feat. with Gtk.Popover()
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
opts = {
'hex' : "system-run-symbolic",
'regex' : "font-select-symbolic",
'string' : "font-x-generic-symbolic",
'no-case' : "tools-check-spelling-symbolic",
}
def make_menu(entry, opts):
menu = Gtk.Menu()
for label, icon in opts.items():
item = Gtk.MenuItem()
item.set_label(label)
item.connect(
"activate",
lambda w: entry.set_icon_from_icon_name(0, opts[w.get_label()])
)
menu.append(item)
# NOTE you can use Gtk.ImageMenuItem to add image but its
# Deprecated since version 3.10
menu.show_all()
return menu
def on_icon_release(widget, pos, event):
menu = make_menu(widget, opts)
menu.popup(
parent_menu_shell = None,
parent_menu_item = None,
func = None,
data = None,
button = Gdk.BUTTON_PRIMARY,
activate_time = event.get_time()
)
def make_entry():
entry = Gtk.Entry()
entry.set_icon_from_icon_name(0, 'action-unavailable-symbolic')
entry.set_icon_from_icon_name(1, 'fonts')
entry.set_icon_sensitive(1, True)
entry.set_icon_activatable(1, True)
entry.connect("icon-release", on_icon_release)
return entry
root = Gtk.Window()
root.add(make_entry())
root.show_all()
Gtk.main()

object oriented architecture and pickling problems and multiprocessing in Tkinter/matplotlib GUI

I know that several questions have been created with people asking about non-responsive GUIs and the ultimate answer is that Tkinter is not thread safe. However, it is my understanding that queues can be utilized to overcome this problem. Therefore, I have been looking into using the multiprocessing module with queues such that my code can be utilized on hyperthreaded and multicore systems.
What I would like to do is to try and do a very complex least squares fitting of multiple imported spectra in different tabs whenever a button is pressed.
The problem is that my code is still hanging up on the long process that I initialize by a button in my GUI. I have knocked the code down to something that still may run and has most of the objects of my original program, yet still suffers from the problem of not being responsive.
I believe my problem is in the multiprocessing portion of my program.
Therefore my question is regarding the multiprocessing portion of the code and if there is a better way to organize the process_spectra() function shown here:
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
At the moment it appears that this is not actually making the deconvolution process into a different thread. I would like the process_spectra function to process all of the spectra with the deconvolution function simultaneously while still being able to interact with and see the changes in the spectra and GUI.
Here is the full code which can be run as a .py file directly to reproduce my problem:
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()
EDIT:
I am editing this question because I realized that my question did not regard the real problem. I think the code I have supplied has problems with having a Tkinter Frame passed as a parameter to something that needs to be pickled, ? and it can't because it's not thread safe?? It gives a pickle error that points to Tkinter in some way.
However, I am not sure how to reorganize this code such that the only part that is pickled is the data part since the threads or processes must access the Tkinter frames in order to update them via refreshFigure().
Does anyone have any ideas regarding how to do this? I have researched it but everyone's examples are usually simple with only one figure or that only refreshes after the process is completed.
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)))