einarr
89636962a7
data file, or None if the file does not exist. This is tested in the test workflow.
461 lines
14 KiB
Python
461 lines
14 KiB
Python
import gtk, gobject
|
|
import sys
|
|
import os
|
|
import inspect
|
|
import logger
|
|
import fluents
|
|
|
|
def _workflow_classes(modname):
|
|
"""Returns a list of all subclasses of Workflow in a given module"""
|
|
workflow_classes = []
|
|
|
|
__import__('workflows.%s' % modname)
|
|
module = sys.modules['workflows.%s' % modname]
|
|
|
|
d = module.__dict__
|
|
for wf in d.values():
|
|
try:
|
|
if issubclass(wf, Workflow):
|
|
workflow_classes.append(wf)
|
|
except TypeError, e:
|
|
pass
|
|
return workflow_classes
|
|
|
|
def workflow_list():
|
|
"""Returns a list containing all new workflows"""
|
|
retval = []
|
|
|
|
# List all .py files that can contain workflow classes
|
|
wf_path = sys.modules['workflows'].__path__
|
|
wf_files = []
|
|
|
|
for dir in wf_path:
|
|
for fn in os.listdir(dir):
|
|
if fn.endswith('.py') and ('#' not in fn):
|
|
wf_files.append(fn[:-3])
|
|
|
|
# Try to load each file and look for Workflow derived classes
|
|
for fn in wf_files:
|
|
try:
|
|
for wf in _workflow_classes(fn):
|
|
retval.append(wf)
|
|
except Exception, e:
|
|
logger.log('warning', 'Cannot load workflow: %s' % fn)
|
|
logger.log('warning', e)
|
|
|
|
return retval
|
|
|
|
|
|
class Workflow:
|
|
"""Defines a workflow that contains a set of analysis stages.
|
|
|
|
A Workflow is a set of analysis stages for a certain type of analysis.
|
|
Each stage contains some possible operations to do accomplish that
|
|
task.
|
|
"""
|
|
|
|
name = "Workflow"
|
|
ident = None
|
|
description = "Workflow Description"
|
|
|
|
def __init__(self, app):
|
|
self.stages = []
|
|
self.stages_by_id = {}
|
|
self.app = app
|
|
|
|
def get_data_file_name(self, filename):
|
|
"""Checks if a file with the given name exists in the data directory.
|
|
Returns the file name if the file exists in the data directory, which
|
|
is defined as datadir/workflowname. If the file does not exist, or the
|
|
workflow does not have an identificator, this method returns None."""
|
|
print os.path.join(self.app.options.datadir, self.ident, filename)
|
|
if self.ident == None:
|
|
return None
|
|
fn = os.path.join(self.app.options.datadir, self.ident, filename)
|
|
if os.path.isfile(fn):
|
|
return fn
|
|
return None
|
|
|
|
def add_stage(self, stage):
|
|
self.stages.append(stage)
|
|
self.stages_by_id[stage.id] = stage
|
|
|
|
def print_tree(self):
|
|
print self.name
|
|
for stage in self.stages:
|
|
print ' %s' % stage.name
|
|
for fun in stage.functions:
|
|
print ' %s' % fun.name
|
|
|
|
def add_project(self,project):
|
|
if project == None:
|
|
logger.log('notice','Proejct is empty')
|
|
logger.log('notice','Project added in : %s' %self.name)
|
|
self.project = project
|
|
|
|
|
|
class EmptyWorkflow(Workflow):
|
|
name = 'Empty Workflow'
|
|
|
|
def __init__(self, app):
|
|
Workflow.__init__(self, None)
|
|
|
|
|
|
class Stage:
|
|
"""A stage is a part of the data analysis process.
|
|
|
|
Each stage contains a set of functions that can be used to
|
|
accomplish the task. A typical early stage is 'preprocessing', which
|
|
can be done in several ways, each represented by a function.
|
|
"""
|
|
|
|
def __init__(self, id, name):
|
|
self.id = id
|
|
self.name = name
|
|
self.functions = []
|
|
self.functions_by_id = {}
|
|
|
|
def add_function(self, fun):
|
|
self.functions.append(fun)
|
|
self.functions_by_id[fun.id] = fun
|
|
|
|
|
|
class Function:
|
|
"""A Function object encapsulates a function on a data set.
|
|
|
|
Each Function instance encapsulates some function that can be applied
|
|
to one or more types of data.
|
|
"""
|
|
|
|
def __init__(self, id, name):
|
|
self.id = id
|
|
self.name = name
|
|
|
|
# just return a Validation object
|
|
def validate_input(input):
|
|
return Validation(True,"Validation Not Implemented")
|
|
|
|
def run(self):
|
|
pass
|
|
|
|
|
|
class Validation:
|
|
def __init__(self,result, reason):
|
|
self.succeeded = result
|
|
self.reason = reason
|
|
|
|
|
|
class WorkflowView (gtk.VBox):
|
|
|
|
def __init__(self, wf):
|
|
gtk.VBox.__init__(self)
|
|
self.workflow = wf
|
|
self.setup_workflow(wf)
|
|
|
|
def setup_workflow(self, wf):
|
|
# Add stage in the process
|
|
for stage in wf.stages:
|
|
exp = gtk.Expander(stage.name)
|
|
btn_align = gtk.Alignment(xscale=0.9)
|
|
btn_align.set_padding(0,4,20,0)
|
|
btn_align.show()
|
|
btn_box = gtk.VBox()
|
|
btn_align.add(btn_box)
|
|
btn_box.show()
|
|
exp.add(btn_align)
|
|
|
|
# Add functions in each stage
|
|
for fun in stage.functions:
|
|
btn = gtk.Button(fun.name)
|
|
btn.connect('clicked',
|
|
lambda button, f=fun : self.run_function(f))
|
|
|
|
btn_box.add(btn)
|
|
btn.show()
|
|
|
|
exp.show()
|
|
self.pack_start(exp, expand=False, fill=False)
|
|
|
|
def remove_workflow(self):
|
|
for c in self.get_children():
|
|
c.hide()
|
|
self.remove(c)
|
|
|
|
def set_workflow(self, workflow):
|
|
self.workflow = workflow
|
|
self.remove_workflow()
|
|
self.setup_workflow(workflow)
|
|
|
|
def run_function(self, function):
|
|
logger.log('debug', 'Starting function: %s' % function.name)
|
|
project = self.workflow.app.project
|
|
parent_data = project.current_data
|
|
|
|
validation = function.validate_input()
|
|
|
|
if not validation.succeeded:
|
|
logger.log('warning','Invalid Inputdata: ' + str(reason))
|
|
return
|
|
|
|
args, varargs, varkw, defaults = inspect.getargspec(function.run)
|
|
|
|
# first argument is 'self' and second should be the selection
|
|
# and we don't care about those...
|
|
args.remove('self')
|
|
if "selection" in args:
|
|
pass_selection = True
|
|
args.remove('selection')
|
|
else:
|
|
pass_selection = False
|
|
|
|
if varargs and len(parent_data) < len(args):
|
|
logger.log('warning', "Function requires minimum %d datasets selected." % len(args))
|
|
return
|
|
elif not varargs and args and len(args) != len(parent_data):
|
|
# functions requiring datasets have to have the right number
|
|
logger.log('warning', "Function requires %d datasets, but only %d selected." % (len(args), len(parent_data)))
|
|
return
|
|
|
|
if not args:
|
|
# we allow functions requiring no data to be run even if a
|
|
# dataset is is selected
|
|
data = []
|
|
else:
|
|
data = parent_data
|
|
|
|
if pass_selection:
|
|
# if the function has a 'selection' argument, we pass in
|
|
# the selection
|
|
new_data = function.run(selection=project.get_selection(), *data)
|
|
else:
|
|
new_data = function.run(*data)
|
|
|
|
if new_data != None:
|
|
project.add_data(parent_data, new_data, function.name)
|
|
|
|
logger.log('debug', 'Function ended: %s' % function.name)
|
|
|
|
|
|
class Options(dict):
|
|
"""Options base class.
|
|
"""
|
|
def __init__(self, *args,**kw):
|
|
dict.__init__(self, *args, **kw)
|
|
self['out_plots'] = None
|
|
self['out_data'] = None
|
|
|
|
def _copy_from_list(self, key_list):
|
|
"""Returns suboptions (dictionary) from a list of keys.
|
|
"""
|
|
d = {}
|
|
for key in key_list:
|
|
d[key] = self.get(key, None)
|
|
return d
|
|
|
|
|
|
class OptionsDialog(gtk.Dialog):
|
|
"""The basic input/output dialog box.
|
|
|
|
This defines the first page of the function options-gui.
|
|
Any function that invokes a option-gui will inherit from this class.
|
|
"""
|
|
def __init__(self, data, options, input_names=['X','Y']):
|
|
gtk.Dialog.__init__(self, 'Input-Output dialog',
|
|
None,
|
|
gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
|
|
self._options = options
|
|
self._data = data
|
|
self._editable = True
|
|
self.set_size_request(550,450)
|
|
|
|
# create notebook
|
|
self.nb = nb = gtk.Notebook()
|
|
|
|
# 1. page: input/output
|
|
|
|
#inputs
|
|
input_frame = gtk.Frame("Input")
|
|
hbox = gtk.HBox(True, 8)
|
|
align = gtk.Alignment(1, 1, 1, 1)
|
|
align.set_padding(8, 8, 8, 8)
|
|
align.add(hbox)
|
|
input_frame.add(align)
|
|
for i, name in enumerate(input_names):
|
|
frame = gtk.Frame(name)
|
|
frame.set_label_align(0.5, 0.5)
|
|
label = gtk.Label(data[i]._name + "\n" + str(data[i]._array.shape))
|
|
frame.add(label)
|
|
hbox.add(frame)
|
|
|
|
#outputs
|
|
output_frame = gtk.Frame("Output")
|
|
output_hbox = gtk.HBox(True,4)
|
|
output_align = gtk.Alignment(1, 1, 1, 1)
|
|
output_align.set_padding(8, 8, 8, 8) #left padding:8
|
|
output_align.add(output_hbox)
|
|
output_frame.add(output_align)
|
|
|
|
# plots
|
|
plot_list = gtk.ListStore(str, 'gboolean', gtk.gdk.Pixbuf)
|
|
plot_treeview = gtk.TreeView(plot_list)
|
|
|
|
# Add plots
|
|
plot_icon = fluents.icon_factory.get('line_plot')
|
|
for plt, name, use in self._options['all_plots']:
|
|
plot_list.append((name, use, plot_icon))
|
|
|
|
# Renderer for icon
|
|
plot_icon = fluents.icon_factory.get('line_plot')
|
|
icon_renderer = gtk.CellRendererPixbuf()
|
|
icon_renderer.set_property('pixbuf', plot_icon)
|
|
|
|
# Renderer for active toggle.
|
|
active_renderer = gtk.CellRendererToggle()
|
|
active_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
|
|
active_renderer.connect('toggled', toggled, plot_list)
|
|
active_column = gtk.TreeViewColumn('Use', active_renderer, active=1)
|
|
|
|
# Renderer for plot title.
|
|
title_renderer = gtk.CellRendererText()
|
|
title_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
|
|
title_column = gtk.TreeViewColumn('Plot', title_renderer, text=0)
|
|
title_column.pack_start(icon_renderer, expand=False)
|
|
|
|
# Add columns to tree view.
|
|
plot_treeview.append_column(active_column)
|
|
plot_treeview.append_column(title_column)
|
|
|
|
## datasets
|
|
dataset_list = gtk.ListStore(str, 'gboolean', gtk.gdk.Pixbuf)
|
|
dataset_treeview = gtk.TreeView(dataset_list)
|
|
|
|
# Add datasets
|
|
data_icon = fluents.icon_factory.get('dataset')
|
|
for dat, name, use in self._options['all_data']:
|
|
dataset_list.append((name, use, data_icon))
|
|
|
|
# Renderer for icon
|
|
icon_renderer = gtk.CellRendererPixbuf()
|
|
icon_renderer.set_property('pixbuf', data_icon)
|
|
|
|
# Renderer for active toggle.
|
|
active_renderer = gtk.CellRendererToggle()
|
|
active_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
|
|
active_renderer.connect('toggled', toggled, dataset_list)
|
|
active_column = gtk.TreeViewColumn('Use', active_renderer, active=1)
|
|
|
|
# Renderer for dataset title.
|
|
title_renderer = gtk.CellRendererText()
|
|
title_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
|
|
title_column = gtk.TreeViewColumn('Dataset', title_renderer, text=0)
|
|
title_column.pack_start(icon_renderer, expand=False)
|
|
|
|
# Add columns to tree view.
|
|
dataset_treeview.append_column(active_column)
|
|
dataset_treeview.append_column(title_column)
|
|
|
|
# add treeviews to output frame
|
|
output_hbox.add(plot_treeview)
|
|
output_hbox.add(dataset_treeview)
|
|
|
|
# vbox for input/spacer/output
|
|
vbox1 = gtk.VBox()
|
|
vbox1.add(input_frame)
|
|
vbox1.add(gtk.HSeparator())
|
|
vbox1.add(output_frame)
|
|
|
|
# add vbox to notebook
|
|
nb.insert_page(vbox1, gtk.Label("Input/Output"), 0)
|
|
self.vbox.add(nb)
|
|
|
|
#keep ref to liststores
|
|
self.dataset_list = dataset_list
|
|
self.plot_list = plot_list
|
|
|
|
def run(self):
|
|
self.vbox.show_all()
|
|
return gtk.Dialog.run(self)
|
|
|
|
def set_options(self, options):
|
|
self._options = options
|
|
|
|
def update_options(self, options):
|
|
self._options.update(options)
|
|
|
|
def set_output(self):
|
|
# get toggled output data
|
|
out_data = [item[0] for name, mark, ic in self.dataset_list for item in self._options['all_data'] if mark==True and name==item[1]]
|
|
# get toggled plots
|
|
out_plots = [item[0] for name, mark, ic in self.plot_list for item in self._options['all_plots'] if mark==True and name==item[1]]
|
|
# update options
|
|
self._options['out_data'] = out_data
|
|
self._options['out_plots'] = out_plots
|
|
|
|
def set_editable(self, editable):
|
|
self._editable = True
|
|
|
|
def set_data(self, data):
|
|
self._data = data
|
|
|
|
def get_data(self):
|
|
return self._data
|
|
|
|
def get_options(self):
|
|
return self._options
|
|
|
|
def add_page_from_glade(self, glade_file, widget_name, page_title):
|
|
"""Adds a new page(s) to the existing notebook.
|
|
The input widget (added as a page in notebook) is defined
|
|
in the glade file.
|
|
|
|
input:
|
|
glade_file -- path to glade file
|
|
widget_name -- name of widget from glade file
|
|
"""
|
|
|
|
try:
|
|
self.wTree = gtk.glade.XML(glade_file)
|
|
except:
|
|
logger.log('notice', 'Could not find glade file: %s' %glade_file)
|
|
|
|
widget = self.wTree.get_widget(widget_name)
|
|
win = widget.get_parent()
|
|
win.hide()
|
|
widget.unparent()
|
|
self.nb.insert_page(widget, gtk.Label(page_title), -1)
|
|
self.nb.set_current_page(0)
|
|
|
|
|
|
def toggled(renderer, path, store):
|
|
it = store.get_iter(path)
|
|
old_value = store.get_value(it, 1)
|
|
store.set_value(it, 1, not old_value)
|
|
|
|
|
|
class WorkflowMenu (gtk.Menu):
|
|
|
|
def __init__(self, workflow):
|
|
gtk.Menu.__init__(self)
|
|
self._workflow = workflow
|
|
for stage in workflow.stages:
|
|
self.append(self._create_stage_item(stage))
|
|
|
|
def _create_stage_item(self, stage):
|
|
stage_menu_item = gtk.MenuItem(stage.name)
|
|
stage_menu_item.show()
|
|
stage_menu = gtk.Menu()
|
|
stage_menu_item.set_submenu(stage_menu)
|
|
|
|
for fun in stage.functions:
|
|
stage_menu.append(self._create_function_item(fun))
|
|
return stage_menu_item
|
|
|
|
def _create_function_item(self, function):
|
|
menuitem = gtk.MenuItem(function.name)
|
|
menuitem.show()
|
|
return menuitem
|
|
|