#!/usr/bin/env python
'''
Lake desmearing GUI using Enthought's Traits, Chaco, and Enable packages.
:note: If you are using Ubuntu 11.04 and cannot see a menubar,
this is a bug in Ubuntu 11.04. Try setting the environment
variable: UBUNTU_MENUPROXY=1
Or, the code does not call for a menubar. ... Nevermind.
'''
import os, sys
# make sure lake is on the path, as well
sys.path.insert(0, os.path.abspath( os.path.join(os.path.dirname(__file__), '..') ))
#os.environ['ETS_TOOLKIT'] = 'qt4'
os.environ['UBUNTU_MENUPROXY'] = "1" # work around a menubar bug in Ubuntu 11.04
import threading
import jldesmear.api.toolbox #@UnusedImport
import jldesmear.api.desmear #@UnusedImport
import jldesmear.api.info #@UnusedImport
from enthought.traits.api \
import HasTraits, Instance, File, String, Float, Enum, Button, Range
from enthought.traits.ui.api \
import View, Group, Item, StatusItem, NoButtons, HSplit, VSplit, HGroup, spring
from enthought.traits.ui.menu import StandardMenuBar
from enthought.enable.component_editor \
import ComponentEditor
from enthought.chaco.api \
import Plot, ArrayPlotData, ScatterPlot
from enthought.chaco.tools.api \
import PanTool, ZoomTool
[docs]class ChiSqr_plot(HasTraits):
''' '''
plot = Instance(Plot)
renderer = Instance(ScatterPlot)
def __init__(self):
plot = Plot( ArrayPlotData(x = [], y = []) )
plot.tools.append(PanTool(plot, drag_button="right"))
plot.overlays.append(ZoomTool(plot))
r = plot.plot(
("x", "y"),
type="scatter",
color="goldenrod",
marker='circle',
marker_size=3
)
self.plot = plot
self.renderer = r[0] # 1st item in the list is our scatter plot
[docs] def SetData(self, chiSqr):
'''
provide the data to be plotted,
replaces any existing data on the plot
:param [float] chiSqr: list of ChiSqr values for each iteration
'''
it = range(len(chiSqr))
p = self.plot
d = p.data
d.set_data("x", it)
d.set_data("y", chiSqr)
p.index_scale = 'linear'
p.value_scale = 'log'
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False),
resizable = True,
title="ChiSqr vs. desmearing iteration",
width=400,
height=300,
)
[docs]class DesmearingGui(HasTraits):
'''Provide interactive access to all the parameters used in desmearing
This is a main GUI for the Lake/Jemian small-angle scattering desmearing program.
It uses Traits, Chaco, and Enable.
Call it with code like this::
DesmearingGui().configure_traits()
'''
infile = File(label="I(Q) file", desc="the name of the input smeared SAS data file", )
sas_plot = Instance(Plot)
residuals_plot = Instance(Plot)
sas_renderer = Instance(ScatterPlot)
residuals_renderer = Instance(ScatterPlot)
status_label = String('status:')
status_msg = String
obj_dsm = Instance( jldesmear.api.desmear.Desmearing )
btnRestartDsm = Button("(re)start")
btnDesmear = Button("N times")
btnDesmearOnce = Button("once")
btnClearConsole = Button("clear console")
console_text = String
chiSqr_plot = Instance(ChiSqr_plot)
l_o = Float(label="slit length", desc="slit length, as defined by Lake", )
qFinal = Float(label="qFinal", desc="fit extrapolation constants for Q>=qFinal",)
NumItr = Range(1,1000, 10, label="# iterations", desc="number of desmearing iterations",)
from jldesmear.api.extrapolation import discover_extrapolations
extrap_keys = sorted(discover_extrapolations().keys())
extrapolation = Enum(
*extrap_keys,
label="Extrapolation",
desc="form of extrapolation")
LakeWeighting = Enum(
'constant', 'fast', 'ChiSqr',
label="weighting",
desc="weighting method for iterative refinement",)
sas_plot_item = Item('sas_plot', editor=ComponentEditor(), show_label=False)
residuals_plot_item = Item('residuals_plot', editor=ComponentEditor(), show_label=False)
console_item = Item(
"console_text",
springy=True,
style='custom',
show_label=False,
)
traits_view = View(
HSplit(
Group(
Group(
Item('infile', show_label=False),
label="input data file name",
show_border = True,
),
Group(
Item('l_o'),
Item('qFinal'),
Item('NumItr'),
Item('extrapolation'),
Item('LakeWeighting'),
label="adjustable parameters",
show_border = True,
),
HGroup(
Item('btnDesmear', show_label=False),
Item('btnDesmearOnce', show_label=False),
Item('btnRestartDsm', show_label=False),
spring,
Item('chiSqr_plot', show_label=False),
label="desmearing controls",
show_border = True,
),
Group(
console_item,
Item('btnClearConsole', show_label=False),
label="console output",
show_border = True,
),
),
VSplit(
sas_plot_item,
residuals_plot_item,
),
),
resizable = True,
title="Lake/Jemian Desmearing GUI",
width=700,
height=500,
menubar = StandardMenuBar,
statusbar = [
StatusItem(name = 'status_label', width = 80),
StatusItem(name = 'status_msg', width = 0.5),
],
buttons=NoButtons,
)
def __init__(self):
super(DesmearingGui, self).__init__()
self.sas_plot, self.sas_renderer = self._init_plot("goldenrod")
self.residuals_plot, self.residuals_renderer = self._init_plot("silver")
if self.chiSqr_plot == None:
self.chiSqr_plot = ChiSqr_plot()
def _infile_default(self):
path = os.path.dirname(__file__)
testfilename = os.path.join(path, '..', 'data', 'test1.smr')
return os.path.abspath(testfilename)
def _l_o_default(self): return 0.08
def _qFinal_default(self): return 0.08
def _NumItr_default(self): return 10
def _extrapolation_default(self): return 'linear'
def _LakeWeighting_default(self): return 'fast'
def _console_text_default(self): return ''
def _init_plot(self, color = "blue"):
'''common construction of a plot
:return: tuple of objects of plot and its renderer
:rtype: (object, object)
'''
plot = Plot( ArrayPlotData(x = [], y = []) )
plot.tools.append(PanTool(plot, drag_button="right"))
plot.overlays.append(ZoomTool(plot))
r = plot.plot(
("x", "y"),
type="scatter",
color=color,
marker='circle',
marker_size=3
)
renderer = r[0] # 1st item in the list is our scatter plot
return plot, renderer
def _infile_changed(self):
if os.path.exists(self.infile):
Qvec, smr, esd = jldesmear.api.toolbox.GetDat(self.infile)
p = self.sas_plot
d = p.data
d.set_data("x", Qvec)
d.set_data("y", smr)
p.index_scale = 'log'
p.value_scale = 'log'
# TODO: append dsm and resmeared data curves
# TODO: what about error bars?
p = self.residuals_plot
p.data.set_data("x", Qvec)
p.index_scale = 'log'
p.value_scale = 'linear'
self.SetStatus("read %d points from %s" % (len(Qvec), self.infile) )
self.setupDesmearing(Qvec, smr, esd)
else:
self.SetStatus("could not find " + self.infile)
def _status_msg_changed(self):
txt = ""
if len(self.console_text):
txt = self.console_text + "\n"
txt += self.status_msg
self.console_text = txt # assign only once to avoid excess Traits updates
def _btnClearConsole_fired(self):
''' clear the console widget '''
self.console_text = ""
self.SetStatus('console cleared')
def _btnRestartDsm_fired(self):
self.SetStatus('desmearing reset')
self._infile_changed()
def _btnDesmear_fired(self):
self.SetStatus('desmearing %d iterations' % self.NumItr)
if self.obj_dsm:
self.toInfo(self.obj_dsm.params)
IterativeDesmear(self.obj_dsm, self.NumItr).start()
def _btnDesmearOnce_fired(self):
self.SetStatus('desmearing one iteration')
if self.obj_dsm:
self.toInfo(self.obj_dsm.params)
IterativeDesmear(self.obj_dsm, 1).start()
[docs] def SetStatus(self, msg):
''' put text in the status box '''
self.status_msg = msg
[docs] def setupDesmearing(self, Qvec, smr, esd):
''' prepare to start desmearing '''
if len(Qvec) == 0:
self.SetStatus("cannot desmear now, no data")
return
if len(Qvec) != len(smr):
self.SetStatus("cannot desmear now, number of data points inconsistent")
return
if len(Qvec) != len(esd):
self.SetStatus("cannot desmear now, number of data points inconsistent")
return
if self.qFinal > Qvec[-2]:
self.SetStatus("cannot desmear now, fit range beyond data range")
return
params = jldesmear.api.info.Info()
if params == None:
raise Exception, "Could not create Info() structure ... serious!"
self.toInfo(params)
self.obj_dsm = jldesmear.api.desmear.Desmearing(Qvec, smr, esd, params)
self.dsm_callback(self.obj_dsm)
[docs] def toInfo(self, params):
''' copy local variables to Info() structure
:param params: desmearing parameters structure
:type params: Info object
'''
params.infile = self.infile
#params.outfile = self.outfile
params.slitlength = self.l_o
params.sFinal = self.qFinal
params.NumItr = self.NumItr
params.extrapname = self.extrapolation
params.LakeWeighting = self.LakeWeighting
params.callback = self.dsm_callback
[docs] def dsm_callback(self, dsm):
'''
this function is called after every desmearing iteration
from :func:`~jldesmear.api.desmear.Desmearing.traditional()`
:param obj dsm: desmearing parameters object
:return: should desmearing stop?
:rtype: bool
'''
msg = "#" + str(dsm.iteration_count)
msg += " ChiSqr=" + str(dsm.ChiSqr[-1])
msg += " " + str(dsm.params.extrap)
# TODO: update the SAS plot
d = self.residuals_plot.data
d.set_data("x", dsm.q)
d.set_data("y", dsm.z)
self.chiSqr_plot.SetData(dsm.ChiSqr)
self.SetStatus( msg )
[docs]class IterativeDesmear(threading.Thread):
'''
Run ``n`` iterations of the desmearing operation in a separate thread.
Running in a separate thread with callbacks allows the
GUI widgets to be updated after each iteration.
:param obj dsm: Desmearing object
:param int n: number of iterations to perform
Start this thread with code such as this example::
IterativeDesmear(self.obj_dsm, self.NumItr).start()
'''
def __init__(self, dsm, n):
threading.Thread.__init__(self)
self.dsm = dsm
self.n = n
def run(self):
for _ in range(self.n):
self.dsm.iterate_and_callback()
def main():
DesmearingGui().configure_traits()
if __name__ == "__main__":
main()