Пример #1
0
    def run(self):

        # Clear the default logging so we can use our own format
        rootLogger = logging.getLogger()
        list(map(rootLogger.removeHandler, rootLogger.handlers[:]))
        list(map(rootLogger.removeFilter, rootLogger.filters[:]))

        logging.getLogger().setLevel(logging.DEBUG)

        ## send the log to STDERR
        try:
            console_handler = logging.StreamHandler()
            console_handler.setFormatter(
                logging.Formatter(
                    "%(asctime)s %(levelname)s:%(name)s:%(message)s"))
            console_handler.setLevel(
                logging.DEBUG if self.debug else logging.ERROR)
            logging.getLogger().addHandler(console_handler)
        except:
            # if there's no console, this fails
            pass

        ## capture log in memory
        mem_handler = logging.StreamHandler(self.application_log)
        mem_handler.setFormatter(
            logging.Formatter(
                "%(asctime)s %(levelname)s:%(name)s:%(message)s"))
        mem_handler.setLevel(logging.DEBUG)
        logging.getLogger().addHandler(mem_handler)

        ## and display gui messages for exceprions
        gui_handler = multiprocess_logging.CallbackHandler(
            lambda msg, app=self: gui_handler_callback(msg, app))
        gui_handler.setLevel(logging.ERROR)
        logging.getLogger().addHandler(gui_handler)

        # must redirect to the gui thread
        self.on_trait_change(self.show_error,
                             'application_error',
                             dispatch='ui')

        # set up the model
        self.model = Workflow(remote_connection=self.remote_connection,
                              debug=self.debug)

        # and the shared central pane
        self.plot_pane = FlowTaskPane(model=self.model)

        # run the GUI
        super(CytoflowApplication, self).run()
Пример #2
0
    def run(self):

        # set the root logger level to DEBUG; decide what to do with each
        # message on a handler-by-handler basis
        logging.getLogger().setLevel(logging.DEBUG)

        ## send the log to STDERR
        try:
            console_handler = logging.StreamHandler()
            console_handler.setFormatter(
                logging.Formatter(
                    "%(asctime)s %(levelname)s:%(name)s:%(message)s"))
            console_handler.setLevel(
                logging.DEBUG if self.debug else logging.ERROR)
            logging.getLogger().addHandler(console_handler)
        except:
            # if there's no console, this fails
            pass

        ## capture log in memory
        mem_handler = logging.StreamHandler(self.application_log)
        mem_handler.setFormatter(
            logging.Formatter(
                "%(asctime)s %(levelname)s:%(name)s:%(message)s"))
        mem_handler.setLevel(logging.DEBUG)
        logging.getLogger().addHandler(mem_handler)

        ## and display gui messages for exceptions
        gui_handler = CallbackHandler(
            lambda rec, app=self: gui_handler_callback(rec.getMessage(), app))
        gui_handler.setLevel(logging.ERROR)
        logging.getLogger().addHandler(gui_handler)

        # must redirect to the gui thread
        self.on_trait_change(self.show_error,
                             'application_error',
                             dispatch='ui')

        # set up the model
        self.model = Workflow(remote_connection=self.remote_connection,
                              debug=self.debug)

        # and the shared central pane
        self.plot_pane = FlowTaskPane(model=self.model)

        # run the GUI
        super(CytoflowApplication, self).run()
Пример #3
0
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)
Пример #4
0
 def create_central_pane(self):
     self.view = FlowTaskPane(model=self.model)
     return self.view
Пример #5
0
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())
Пример #6
0
 def create_central_pane(self):
     self.view = FlowTaskPane(model = self.model)
     return self.view
Пример #7
0
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
Пример #8
0
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)