class FlowTask(Task): """ classdocs """ id = "edu.mit.synbio.cytoflow.flow_task" name = "Cytometry analysis" # the main workflow instance. # THIS IS WHERE IT'S INITIALLY INSTANTIATED (note the args=()) model = Instance(Workflow, args=()) # the center pane view = Instance(FlowTaskPane) # plugin lists, to setup the interface op_plugins = List(IOperationPlugin) view_plugins = List(IViewPlugin) menu_bar = SMenuBar( SMenu( TaskAction(name='Open...', method='on_open', accelerator='Ctrl+O'), TaskAction( name='Save', #image='save', method='on_save', accelerator='Ctrl+S'), TaskAction(name='Save As...', method='on_save_as', accelerator='Ctrl+e'), TaskAction(name='Export image...', method='on_export', accelerator='Ctrl+x'), TaskAction(name='Export IPython notebook...', method='on_ipython', accelerator='Ctrl+I'), TaskAction(name='Preferences...', method='on_prefs', accelerator='Ctrl+P'), id='File', name='&File'), SMenu(TaskAction(name='About...', method='on_about', accelerator="Ctrl+A"), id="Help", name="&Help")) tool_bars = [ SToolBar(TaskAction(method='on_new', name="New", tooltip='New workflow', image=ImageResource('new')), TaskAction(method='on_open', name="Open", tooltip='Open a file', image=ImageResource('open')), TaskAction(method='on_save', name="Save", tooltip='Save the current file', image=ImageResource('save')), TaskAction(method='on_export', name="Export", tooltip='Export the current plot', image=ImageResource('export')), TaskAction(method='on_ipython', name='IPython', tooltip="Export to an IPython notebook...", image=ImageResource('ipython')), TaskAction(method='on_prefs', name="Prefs", tooltip='Preferences', image=ImageResource('prefs')), image_size=(32, 32)) ] # are we debugging? ie, do we need a default setup? debug = Bool worker = Instance(threading.Thread) to_update = Instance(UniquePriorityQueue, ()) worker_flag = Instance(threading.Event, args=()) worker_lock = Instance(threading.Lock, args=()) def initialized(self): # make sure that when the result changes we get notified # can't use a static notifier because selected.result gets updated # on the worker thread, but we need to dispatch on the UI thread self.model.on_trait_change(self._result_updated, "selected:result", dispatch='ui') def activated(self): # add an import plugin plugin = ImportPlugin() wi = WorkflowItem(task=self) wi.operation = plugin.get_operation() self.model.workflow.append(wi) self.model.selected = wi # if we're debugging, add a few data bits if self.debug: from cytoflow import Tube wi.operation.conditions["Dox"] = "log" tube1 = Tube(file="../cytoflow/tests/data/Plate01/CFP_Well_A4.fcs", conditions={"Dox": 0.1}) tube2 = Tube(file="../cytoflow/tests/data/Plate01/RFP_Well_A3.fcs", conditions={"Dox": 1.0}) wi.operation.tubes.append(tube1) wi.operation.tubes.append(tube2) self.add_operation( 'edu.mit.synbio.cytoflowgui.op_plugins.threshold') self.model.selected.operation.channel = "Y2-A" self.model.selected.operation.threshold = 2000 self.model.selected.operation.name = "T" def prepare_destroy(self): self.model = None def _default_layout_default(self): return TaskLayout(left=PaneItem("edu.mit.synbio.workflow_pane"), right=PaneItem("edu.mit.synbio.view_traits_pane")) def create_central_pane(self): self.view = FlowTaskPane(model=self.model) return self.view def create_dock_panes(self): return [ WorkflowDockPane(model=self.model, plugins=self.op_plugins, task=self), ViewDockPane(model=self.model, plugins=self.view_plugins, task=self) ] def on_new(self): self.model.workflow = [] # add an import plugin plugin = ImportPlugin() wi = WorkflowItem(task=self) wi.operation = plugin.get_operation() self.model.workflow.append(wi) self.model.selected = wi def on_open(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, action='open', wildcard='*.flow') if dialog.open() == OK: self.open_file(dialog.path) def open_file(self, path): f = open(path, 'r') unpickler = pickle.Unpickler(f) new_model = unpickler.load() # update the link back to the controller (ie, self) for wi in new_model.workflow: wi.task = self # and set up the view handlers for view in wi.views: view.handler = view.handler_factory(model=view, wi=wi) # replace the current workflow with the one we just loaded if False: from event_tracer import record_events with record_events() as container: self.model.workflow[:] = new_model.workflow self.model.selected = new_model.selected container.save_to_directory(os.getcwd()) else: self.model.workflow[:] = new_model.workflow self.model.selected = new_model.selected wi = self.model.workflow[0] while True: wi.status = "invalid" with self.worker_lock: self.to_update.put_nowait((self.model.workflow.index(wi), wi)) if wi.next: wi = wi.next else: break # check to see if we have a worker thread around if not self.worker or not self.worker.is_alive(): self.worker = threading.Thread(target=update_model, args=(self.worker_flag, self.worker_lock, self.to_update)) self.worker.daemon = True self.worker.start() # start the worker thread processing with self.worker_lock: if not self.to_update.empty(): self.worker_flag.set() def on_save(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, action='save as', wildcard='*.flow') if dialog.open() == OK: self.save_file(dialog.path) def on_save_as(self): pass def save_file(self, path): # TODO - error handling f = open(path, 'w') pickler = pickle.Pickler(f, 0) # text protocol for now pickler.dump(self.model) def on_export(self): """ Shows a dialog to export a file """ dialog = FileDialog(parent=self.window.control, action='save as') if dialog.open() == OK: self.view.export(dialog.path) def on_ipython(self): """ Shows a dialog to export the workflow to an IPython notebook """ dialog = FileDialog(parent=self.window.control, action='save as', wildcard='*.ipynb') if dialog.open() == OK: writer = IPythonNotebookWriter(file=dialog.path) writer.export(self.workflow) def on_prefs(self): pass def on_about(self): from cytoflow import __version__ as cf_version from fcsparser import __version__ as fcs_version from pandas.version import __version__ as pd_version from numpy.version import version as np_version from numexpr import __version__ as numexp_version from seaborn import __version__ as sns_version from matplotlib import __version__ as mpl_version from pyface import __version__ as py_version from envisage import __version__ as env_version from traits import __version__ as trt_version from traitsui import __version__ as trt_ui_version text = [ "<b>Cytoflow {0}</b>".format(cf_version), "<p>", "fcsversion {0}".format(fcs_version), "pandas {0}".format(pd_version), "numpy {0}".format(np_version), "numexpr {0}".format(numexp_version), "seaborn {0}".format(sns_version), "matplotlib {0}".format(mpl_version), "pyface {0}".format(py_version), "envisage {0}".format(env_version), "traits {0}".format(trt_version), "traitsui {0}".format(trt_ui_version), "Icons from the <a href=http://tango.freedesktop.org>Tango Desktop Project</a>", "<a href=https://thenounproject.com/search/?q=setup&i=14287>Settings icon</a> by Paulo Sa Ferreira from <a href=https://thenounproject.com>The Noun Project</a>", "Cuvette image from Wikimedia Commons user <a href=http://commons.wikimedia.org/wiki/File:Hellma_Large_cone_cytometry_cell.JPG>HellmaUSA</a>" ] dialog = AboutDialog(parent=self.window.control, title="About", image=ImageResource('cuvette'), additions=text) dialog.open() def add_operation(self, op_id): # first, find the matching plugin plugin = next((x for x in self.op_plugins if x.id == op_id)) # default to inserting at the end of the list if none selected after = self.model.selected if after is None: after = self.model.workflow[-1] idx = self.model.workflow.index(after) wi = WorkflowItem(task=self) wi.operation = plugin.get_operation() wi.next = after.next after.next = wi wi.previous = after if wi.next: wi.next.previous = wi self.model.workflow.insert(idx + 1, wi) # set up the default view wi.default_view = plugin.get_default_view() if wi.default_view is not None: wi.default_view.op = wi.operation wi.default_view.handler = \ wi.default_view.handler_factory(model = wi.default_view, wi = wi.previous) wi.views.append(wi.default_view) # select (open) the new workflow item self.model.selected = wi if wi.default_view: wi.current_view = wi.default_view # invalidate everything following self.operation_parameters_updated() @on_trait_change("model:workflow[]") def _on_remove_operation(self, obj, name, old, new): if name == "workflow_items" and len(new) == 0 and len(old) > 0: assert len(old) == 1 wi = old[0] if self.model.selected == wi: self.model.selected = wi.previous wi.previous.next = wi.next if wi.next: wi.next.previous = wi.previous del wi.default_view del wi.views del wi self.operation_parameters_updated() @on_trait_change("model:selected:operation:+") def operation_parameters_updated(self): # invalidate this workflow item and all the ones following it wi = self.model.selected while True: wi.status = "invalid" with self.worker_lock: self.to_update.put_nowait((self.model.workflow.index(wi), wi)) if wi.next: wi = wi.next else: break # check to see if we have a worker thread around if not self.worker or not self.worker.is_alive(): self.worker = threading.Thread(target=update_model, args=(self.worker_flag, self.worker_lock, self.to_update)) self.worker.daemon = True self.worker.start() # start the worker thread processing with self.worker_lock: if not self.to_update.empty(): self.worker_flag.set() def set_current_view(self, view_id): """ called by the view pane """ wi = self.model.selected if view_id == "default": view_id = self.model.selected.default_view.id view = next((x for x in wi.views if x.id == view_id), None) if not view: plugin = next( (x for x in self.view_plugins if x.view_id == view_id)) view = plugin.get_view() view.handler = view.handler_factory(model=view, wi=wi) wi.views.append(view) wi.current_view = view @on_trait_change("model:selected.current_view") def _current_view_changed(self, obj, name, old, new): # we get notified if *either* the currently selected workflowitem # *or* the current view changes. if name == 'selected': new = new.current_view if new else None old = old.current_view if old else None # remove the notifications from the old view if old: old.on_trait_change(self.view_parameters_updated, remove=True) # and if the old view was interactive, turn off its interactivity # to remove the matplotlib event handlers if "interactive" in old.traits(): old.interactive = False # whenever the view parameters change, we need to know so we can # update the plot(s) if new: new.on_trait_change(self.view_parameters_updated) if self.model.selected: self.view.plot(self.model.selected) else: self.view.clear_plot() else: self.view.clear_plot() def _result_updated(self, obj, name, old, new): print "result updated" if self.model.selected: self.view.plot(self.model.selected) else: self.view.clear_plot() def view_parameters_updated(self, obj, name, new): # i should be able to specify the metadata i want in the listener, # but there's an odd interaction (bug) between metadata, dynamic # trait listeners and instance traits. so, check for 'transient' # here instead, if obj.trait(name).transient: return print "view parameters updated: {0}".format(name) wi = self.model.selected if wi is None: wi = self.model.workflow[-1] self.view.plot(wi)
class FlowTask(Task): """ classdocs """ id = "edu.mit.synbio.cytoflow.flow_task" name = "Cytometry analysis" # the main workflow instance. model = Instance(Workflow) # the center pane plot_pane = Instance(FlowTaskPane) workflow_pane = Instance(WorkflowDockPane) view_pane = Instance(ViewDockPane) # plugin lists, to setup the interface op_plugins = List(IOperationPlugin) view_plugins = List(IViewPlugin) menu_bar = SMenuBar( SMenu( TaskAction(name='Open...', method='on_open', accelerator='Ctrl+O'), TaskAction( name='Save', #image='save', method='on_save', accelerator='Ctrl+S'), TaskAction(name='Save As...', method='on_save_as', accelerator='Ctrl+e'), TaskAction(name='Save Plot...', method='on_export', accelerator='Ctrl+x'), # TaskAction(name='Export Jupyter notebook...', # method='on_notebook', # accelerator='Ctrl+I'), # TaskAction(name='Preferences...', # method='on_prefs', # accelerator='Ctrl+P'), id='File', name='&File'), SMenu(id='View', name='&View'), SMenu(TaskAction(name='Report a problem....', method='on_problem'), TaskAction(name='About...', method='on_about'), id="Help", name="&Help")) tool_bars = [ SToolBar( TaskAction(method='on_new', name="New", tooltip='New workflow', image=ImageResource('new')), TaskAction(method='on_open', name="Open", tooltip='Open a file', image=ImageResource('open')), TaskAction(method='on_save', name="Save", tooltip='Save the current file', image=ImageResource('save')), TaskAction(method='on_export', name="Save Plot", tooltip='Save the current plot', image=ImageResource('export')), # TaskAction(method='on_notebook', # name='Notebook', # tooltip="Export to an Jupyter notebook...", # image=ImageResource('ipython')), # TaskAction(method='on_prefs', # name = "Prefs", # tooltip='Preferences', # image=ImageResource('prefs')), image_size=(32, 32)) ] # the file to save to if the user clicks "save" and has already clicked # "open" or "save as". filename = Unicode def prepare_destroy(self): self.model.shutdown_remote_process() def activated(self): # add the import op self.add_operation(ImportPlugin().id) self.model.selected = self.model.workflow[0] # if we're debugging, add a few data bits if self.model.debug: from cytoflow import Tube import_op = self.model.workflow[0].operation import_op.conditions = {"Dox": "float", "Well": "category"} # import_op.conditions["Dox"] = "float" # import_op.conditions["Well"] = "category" tube1 = Tube(file="../cytoflow/tests/data/Plate01/CFP_Well_A4.fcs", conditions={ "Dox": 0.0, "Well": 'A' }) tube2 = Tube(file="../cytoflow/tests/data/Plate01/RFP_Well_A3.fcs", conditions={ "Dox": 10.0, "Well": 'A' }) tube3 = Tube(file="../cytoflow/tests/data/Plate01/CFP_Well_B4.fcs", conditions={ "Dox": 0.0, "Well": 'B' }) tube4 = Tube(file="../cytoflow/tests/data/Plate01/RFP_Well_A6.fcs", conditions={ "Dox": 10.0, "Well": 'B' }) import_op.tubes = [tube1, tube2, tube3, tube4] # self.add_operation(ChannelStatisticPlugin().id) # stat_op = self.model.workflow[1].operation # stat_op.name = "Test" # stat_op.channel = "Y2-A" # stat_op.statistic_name = "Geom.Mean" # stat_op.by = ["Dox", "Well"] # self.model.selected = self.model.workflow[1] self.model.modified = False def _default_layout_default(self): return TaskLayout(left=PaneItem("edu.mit.synbio.workflow_pane"), right=PaneItem("edu.mit.synbio.view_traits_pane")) def create_central_pane(self): self.plot_pane = FlowTaskPane(model=self.model) return self.plot_pane def create_dock_panes(self): self.workflow_pane = WorkflowDockPane(model=self.model, plugins=self.op_plugins, task=self) self.view_pane = ViewDockPane(model=self.model, plugins=self.view_plugins, task=self) return [self.workflow_pane, self.view_pane] def on_new(self): if self.model.modified: ret = confirm( parent=None, message= "Are you sure you want to discard the current workflow?", title="Clear workflow?") if ret != YES: return self.filename = "" self.window.title = "Cytoflow" # clear the workflow self.model.workflow = [] # add the import op self.add_operation(ImportPlugin().id) # and select the operation self.model.selected = self.model.workflow[0] self.model.modified = False def on_open(self): """ Shows a dialog to open a file. """ if self.model.modified: ret = confirm( parent=None, message= "Are you sure you want to discard the current workflow?", title="Clear workflow?") if ret != YES: return dialog = FileDialog( parent=self.window.control, action='open', wildcard=( FileDialog.create_wildcard("Cytoflow workflow", "*.flow") + ';' + #@UndefinedVariable FileDialog.create_wildcard("All files", "*"))) #@UndefinedVariable if dialog.open() == OK: self.open_file(dialog.path) self.filename = dialog.path self.window.title = "Cytoflow - " + self.filename def open_file(self, path): f = open(path, 'r') unpickler = pickle.Unpickler(f) try: version = unpickler.load() except TraitError: error(parent=None, message="This doesn't look like a Cytoflow file. Or maybe " "you tried to load a workflow older than version " "0.5?") return if version != self.model.version: ret = confirm( parent=None, message="This is Cytoflow {}, but you're trying " "to load a workflow from version {}. This may or " "may not work! Are you sure you want to proceed?".format( self.model.version, version), title="Load workflow?") if ret != YES: return try: new_workflow = unpickler.load() except TraitError as e: error(parent=None, message="Error trying to load the workflow.") return # a few things to take care of when reloading for wi_idx, wi in enumerate(new_workflow): # get wi lock wi.lock.acquire() # clear the wi status wi.status = "loading" # re-link the linked list. i thought this would get taken care # of in deserialization, but i guess not... if wi_idx > 0: wi.previous_wi = new_workflow[wi_idx - 1] # replace the current workflow with the one we just loaded if False: # for debugging the loading of things from .event_tracer import record_events with record_events() as container: self.model.workflow = new_workflow container.save_to_directory(os.getcwd()) else: self.model.workflow = new_workflow self.model.modified = False for wi in self.model.workflow: wi.lock.release() def on_save(self): """ Save the file to the previous filename """ if self.filename: self.save_file(self.filename) else: self.on_save_as() def on_save_as(self): dialog = DefaultFileDialog( parent=self.window.control, action='save as', default_suffix="flow", wildcard=( FileDialog.create_wildcard("Cytoflow workflow", "*.flow") + ';' + #@UndefinedVariable FileDialog.create_wildcard("All files", "*"))) #@UndefinedVariable if dialog.open() == OK: self.save_file(dialog.path) self.filename = dialog.path self.window.title = "Cytoflow - " + self.filename pass def save_file(self, path): # TODO - error handling f = open(path, 'w') pickler = pickle.Pickler(f, 0) # text protocol for now pickler.dump(self.model.version) pickler.dump(self.model.workflow) self.model.modified = False @on_trait_change('model.modified', post_init=True) def _on_model_modified(self, val): if val: if not self.window.title.endswith("*"): self.window.title += "*" else: if self.window.title.endswith("*"): self.window.title = self.window.title[:-1] def on_export(self): """ Shows a dialog to export a file """ information( None, "This will save exactly what you see on the screen " "to a file.", "Export") f = "" filetypes_groups = self.plot_pane.canvas.get_supported_filetypes_grouped( ) filename_exts = [] for name, ext in filetypes_groups.items(): if f: f += ";" f += FileDialog.create_wildcard(name, " ".join(["*." + e for e in ext ])) #@UndefinedVariable filename_exts.append(ext) dialog = FileDialog(parent=self.window.control, action='save as', wildcard=f) if dialog.open() == OK: filetypes = list( self.plot_pane.canvas.get_supported_filetypes().keys()) if not [ ext for ext in ["." + ext for ext in filetypes] if dialog.path.endswith(ext) ]: selected_exts = filename_exts[dialog.wildcard_index] ext = sorted(selected_exts, key=len)[0] dialog.path += "." dialog.path += ext self.plot_pane.export(dialog.path) def on_notebook(self): """ Shows a dialog to export the workflow to an Jupyter notebook """ return dialog = FileDialog(parent=self.window.control, action='save as', wildcard='*.ipynb') if dialog.open() == OK: writer = JupyterNotebookWriter(file=dialog.path) writer.export(self.model.workflow) def on_prefs(self): pass def on_problem(self): information( None, "Your email client will now create a new message to the " "developer. Debugging logs are attached. Please fill " "out the template bug report and send -- thank you for " "reporting a bug!") log = self.application.application_log.getvalue() versions = [ "{0} {1}".format(key, value) for key, value in self._get_package_versions().items() ] body = """ Thank you for your bug report! Please fill out the following template. PLATFORM (Mac, PC, Linux, other): OPERATING SYSTEM (eg OSX 10.7, Windows 8.1): SEVERITY (Critical? Major? Minor? Enhancement?): DESCRIPTION: - What were you trying to do? - What happened? - What did you expect to happen? PACKAGE VERSIONS: {0} DEBUG LOG: {1} """.format(versions, log) # mailto.mailto("*****@*****.**", # subject = "Cytoflow bug report", # body = body) def _get_package_versions(self): import sys from cytoflow import __version__ as cf_version from fcsparser import __version__ as fcs_version from pandas import __version__ as pd_version from numpy import __version__ as np_version from numexpr import __version__ as nxp_version from bottleneck import __version__ as btl_version from seaborn import __version__ as sns_version from matplotlib import __version__ as mpl_version from scipy import __version__ as scipy_version from sklearn import __version__ as skl_version from pyface import __version__ as pyf_version from envisage import __version__ as env_version from traits import __version__ as trt_version from traitsui import __version__ as trt_ui_version return { "python": sys.version, "cytoflow": cf_version, "fcsparser": fcs_version, "pandas": pd_version, "numpy": np_version, "numexpr": nxp_version, "bottleneck": btl_version, "seaborn": sns_version, "matplotlib": mpl_version, "scipy": scipy_version, "scikit-learn": skl_version, "pyface": pyf_version, "envisage": env_version, "traits": trt_version, "traitsui": trt_ui_version } def on_about(self): versions = self._get_package_versions() text = ["<b>Cytoflow {0}</b>".format(versions['cytoflow']), "<p>"] ver_text = [ "{0} {1}".format(key, value) for key, value in versions.items() ] text.extend(ver_text) text.extend([ "Icons from the <a href=http://tango.freedesktop.org>Tango Desktop Project</a>", "<a href=https://thenounproject.com/search/?q=setup&i=14287>Settings icon</a> by Paulo Sa Ferreira from <a href=https://thenounproject.com>The Noun Project</a>", "<a href=http://www.freepik.com/free-photos-vectors/background>App icon from Starline - Freepik.com</a>", "Cuvette image from Wikimedia Commons user <a href=http://commons.wikimedia.org/wiki/File:Hellma_Large_cone_cytometry_cell.JPG>HellmaUSA</a>" ]) dialog = AboutDialog(text=text, parent=self.window.control, title="About", image=ImageResource('cuvette'), additions=text) dialog.open() @on_trait_change('model.selected', post_init=True) def _on_select_op(self, selected): if selected: self.view_pane.enabled = (selected is not None) self.view_pane.default_view = selected.default_view.id if selected.default_view else "" self.view_pane.selected_view = selected.current_view.id if selected.current_view else "" else: self.view_pane.enabled = False @on_trait_change('view_pane.selected_view', post_init=True) def _on_select_view(self, view_id): if not view_id: return # if we already have an instantiated view object, find it try: self.model.selected.current_view = next( (x for x in self.model.selected.views if x.id == view_id)) except StopIteration: # else make the new view plugin = next( (x for x in self.view_plugins if x.view_id == view_id)) view = plugin.get_view() self.model.selected.views.append(view) self.model.selected.current_view = view def add_operation(self, op_id): # first, find the matching plugin plugin = next((x for x in self.op_plugins if x.id == op_id)) # next, get an operation op = plugin.get_operation() # make a new workflow item wi = WorkflowItem( operation=op, deletable=(op_id != 'edu.mit.synbio.cytoflowgui.op_plugins.import')) # if the op has a default view, add it to the wi try: wi.default_view = op.default_view() wi.views.append(wi.default_view) wi.current_view = wi.default_view except AttributeError: pass # figure out where to add it if self.model.selected: idx = self.model.workflow.index(self.model.selected) + 1 else: idx = len(self.model.workflow) # the add_remove_items handler takes care of updating the linked list self.model.workflow.insert(idx, wi) # and make sure to actually select the new wi self.model.selected = wi
class FlowTask(Task): """ classdocs """ id = "edu.mit.synbio.cytoflow.flow_task" name = "Cytometry analysis" # the main workflow instance. # THIS IS WHERE IT'S INSTANTIATED (note the args=() ) model = Instance(LocalWorkflow, args = ()) # the center pane view = Instance(FlowTaskPane) # plugin lists, to setup the interface op_plugins = List(IOperationPlugin) view_plugins = List(IViewPlugin) menu_bar = SMenuBar(SMenu(TaskAction(name='Open...', method='on_open', accelerator='Ctrl+O'), TaskAction(name='Save', #image='save', method='on_save', accelerator='Ctrl+S'), TaskAction(name='Save As...', method='on_save_as', accelerator='Ctrl+e'), TaskAction(name='Save Plot...', method='on_export', accelerator='Ctrl+x'), TaskAction(name='Export Jupyter notebook...', method='on_notebook', accelerator='Ctrl+I'), TaskAction(name='Preferences...', method='on_prefs', accelerator='Ctrl+P'), id='File', name='&File'), SMenu(id = 'View', name = '&View'), SMenu(TaskAction(name = 'Report a problem....', method = 'on_problem'), TaskAction(name='About...', method='on_about'), id="Help", name ="&Help")) tool_bars = [ SToolBar(TaskAction(method='on_new', name = "New", tooltip='New workflow', image=ImageResource('new')), TaskAction(method='on_open', name = "Open", tooltip='Open a file', image=ImageResource('open')), TaskAction(method='on_save', name = "Save", tooltip='Save the current file', image=ImageResource('save')), TaskAction(method='on_export', name = "Save Plot", tooltip='Save the current plot', image=ImageResource('export')), TaskAction(method='on_notebook', name='Notebook', tooltip="Export to an Jupyter notebook...", image=ImageResource('ipython')), TaskAction(method='on_prefs', name = "Prefs", tooltip='Preferences', image=ImageResource('prefs'), enabled = False), image_size = (32, 32))] # are we debugging? ie, do we need a default setup? debug = Bool def activated(self): # add an import plugin import_op = ImportPlugin().get_operation() # if we're debugging, add a few data bits if self.debug: from cytoflow import Tube import_op.conditions["Dox"] = "float" import_op.conditions["Replicate"] = "int" tube1 = Tube(file = "../cytoflow/tests/data/Plate01/CFP_Well_A4.fcs", conditions = {"Dox" : 1.0, "Replicate" : 1}) tube2 = Tube(file = "../cytoflow/tests/data/Plate01/RFP_Well_A3.fcs", conditions = {"Dox" : 10.0, "Replicate" : 1}) tube3 = Tube(file = "../cytoflow/tests/data/Plate01/CFP_Well_B4.fcs", conditions = {"Dox" : 1.0, "Replicate" : 2}) tube4 = Tube(file = "../cytoflow/tests/data/Plate01/RFP_Well_A6.fcs", conditions = {"Dox" : 10.0, "Replicate" : 2}) import_op.tubes = [tube1, tube2, tube3, tube4] self.model.add_operation(import_op) def prepare_destroy(self): self.model = None def _default_layout_default(self): return TaskLayout(left = PaneItem("edu.mit.synbio.workflow_pane"), right = PaneItem("edu.mit.synbio.view_traits_pane")) def create_central_pane(self): self.view = FlowTaskPane(model = self.model) return self.view def create_dock_panes(self): return [WorkflowDockPane(model = self.model, plugins = self.op_plugins, task = self), ViewDockPane(model = self.model, plugins = self.view_plugins, task = self)] def on_new(self): self.model.workflow = [] # add an import operation import_op = ImportPlugin().get_operation() self.model.add_operation(import_op) def on_open(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, action = 'open', wildcard='*.flow') if dialog.open() == OK: self.open_file(dialog.path) def open_file(self, path): f = open(path, 'r') unpickler = pickle.Unpickler(f) new_workflow = unpickler.load() # replace the current workflow with the one we just loaded if False: from event_tracer import record_events with record_events() as container: self.model.workflow = new_workflow container.save_to_directory(os.getcwd()) else: self.model.workflow = new_workflow def on_save(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, action = 'save as', wildcard='*.flow') if dialog.open() == OK: self.save_file(dialog.path) def on_save_as(self): pass def save_file(self, path): # TODO - error handling f = open(path, 'w') pickler = pickle.Pickler(f, 0) # text protocol for now pickler.dump(self.model.workflow) def on_export(self): """ Shows a dialog to export a file """ information(None, "This will save exactly what you see on the screen " "to a file. Choose the file type via the file " "extension (ie .png, .pdf, .jpg)", "Export") dialog = FileDialog(parent = self.window.control, action = 'save as') if dialog.open() == OK: self.view.export(dialog.path) def on_notebook(self): """ Shows a dialog to export the workflow to an Jupyter notebook """ return dialog = FileDialog(parent = self.window.control, action = 'save as', wildcard = '*.ipynb') if dialog.open() == OK: writer = JupyterNotebookWriter(file = dialog.path) writer.export(self.workflow) def on_prefs(self): pass def on_problem(self): information(None, "Your email client will now create a new message to the " "developer. Debugging logs are attached. Please fill " "out the template bug report and send -- thank you for " "reporting a bug!") # import tempfile log = guiutil.parent_log.getvalue() log += self.model.get_child_log() # # logfile = tempfile.NamedTemporaryFile(delete = False) # logfile.write(parent_log) # logfile.write(child_log) # logfile.close() versions = ["{0} {1}".format(key, value) for key, value in self._get_package_versions().iteritems()] body = """ Thank you for your bug report! Please fill out the following template. PLATFORM (Mac, PC, Linux, other): OPERATING SYSTEM (eg OSX 10.7, Windows 8.1): SEVERITY (Critical? Major? Minor? Enhancement?): DESCRIPTION: - What were you trying to do? - What happened? - What did you expect to happen? PACKAGE VERSIONS: {0} DEBUG LOG: {1} """.format(versions, log) mailto.mailto("*****@*****.**", subject = "Cytoflow bug report", body = body) def _get_package_versions(self): import sys from cytoflow import __version__ as cf_version from fcsparser import __version__ as fcs_version from pandas import __version__ as pd_version from numpy import __version__ as np_version from numexpr import __version__ as nxp_version from bottleneck import __version__ as btl_version from seaborn import __version__ as sns_version from matplotlib import __version__ as mpl_version from scipy import __version__ as scipy_version from sklearn import __version__ as skl_version from pyface import __version__ as pyf_version from envisage import __version__ as env_version from traits import __version__ as trt_version from traitsui import __version__ as trt_ui_version return {"python" : sys.version, "cytoflow" : cf_version, "fcsparser" : fcs_version, "pandas" : pd_version, "numpy" : np_version, "numexpr" : nxp_version, "bottleneck" : btl_version, "seaborn" : sns_version, "matplotlib" : mpl_version, "scipy" : scipy_version, "scikit-learn" : skl_version, "pyface" : pyf_version, "envisage" : env_version, "traits" : trt_version, "traitsui" : trt_ui_version} def on_about(self): versions = self._get_package_versions() text = ["<b>Cytoflow {0}</b>".format(versions['cytoflow']), "<p>"] ver_text = ["{0} {1}".format(key, value) for key, value in versions.iteritems()] text.extend(ver_text) text.extend(["Icons from the <a href=http://tango.freedesktop.org>Tango Desktop Project</a>", "<a href=https://thenounproject.com/search/?q=setup&i=14287>Settings icon</a> by Paulo Sa Ferreira from <a href=https://thenounproject.com>The Noun Project</a>", "<a href=http://www.freepik.com/free-photos-vectors/background>App icon from Starline - Freepik.com</a>", "Cuvette image from Wikimedia Commons user <a href=http://commons.wikimedia.org/wiki/File:Hellma_Large_cone_cytometry_cell.JPG>HellmaUSA</a>"]) dialog = AboutDialog(text = text, parent = self.window.control, title = "About", image = ImageResource('cuvette'), additions = text) dialog.open() def add_operation(self, op_id): # first, find the matching plugin plugin = next((x for x in self.op_plugins if x.id == op_id)) # add the operation and the operation's default view self.model.add_operation(plugin.get_operation()) def set_current_view(self, view_id): """ called by the view pane """ if view_id == "default": self.model.set_current_view(self.model.selected.default_view) else: plugin = next((x for x in self.view_plugins if x.view_id == view_id)) self.model.set_current_view(plugin.get_view())
class FlowTask(Task): """ classdocs """ id = "edu.mit.synbio.cytoflow.flow_task" name = "Cytometry analysis" # the main workflow instance. # THIS IS WHERE IT'S INITIALLY INSTANTIATED (note the args=()) model = Instance(Workflow, args = ()) # the center pane view = Instance(FlowTaskPane) # plugin lists, to setup the interface op_plugins = List(IOperationPlugin) view_plugins = List(IViewPlugin) menu_bar = SMenuBar(SMenu(TaskAction(name='Open...', method='on_open', accelerator='Ctrl+O'), TaskAction(name='Save', #image='save', method='on_save', accelerator='Ctrl+S'), TaskAction(name='Save As...', method='on_save_as', accelerator='Ctrl+e'), TaskAction(name='Export image...', method='on_export', accelerator='Ctrl+x'), TaskAction(name='Export IPython notebook...', method='on_ipython', accelerator='Ctrl+I'), TaskAction(name='Preferences...', method='on_prefs', accelerator='Ctrl+P'), id='File', name='&File'), SMenu(TaskAction(name='About...', method='on_about', accelerator="Ctrl+A"), id="Help", name ="&Help")) tool_bars = [ SToolBar(TaskAction(method='on_new', name = "New", tooltip='New workflow', image=ImageResource('new')), TaskAction(method='on_open', name = "Open", tooltip='Open a file', image=ImageResource('open')), TaskAction(method='on_save', name = "Save", tooltip='Save the current file', image=ImageResource('save')), TaskAction(method='on_export', name = "Export", tooltip='Export the current plot', image=ImageResource('export')), TaskAction(method='on_ipython', name='IPython', tooltip="Export to an IPython notebook...", image=ImageResource('ipython')), TaskAction(method='on_prefs', name = "Prefs", tooltip='Preferences', image=ImageResource('prefs')), image_size = (32, 32))] # are we debugging? ie, do we need a default setup? debug = Bool worker = Instance(threading.Thread) to_update = Instance(UniquePriorityQueue, ()) worker_flag = Instance(threading.Event, args = ()) worker_lock = Instance(threading.Lock, args = ()) def initialized(self): # make sure that when the result changes we get notified # can't use a static notifier because selected.result gets updated # on the worker thread, but we need to dispatch on the UI thread self.model.on_trait_change(self._result_updated, "selected:result", dispatch = 'ui') def activated(self): # add an import plugin plugin = ImportPlugin() wi = WorkflowItem(task = self) wi.operation = plugin.get_operation() self.model.workflow.append(wi) self.model.selected = wi # if we're debugging, add a few data bits if self.debug: from cytoflow import Tube wi.operation.conditions["Dox"] = "log" tube1 = Tube(file = "../cytoflow/tests/data/Plate01/CFP_Well_A4.fcs", conditions = {"Dox" : 0.1}) tube2 = Tube(file = "../cytoflow/tests/data/Plate01/RFP_Well_A3.fcs", conditions = {"Dox" : 1.0}) wi.operation.tubes.append(tube1) wi.operation.tubes.append(tube2) self.add_operation('edu.mit.synbio.cytoflowgui.op_plugins.threshold') self.model.selected.operation.channel = "Y2-A" self.model.selected.operation.threshold = 2000 self.model.selected.operation.name = "T" def prepare_destroy(self): self.model = None def _default_layout_default(self): return TaskLayout(left = PaneItem("edu.mit.synbio.workflow_pane"), right = PaneItem("edu.mit.synbio.view_traits_pane")) def create_central_pane(self): self.view = FlowTaskPane(model = self.model) return self.view def create_dock_panes(self): return [WorkflowDockPane(model = self.model, plugins = self.op_plugins, task = self), ViewDockPane(model = self.model, plugins = self.view_plugins, task = self)] def on_new(self): self.model.workflow = [] # add an import plugin plugin = ImportPlugin() wi = WorkflowItem(task = self) wi.operation = plugin.get_operation() self.model.workflow.append(wi) self.model.selected = wi def on_open(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, action = 'open', wildcard='*.flow') if dialog.open() == OK: self.open_file(dialog.path) def open_file(self, path): f = open(path, 'r') unpickler = pickle.Unpickler(f) new_model = unpickler.load() # update the link back to the controller (ie, self) for wi in new_model.workflow: wi.task = self # and set up the view handlers for view in wi.views: view.handler = view.handler_factory(model = view, wi = wi) # replace the current workflow with the one we just loaded if False: from event_tracer import record_events with record_events() as container: self.model.workflow[:] = new_model.workflow self.model.selected = new_model.selected container.save_to_directory(os.getcwd()) else: self.model.workflow[:] = new_model.workflow self.model.selected = new_model.selected wi = self.model.workflow[0] while True: wi.status = "invalid" with self.worker_lock: self.to_update.put_nowait((self.model.workflow.index(wi), wi)) if wi.next: wi = wi.next else: break # check to see if we have a worker thread around if not self.worker or not self.worker.is_alive(): self.worker = threading.Thread(target = update_model, args = (self.worker_flag, self.worker_lock, self.to_update)) self.worker.daemon = True self.worker.start() # start the worker thread processing with self.worker_lock: if not self.to_update.empty(): self.worker_flag.set() def on_save(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, action = 'save as', wildcard='*.flow') if dialog.open() == OK: self.save_file(dialog.path) def on_save_as(self): pass def save_file(self, path): # TODO - error handling f = open(path, 'w') pickler = pickle.Pickler(f, 0) # text protocol for now pickler.dump(self.model) def on_export(self): """ Shows a dialog to export a file """ dialog = FileDialog(parent = self.window.control, action = 'save as') if dialog.open() == OK: self.view.export(dialog.path) def on_ipython(self): """ Shows a dialog to export the workflow to an IPython notebook """ dialog = FileDialog(parent = self.window.control, action = 'save as', wildcard = '*.ipynb') if dialog.open() == OK: writer = IPythonNotebookWriter(file = dialog.path) writer.export(self.workflow) def on_prefs(self): pass def on_about(self): from cytoflow import __version__ as cf_version from fcsparser import __version__ as fcs_version from pandas.version import __version__ as pd_version from numpy.version import version as np_version from numexpr import __version__ as numexp_version from seaborn import __version__ as sns_version from matplotlib import __version__ as mpl_version from pyface import __version__ as py_version from envisage import __version__ as env_version from traits import __version__ as trt_version from traitsui import __version__ as trt_ui_version text = ["<b>Cytoflow {0}</b>".format(cf_version), "<p>", "fcsversion {0}".format(fcs_version), "pandas {0}".format(pd_version), "numpy {0}".format(np_version), "numexpr {0}".format(numexp_version), "seaborn {0}".format(sns_version), "matplotlib {0}".format(mpl_version), "pyface {0}".format(py_version), "envisage {0}".format(env_version), "traits {0}".format(trt_version), "traitsui {0}".format(trt_ui_version), "Icons from the <a href=http://tango.freedesktop.org>Tango Desktop Project</a>", "<a href=https://thenounproject.com/search/?q=setup&i=14287>Settings icon</a> by Paulo Sa Ferreira from <a href=https://thenounproject.com>The Noun Project</a>", "Cuvette image from Wikimedia Commons user <a href=http://commons.wikimedia.org/wiki/File:Hellma_Large_cone_cytometry_cell.JPG>HellmaUSA</a>"] dialog = AboutDialog(parent = self.window.control, title = "About", image = ImageResource('cuvette'), additions = text) dialog.open() def add_operation(self, op_id): # first, find the matching plugin plugin = next((x for x in self.op_plugins if x.id == op_id)) # default to inserting at the end of the list if none selected after = self.model.selected if after is None: after = self.model.workflow[-1] idx = self.model.workflow.index(after) wi = WorkflowItem(task = self) wi.operation = plugin.get_operation() wi.next = after.next after.next = wi wi.previous = after if wi.next: wi.next.previous = wi self.model.workflow.insert(idx+1, wi) # set up the default view wi.default_view = plugin.get_default_view() if wi.default_view is not None: wi.default_view.op = wi.operation wi.default_view.handler = \ wi.default_view.handler_factory(model = wi.default_view, wi = wi.previous) wi.views.append(wi.default_view) # select (open) the new workflow item self.model.selected = wi if wi.default_view: wi.current_view = wi.default_view # invalidate everything following self.operation_parameters_updated() @on_trait_change("model:workflow[]") def _on_remove_operation(self, obj, name, old, new): if name == "workflow_items" and len(new) == 0 and len(old) > 0: assert len(old) == 1 wi = old[0] if self.model.selected == wi: self.model.selected = wi.previous wi.previous.next = wi.next if wi.next: wi.next.previous = wi.previous del wi.default_view del wi.views del wi self.operation_parameters_updated() @on_trait_change("model:selected:operation:+") def operation_parameters_updated(self): # invalidate this workflow item and all the ones following it wi = self.model.selected while True: wi.status = "invalid" with self.worker_lock: self.to_update.put_nowait((self.model.workflow.index(wi), wi)) if wi.next: wi = wi.next else: break # check to see if we have a worker thread around if not self.worker or not self.worker.is_alive(): self.worker = threading.Thread(target = update_model, args = (self.worker_flag, self.worker_lock, self.to_update)) self.worker.daemon = True self.worker.start() # start the worker thread processing with self.worker_lock: if not self.to_update.empty(): self.worker_flag.set() def set_current_view(self, view_id): """ called by the view pane """ wi = self.model.selected if view_id == "default": view_id = self.model.selected.default_view.id view = next((x for x in wi.views if x.id == view_id), None) if not view: plugin = next((x for x in self.view_plugins if x.view_id == view_id)) view = plugin.get_view() view.handler = view.handler_factory(model = view, wi = wi) wi.views.append(view) wi.current_view = view @on_trait_change("model:selected.current_view") def _current_view_changed(self, obj, name, old, new): # we get notified if *either* the currently selected workflowitem # *or* the current view changes. if name == 'selected': new = new.current_view if new else None old = old.current_view if old else None # remove the notifications from the old view if old: old.on_trait_change(self.view_parameters_updated, remove = True) # and if the old view was interactive, turn off its interactivity # to remove the matplotlib event handlers if "interactive" in old.traits(): old.interactive = False # whenever the view parameters change, we need to know so we can # update the plot(s) if new: new.on_trait_change(self.view_parameters_updated) if self.model.selected: self.view.plot(self.model.selected) else: self.view.clear_plot() else: self.view.clear_plot() def _result_updated(self, obj, name, old, new): print "result updated" if self.model.selected: self.view.plot(self.model.selected) else: self.view.clear_plot() def view_parameters_updated(self, obj, name, new): # i should be able to specify the metadata i want in the listener, # but there's an odd interaction (bug) between metadata, dynamic # trait listeners and instance traits. so, check for 'transient' # here instead, if obj.trait(name).transient: return print "view parameters updated: {0}".format(name) wi = self.model.selected if wi is None: wi = self.model.workflow[-1] self.view.plot(wi)