def __init__(self, default_value, allow_none=True, **metadata):
        from openmdao.main.vartree import VariableTree # Break import loop on VariableTree
        if isinstance(default_value, VariableTree):
            klass = default_value.__class__
            if 'iotype' in metadata:
                default_value._iotype = metadata['iotype']
            else:
                metadata['iotype'] = default_value.iotype
        else:
            raise TypeError('default_value must be an instance of VariableTree'
                            ' or subclass')

        metadata.setdefault('copy', 'deep')
        self._allow_none = allow_none
        self.klass = klass
        self._instance = Instance(klass=klass, allow_none=False, factory=None,
                                  args=None, kw=None, **metadata)
        self._instance.default_value = default_value
        super(VarTree, self).__init__(default_value, **metadata)
Exemple #2
0
    def __init__(self, klass=object, allow_none=True, factory=None,
                 args=None, kw=None, **metadata):

        default_value = None
        try:
            iszopeiface = issubclass(klass, zope.interface.Interface)
        except TypeError:
            iszopeiface = False
            if not isclass(klass):
                default_value = klass
                klass = klass.__class__

        metadata.setdefault('copy', 'deep')

        self._allow_none = allow_none
        self.klass = klass

        if has_interface(klass, IContainer) or \
           (isclass(klass) and IContainer.implementedBy(klass)):
            self._is_container = True
        else:
            self._is_container = False

        if iszopeiface:
            self._instance = None
            self.factory = factory
            self.args = args
            self.kw = kw
        else:
            self._instance = Instance(klass=klass, allow_none=allow_none,
                                      factory=factory, args=args, kw=kw,
                                      **metadata)
            if default_value:
                self._instance.default_value = default_value
            else:
                default_value = self._instance.default_value

            if klass.__name__ == 'VariableTree':
                raise TypeError('Slotting of VariableTrees is not supported,'
                                ' please use VarTree instead')

        super(Slot, self).__init__(default_value, **metadata)
Exemple #3
0
    def __init__(self, **kwargs):
        '''Constructor for UserInterface object
        
        Adds panels and plots to a userinterface window.
        '''
        
        super(UserInterface, self).__init__()
        self.add_trait('rawviewer', RawViewer())
        self.add_trait('cpanel', ControlPanel())
        self.add_trait('mdpanel', MetadataPanel())
        self.add_trait('messagelog', MessageLog())

        self.rawviewer.startProcessJob()
        self.cpanel.sync_trait('datalistlength', self.rawviewer)

        self.imagepanel = Instance(Component)
        self.createImagePanel()
        self.rrpanel = Instance(Component)
        self.rrpanel = VPlotContainer(stack_order = 'top_to_bottom',
                                        resizeable='', use_backbuffer=True,
                                        bgcolor='transparent')
        self.rrpanel.get_preferred_size()
Exemple #4
0
class Graph3D(HasTraits):
    scene = Instance(MlabSceneModel, ())
#    def _scene_default(self):
#        s = MlabSceneModel()
#        pt_gen = point_generator(5, 0, 0)
#        j = 0
#        xx = []
#        yy = []
#        zz = []
#        for p1, p2 in [((-5, 0), (5, 0)), ((5, 1), (-5, 1))]:
# #        for p1, p2 in pt_gen:
#
#            #move to p1
#            #move to p2
#            moving = True
#            i = 0
#            zs = []
#            xs = []
#            ys = []
#            while moving:
#                if i == 11:
#                    break
#
#                x = p1[0] - i if j % 2 else p1[0] + i
#                mag = 40 - x * x
#                zs.append(mag)
#
#                xs.append(x)
#                ys.append(p1[1])
#
#                if j % 2:
#                    data = sorted(zip(xs, ys, zs), key = lambda d:d[0])
#
#                    xs = [d[0] for d in data]
#                    ys = [d[1] for d in data]
#                    zs = [d[2] for d in data]
#
#                i += 1
#            j += 1
#            xx.append(xs)
#            yy.append(ys)
#            zz.append(zs)
#
#        s.mlab.mesh(asarray(xx), asarray(yy), asarray(zz))
# #        s.mlab.plot3d(asarray(xx)[0], asarray(yy)[0], asarray(zz)[0], asarray(zz)[0])
#        return s
#        xs = []
#        ys = []
#        zs = []
#        for i in range(5):
#
#            x = linspace(-5, 5, 100)
#            y = ones(100) * i
#            z = 10 - 0.2 * x * x
#            xs.append(x)
#            ys.append(y)
#            zs.append(z)
#
#        s.mlab.mesh(asarray(xs), asarray(ys), asarray(zs)
#                        #z,
# #                          line_width = 50,
# #                          tube_radius = None,
#                          #representation = 'wireframe'
#                          )
#        return s
    def clear(self):
        self.scene.mlab.clf()

    def plot_data(self, z, func='surf', outline=True,
                  **kw):

        mlab = self.scene.mlab
        getattr(mlab, func)(z, **kw)

        if outline:
            mlab.outline()

    def plot_surf(self, *args, **kw):
        mlab = self.scene.mlab
        mlab.surf(*args, **kw)
        mlab.outline()
        mlab.axes()


    def plot_points(self, x, y, z, color=None):
        mlab = self.scene.mlab
        if color:
            pts = mlab.points3d(x, y, z, z, scale_mode='none', scale_factor=0.1)
        else:
            pts = mlab.points3d(x, y, z, scale_mode='none', scale_factor=0.1)
        mesh = mlab.pipeline.delaunay2d(pts)
        mlab.pipeline.surface(mesh)

    def plot_lines(self, x, y, z):
        import numpy as np
#        n_mer, n_long = 6, 11
#        pi = numpy.pi
#        dphi = pi / 1000.0
#        phi = numpy.arange(0.0, 2 * pi + 0.5 * dphi, dphi)
#        mu = phi * n_mer
#        x = numpy.cos(mu) * (1 + numpy.cos(n_long * mu / n_mer) * 0.5)
#        y = numpy.sin(mu) * (1 + numpy.cos(n_long * mu / n_mer) * 0.5)
#        print x
#        z = numpy.sin(n_long * mu / n_mer) * 0.5
#
#        l = self.scene.mlab.plot3d(x, y, z, numpy.sin(mu), tube_radius=0.025, colormap='Spectral')
#        return l

        x = np.array(x)
        y = np.array(y)
        z = np.array(z)
        self.scene.mlab.plot3d(x, y, z)

    def traits_view(self):
#        self.scene.mlab.points3d(x, y, z)
        kw = dict()
        klass = Scene

        use_mayavi_toolbar = True
        use_raw_toolbar = False
        if use_mayavi_toolbar:
            klass = MayaviScene
        elif use_raw_toolbar:
            klass = None

        if klass is not None:
            kw['scene_class'] = klass

        v = View(Item('scene', show_label=False,
                      height=400,
                      width=400,
                      resizable=True,
                      editor=SceneEditor(**kw


                                           )))
        return v
Exemple #5
0
class UserInterface(HasTraits):
    '''UserInterface Class
    
    GUI for SrXes. Uses traits to watch for user interaction and then adds
    jobs to a queue for processing. 
    '''

    def __init__(self, **kwargs):
        '''Constructor for UserInterface object
        
        Adds panels and plots to a userinterface window.
        '''
        
        super(UserInterface, self).__init__()
        self.add_trait('rawviewer', RawViewer())
        self.add_trait('cpanel', ControlPanel())
        self.add_trait('mdpanel', MetadataPanel())
        self.add_trait('messagelog', MessageLog())

        self.rawviewer.startProcessJob()
        self.cpanel.sync_trait('datalistlength', self.rawviewer)

        self.imagepanel = Instance(Component)
        self.createImagePanel()
        self.rrpanel = Instance(Component)
        self.rrpanel = VPlotContainer(stack_order = 'top_to_bottom',
                                        resizeable='', use_backbuffer=True,
                                        bgcolor='transparent')
        self.rrpanel.get_preferred_size()
    
    

    # TODO: Adjust view
    view = View(
             HSplit(
               VSplit(
                    UItem('imagepanel', editor=ComponentEditor(), padding=0),
                    UItem('mdpanel', style="custom", padding=5, height=85, width=700)
                     ),
               VGroup(
                    UItem('cpanel', style="custom", width=-430, padding=10),
                    UItem('messagelog', style ="custom", width=-430, padding =10),
                    UItem('rrpanel', editor=ComponentEditor(), style='custom')
                     ),
                show_labels=False,
                  ),
            resizable = True,
            height = 0.96, width = 1.0,
            handler = PyXDAHandler(),
            buttons = NoButtons,
            title = 'SrXes',
            icon = LOGO)

    #############################
    # UI Action Handling
    #############################
    @on_trait_change('cpanel.left_arrow', post_init=True)
    def _left_arrow_fired(self):
        '''Left arrow has been pushed
        
        Changes the image display to the left one over if it exists.
        '''
        
        self.rawviewer.jobqueue.put(['updatecache', ['left']])
        return
    
    @on_trait_change('cpanel.right_arrow', post_init=True)
    def _right_arrow_fired(self):
        '''Right arrow has been pushed
        
        Changes the image display to the right one over if it exists.
        '''
        
        self.rawviewer.jobqueue.put(['updatecache', ['right']])
        return
    
    @on_trait_change('cpanel.generate', post_init=True)
    def _generate_fired(self):
        '''Generate Intensity button has been pushed
        
        Creates a reduced representation plot in the GUI.
        '''
        
        self.rawviewer.jobqueue.put(['plotrr', [self.cpanel.rrchoice]])
        time.sleep(0.5)
        self.updateRRPanel(self.cpanel.rrchoice)
        return
    
    @on_trait_change('cpanel.dirpath', post_init=True)
    def _dirpath_changed(self):
        '''Directory path has changed
        
        If there are tiff images in the folder path, they will be loaded and 
        the first image will be plotted to the screen. If there are no tiff
        images or the path is invalid, rawviewer.message will be changed to a
        string explaining the error.
        '''
        
        self.rawviewer.jobqueue.put(['startload', [self.cpanel.dirpath]])
    
    @on_trait_change('rawviewer.pic', post_init=True)
    def _pic_changed(self):
        '''The displayed 2D image has been changed
        
        Changes the control panel index, and the metadata associated with it.
        '''
        
        pic =  self.rawviewer.pic
        self.cpanel.index = pic.n + 1
        self.mdpanel.name = pic.name
        if pic.metadata:
            for key in pic.metadata.keys():
                setattr(self.mdpanel, key, pic.metadata[key])
        return
        
    @on_trait_change('rawviewer.display.filename', post_init=True)
    def _filename_changed(self):
        '''The filename of the 2D image has changed
        
        Changes the displayed filename to the updated one.
        '''
        
        print 'filename changed'
        if self.rawviewer.display.filename == -1:
            self.cpanel.filename = ''
        else:
            self.cpanel.filename = self.rawviewer.datalist[self.rawviewer.display.filename].name
    
    @on_trait_change('rawviewer.loadimage.message', post_init=True)
    def handleMessage(self):
        '''Rawviewer.message has changed
        
        Displays the new message in messagelog. If there is already a message 
        inside messagelog, the new one is plotted below it.
        '''
    
        if self.rawviewer.loadimage.message != '':
            if self.messagelog.line_pos == 0:     
                self.messagelog.line1 = 'Out: ' + self.rawviewer.loadimage.message
                self.messagelog.line_pos = self.messagelog.line_pos +1
                return
            if self.messagelog.line_pos == 1:
                self.messagelog.line2 = 'Out: ' + self.rawviewer.loadimage.message
                self.messagelog.line_pos = self.messagelog.line_pos + 1
                return
            if self.messagelog.line_pos == 2:
                self.messagelog.line3 = 'Out: ' + self.rawviewer.loadimage.message
                self.messagelog.line_pos = 0
                return
        return

    
    # TODO: Update
    def createImagePanel(self):
        '''Creates the Image Panel
        
        Creates the image panel that contains the 2D image, colorbarm histogram,
        and 1D slice.
        '''
        
        cont = VPlotContainer(stack_order = 'top_to_bottom',
                                bgcolor = 'transparent',
                                use_backbuffer=True)

        imageplot = getattr(self.rawviewer, 'imageplot')
        colorbar = getattr(self.rawviewer.display, 'colorbar')
        histogram = getattr(self.rawviewer, 'histogram')
        plot1d = getattr(self.rawviewer, 'plot1d')

        imgcont = HPlotContainer(imageplot, colorbar, bgcolor = 'transparent',
                                    spacing = 20.0)
        cont.add(imgcont)
        cont.add(histogram)
        cont.add(plot1d)
        
        self.imagepanel = cont
        self.imagepanel.get_preferred_size()
        self.imagepanel.invalidate_draw()
        return

    def updateRRPanel(self, choice):
        '''Updates the Reduced Representation Panel
        
        Args:
            choice: the new variable for the RR. eg: mean, total intensity...
        '''
        
        rrplots = getattr(self.rawviewer, 'rrplots')
        
        if rrplots[choice] not in self.rrpanel._components:
            self.rrpanel.add(rrplots[choice])

        self.rrpanel.invalidate_and_redraw()
        return
Exemple #6
0
class Explorer3D(HasTraits):
    """This class basically allows you to create a 3D cube of data (a
    numpy array), specify an equation for the scalars and view it
    using the mayavi plugin.
    """

    ########################################
    # Traits.

    # Set by envisage when this is offered as a service offer.
    window = Instance('pyface.workbench.api.WorkbenchWindow')

    # The equation that generates the scalar field.
    equation = Str('sin(x*y*z)/(x*y*z)',
                   desc='equation to evaluate (enter to set)',
                   auto_set=False,
                   enter_set=True)

    # Dimensions of the cube of data.
    dimensions = Array(value=(128, 128, 128),
                       dtype=int,
                       shape=(3,),
                       cols=1,
                       labels=['nx', 'ny', 'nz'],
                       desc='the array dimensions')

    # The volume of interest (VOI).
    volume = Array(dtype=float,
                   value=(-5,5,-5,5,-5,5),
                   shape=(6,),
                   cols=2,
                   labels=['xmin','xmax','ymin','ymax','zmin','zmax'],
                   desc='the volume of interest')

    # Clicking this button resets the data with the new dimensions and
    # VOI.
    update_data = Button('Update data')

    ########################################
    # Private traits.
    # Our data source.
    _x = Array
    _y = Array
    _z = Array
    data = Array
    source = Any
    _ipw1 = Any
    _ipw2 = Any
    _ipw3 = Any

    ########################################
    # Our UI view.
    view = View(Item('equation', editor=TextEditor(auto_set=False,
                                                   enter_set=True)),
                Item('dimensions'),
                Item('volume'),
                Item('update_data', show_label=False),
                resizable=True,
                scrollable=True,
                )

    ######################################################################
    # `object` interface.
    ######################################################################
    def __init__(self, **traits):
        super(Explorer3D, self).__init__(**traits)
        # Make some default data.
        if len(self.data) == 0:
            self._make_data()
        # Note: to show the visualization by default we must wait till
        # the mayavi engine has started.  To do this we hook into the
        # mayavi engine's started event and setup our visualization.
        # Now, when this object is constructed (i.e. when this method
        # is invoked), the services are not running yet and our own
        # application instance has not been set.  So we can't even
        # get hold of the mayavi instance.  So, we do the hooking up
        # when our application instance is set by listening for
        # changes to our application trait.

    def get_mayavi(self):
        from mayavi.plugins.script import Script
        return self.window.get_service(Script)

    ######################################################################
    # Non-public methods.
    ######################################################################
    def _make_data(self):
        dims = self.dimensions.tolist()
        np = dims[0]*dims[1]*dims[2]
        xmin, xmax, ymin, ymax, zmin, zmax = self.volume
        x, y, z = numpy.ogrid[xmin:xmax:dims[0]*1j,
                              ymin:ymax:dims[1]*1j,
                              zmin:zmax:dims[2]*1j]
        self._x = x.astype('f')
        self._y = y.astype('f')
        self._z = z.astype('f')
        self._equation_changed('', self.equation)

    def _show_data(self):
        if self.source is not None:
            return
        mayavi = self.get_mayavi()
        if mayavi.engine.current_scene is None:
            mayavi.new_scene()
        from mayavi.sources.array_source import ArraySource
        vol = self.volume
        origin = vol[::2]
        spacing = (vol[1::2] - origin)/(self.dimensions -1)
        src = ArraySource(transpose_input_array=False,
                          scalar_data=self.data,
                          origin=origin,
                          spacing=spacing)
        self.source = src
        mayavi.add_source(src)

        from mayavi.modules.outline import Outline
        from mayavi.modules.image_plane_widget import ImagePlaneWidget
        from mayavi.modules.axes import Axes
        # Visualize the data.
        o = Outline()
        mayavi.add_module(o)
        a = Axes()
        mayavi.add_module(a)
        self._ipw1 = ipw = ImagePlaneWidget()
        mayavi.add_module(ipw)
        ipw.module_manager.scalar_lut_manager.show_scalar_bar = True

        self._ipw2 = ipw_y = ImagePlaneWidget()
        mayavi.add_module(ipw_y)
        ipw_y.ipw.plane_orientation = 'y_axes'

        self._ipw3 = ipw_z = ImagePlaneWidget()
        mayavi.add_module(ipw_z)
        ipw_z.ipw.plane_orientation = 'z_axes'

    ######################################################################
    # Traits static event handlers.
    ######################################################################
    def _equation_changed(self, old, new):
        try:
            g = numpy.__dict__
            s = eval(new, g, {'x':self._x,
                              'y':self._y,
                              'z':self._z})
            # The copy makes the data contiguous and the transpose
            # makes it suitable for display via tvtk.
            s = s.transpose().copy()
            # Reshaping the array is needed since the transpose
            # messes up the dimensions of the data.  The scalars
            # themselves are ravel'd and used internally by VTK so the
            # dimension does not matter for the scalars.
            s.shape = s.shape[::-1]
            self.data = s
        except:
            pass

    def _dimensions_changed(self):
        """This does nothing and only changes to update_data do
        anything.
        """
        return

    def _volume_changed(self):
        return

    def _update_data_fired(self):
        self._make_data()
        src = self.source
        if src is not None:
            vol = self.volume
            origin = vol[::2]
            spacing = (vol[1::2] - origin)/(self.dimensions -1)
            # Set the source spacing and origin.
            src.trait_set(spacing=spacing, origin=origin)
            # Update the sources data.
            src.update_image_data = True
            self._reset_ipw()

    def _reset_ipw(self):
        ipw1, ipw2, ipw3 = self._ipw1, self._ipw2, self._ipw3
        if ipw1.running:
            ipw1.ipw.place_widget()
        if ipw2.running:
            ipw2.ipw.place_widget()
            ipw2.ipw.plane_orientation = 'y_axes'
        if ipw3.running:
            ipw3.ipw.place_widget()
            ipw3.ipw.plane_orientation = 'z_axes'
        self.source.render()

    def _data_changed(self, value):
        if self.source is None:
            return
        self.source.scalar_data = value

    def _window_changed(self):
        m = self.get_mayavi()
        if m.engine.running:
            if len(self.data) == 0:
                # Happens since the window may be set on __init__ at
                # which time the data is not created.
                self._make_data()
            self._show_data()
        else:
            # Show the data once the mayavi engine has started.
            m.engine.on_trait_change(self._show_data, 'started')
Exemple #7
0
# Enthought library imports.
from pyface.action.api import Action, ActionItem, Group, \
     MenuManager, MenuBarManager, ToolBarManager
from pyface.util.id_helper import get_unique_id
from traits.api import Bool, Callable, Enum, HasTraits, Instance, \
     List, Property, Str, Trait, Tuple, Unicode

# Trait definitions.
SubSchema = Trait(None, Action, ActionItem, Group, MenuManager,
                  Instance('pyface.tasks.action.schema.GroupSchema'),
                  Instance('pyface.tasks.action.schema.MenuSchema'),
                  Instance('pyface.tasks.action.schema.Schema'))


class Schema(HasTraits):
    """ The abstract base class for all Tasks action schemas.
    """

    # The schema's identifier (unique within its parent schema).
    id = Str

    def _id_default(self):
        return get_unique_id(self)

    # The list of sub-items in the schema. These items can be other
    # (non-top-level) schema or concrete instances from the Pyface API.
    items = List(SubSchema)

    def __init__(self, *items, **traits):
        """ Creates a new schema.
        """
class VTKFileReader(VTKXMLFileReader):

    """A VTK file reader.  This does not handle the new XML file
    format but only the older format.  The reader supports all the
    different types of data sets.  This reader also supports a time
    series.
    """

    # The version of this class.  Used for persistence.
    __version__ = 0

    # The VTK data file reader.
    reader = Instance(tvtk.DataSetReader, args=(),
                      kw={'read_all_scalars':True,
                          'read_all_vectors': True,
                          'read_all_tensors': True,
                          'read_all_fields': True} )

    # Information about what this object can produce.
    output_info = PipelineInfo(datasets=['any'],
                               attribute_types=['any'],
                               attributes=['any'])

    ######################################################################
    # `FileDataSource` interface
    ######################################################################

    def has_output_port(self):
        """ Return True as the reader has output port."""
        return True

    def get_output_object(self):
        """ Return the reader output port."""
        return self.reader.output_port
    
    ######################################################################
    # Non-public interface
    ######################################################################
    def _file_path_changed(self, fpath):
        value = fpath.get()
        if len(value) == 0:
            self.name = 'No VTK file'
            return
        else:
            self.reader.file_name = value
            self.update()

            # Setup the outputs by resetting self.outputs.  Changing
            # the outputs automatically fires a pipeline_changed
            # event.
            try:
                n = self.reader.number_of_outputs
            except AttributeError: # for VTK >= 4.5
                n = self.reader.number_of_output_ports
            outputs = []
            for i in range(n):
                outputs.append(self.reader.get_output(i))
            self.outputs = outputs

            # FIXME: Only the first output goes through the assign
            # attribute filter.
            aa = self._assign_attribute
            self.configure_input_data(aa, outputs[0])
            self.update_data()
            aa.update()
            outputs[0] = aa.output

            self.outputs = outputs

            # FIXME: The output info is only based on the first output.
            self.output_info.datasets = [get_tvtk_dataset_name(outputs[0])]

            # Change our name on the tree view
            self.name = self._get_name()

    def _get_name(self):
        """ Gets the name to display on the tree view.
        """
        fname = basename(self.file_path.get())
        ret = "VTK file (%s)"%fname
        if len(self.file_list) > 1:
            ret += " (timeseries)"
        if '[Hidden]' in self.name:
            ret += ' [Hidden]'

        return ret
class TreeItem(HasTraits):
    """ A generic base-class for items in a tree data structure. """

    #### 'TreeItem' interface #################################################

    # Does this item allow children?
    allows_children = Bool(True)

    # The item's children.
    children = List(Instance('TreeItem'))

    # Arbitrary data associated with the item.
    data = Any

    # Does the item have any children?
    has_children = Property(Bool)

    # The item's parent.
    parent = Instance('TreeItem')

    ###########################################################################
    # 'object' interface.
    ###########################################################################

    def __str__(self):
        """ Returns the informal string representation of the object. """

        if self.data is None:
            s = ''

        else:
            s = str(self.data)

        return s

    ###########################################################################
    # 'TreeItem' interface.
    ###########################################################################

    #### Properties ###########################################################

    # has_children
    def _get_has_children(self):
        """ True iff the item has children. """

        return len(self.children) != 0

    #### Methods ##############################################################

    def append(self, child):
        """ Appends a child to this item.

        This removes the child from its current parent (if it has one).

        """

        return self.insert(len(self.children), child)

    def insert(self, index, child):
        """ Inserts a child into this item at the specified index.

        This removes the child from its current parent (if it has one).

        """

        if child.parent is not None:
            child.parent.remove(child)

        child.parent = self
        self.children.insert(index, child)

        return child

    def remove(self, child):
        """ Removes a child from this item. """

        child.parent = None
        self.children.remove(child)

        return child

    def insert_before(self, before, child):
        """ Inserts a child into this item before the specified item.

        This removes the child from its current parent (if it has one).

        """

        index = self.children.index(before)

        self.insert(index, child)

        return (index, child)

    def insert_after(self, after, child):
        """ Inserts a child into this item after the specified item.

        This removes the child from its current parent (if it has one).

        """

        index = self.children.index(after)

        self.insert(index + 1, child)

        return (index, child)
class _ThemedCheckboxEditor(Editor):
    """ Traits UI themed checkbox editor.
    """

    # The ThemedControl used for the checkbox:
    checkbox = Instance(ThemedControl)

    #---------------------------------------------------------------------------
    #  Finishes initializing the editor by creating the underlying toolkit
    #  widget:
    #---------------------------------------------------------------------------

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        # Create the checkbox and its control:
        item = self.item
        factory = self.factory
        label = self.string_value(factory.label or item.label)
        min_size = (0, 0)
        if factory.theme is not None:
            min_size = (80, 0)

        self.checkbox = checkbox = ThemedControl(
            **factory.get('image', 'position', 'spacing', 'theme')).set(
                text=label, controller=self, min_size=min_size)
        self.control = checkbox.create_control(parent)

        # Set the tooltip:
        self.set_tooltip()

    #---------------------------------------------------------------------------
    #  Updates the editor when the object trait changes external to the editor:
    #---------------------------------------------------------------------------

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        if self.checkbox.state == 'hover':
            self._set_hover_theme()
        else:
            self._set_theme()

    #-- ThemedControl Event Handlers -------------------------------------------

    def normal_motion(self, x, y, event):
        self._set_hover_theme('hover')
        self.control.CaptureMouse()

    def hover_left_down(self, x, y, event):
        self.control.ReleaseMouse()
        self._set_hover_theme('down', not self.value)

    def hover_motion(self, x, y, event):
        if not self.checkbox.in_control(x, y):
            self.control.ReleaseMouse()
            self._set_theme('normal')

    def down_left_up(self, x, y, event):
        if self.checkbox.in_control(x, y):
            self.value = not self.value
            self.normal_motion(x, y, event)
        else:
            self._set_theme('normal')

    def down_motion(self, x, y, event):
        if not self.checkbox.in_control(x, y):
            self._set_theme()
        else:
            self._set_hover_theme(value=not self.value)

    #-- Private Methods --------------------------------------------------------

    def _set_theme(self, state=None, value=None):
        """ Sets the theme, image, offset and optional checkbox state to use for
            a specified checkbox state value.
        """
        if value is None:
            value = self.value

        factory = self.factory
        theme, image = factory.theme, factory.image
        if value:
            theme, image = factory.on_theme, factory.on_image

        n = (1 * value) * (theme is not None)
        self.checkbox.set(offset=(n, n),
                          theme=theme or factory.theme,
                          image=image or factory.image,
                          state=state or self.checkbox.state)

    def _set_hover_theme(self, state=None, value=None):
        """ Sets the theme, image, offset and optional checkbox state to use for
            a specified checkbox state value while in hover mode.
        """
        if value is None:
            value = self.value

        factory = self.factory
        theme, image = factory.hover_off_theme, factory.hover_off_image
        if value:
            theme = factory.hover_on_theme or factory.on_theme
            image = factory.hover_on_image or factory.on_image

        n = (1 * value) * (theme is not None)
        self.checkbox.set(offset=(n, n),
                          theme=theme or factory.theme,
                          image=image or factory.image,
                          state=state or self.checkbox.state)
Exemple #11
0
class CandlePlot(BaseCandlePlot):
    """ A plot consisting of a filled bar with an optional centerline and
    stems extending to extrema.  Usually used to represent some statistics
    on bins of data, with the centerline representing the mean, the bar
    extents representing +/- 1 standard dev or 10th/90th percentiles, and
    the stems extents representing the minimum and maximum samples.

    The values in the **index** datasource indicate the centers of the bins;
    the widths of the bins are *not* specified in data space, and are
    determined by the minimum space between adjacent index values.
    """

    #------------------------------------------------------------------------
    # Data-related traits
    #------------------------------------------------------------------------

    # The minimum values at each index point.  If None, then no stem and no
    # endcap line will be drawn below each bar.
    min_values = Instance(AbstractDataSource)

    # The "lower" extent of the "bar", i.e. the value closest to the
    # corresponding value in min_values at each index.
    bar_min = Instance(AbstractDataSource)

    # Values that appear inside the bar, between bar_min and bar_max.  These
    # Are usually mean or median values, and are rendered with a solid line
    # of a different color than the bar fill color.  This can be None.
    center_values = Instance(AbstractDataSource)

    # The "upper" extent of the "bar", i.e. the value closest to the
    # corresponding value in max_values at each index.
    bar_max = Instance(AbstractDataSource)

    # The maximum value at each index point.  If None, then no stem and no
    # endcap line will be drawn above each bar.
    max_values = Instance(AbstractDataSource)

    value = Property

    def map_data(self, screen_pt, all_values=True):
        """ Maps a screen space point into the "index" space of the plot.

        Overrides the BaseXYPlot implementation, and always returns an
        array of (index, value) tuples.
        """
        x, y = screen_pt
        if self.orientation == 'v':
            x, y = y, x
        return array((self.index_mapper.map_data(x),
                      self.value_mapper.map_data(y)))

    def map_index(self, screen_pt, threshold=0.0, outside_returns_none=True,
                  index_only = True):
        if not index_only:
            raise NotImplementedError("Candle Plots only support index_only map_index()")
        if len(screen_pt) == 0:
            return None

        # Find the closest index point using numpy
        index_data = self.index.get_data()
        if len(index_data) == 0:
            return None

        target_data = self.index_mapper.map_data(screen_pt[0])

        index = searchsorted(index_data, [target_data])[0]
        if index == len(index_data):
            index -= 1
        # Bracket index and map those points to screen space, then
        # compute the distance
        if index > 0:
            lower = index_data[index-1]
            upper = index_data[index]
            screen_low, screen_high = self.index_mapper.map_screen(array([lower, upper]))
            # Find the closest index
            low_dist = abs(screen_pt[0] - screen_low)
            high_dist = abs(screen_pt[0] - screen_high)
            if low_dist < high_dist:
                index = index - 1
                dist = low_dist
            else:
                dist = high_dist
            # Determine if we need to check the threshold
            if threshold > 0 and dist >= threshold:
                return None
            else:
                return index
        else:
            screen = self.index_mapper.map_screen(index_data[0])
            if threshold > 0 and abs(screen - screen_pt[0]) >= threshold:
                return None
            else:
                return index

    def _gather_points(self):
        index = self.index.get_data()
        mask = broaden(self.index_range.mask_data(index))

        if not mask.any():
            self._cached_data_pts = []
            self._cache_valid = True
            return

        data_pts = [compress(mask, index)]

        for v in (self.min_values, self.bar_min, self.center_values, self.bar_max, self.max_values):
            if v is None or len(v.get_data()) == 0:
                data_pts.append(None)
            else:
                data_pts.append(compress(mask, v.get_data()))

        self._cached_data_pts = data_pts
        self._cache_valid = True

    def _draw_plot(self, gc, view_bounds=None, mode="normal"):
        self._gather_points()
        if len(self._cached_data_pts) == 0:
            return

        index = self.index_mapper.map_screen(self._cached_data_pts[0])
        if len(index) == 0:
            return

        vals = []
        for v in self._cached_data_pts[1:]:
            if v is None:
                vals.append(None)
            else:
                vals.append(self.value_mapper.map_screen(v))

        # Compute lefts and rights from self.index, which represents bin
        # centers.
        if len(index) == 1:
            width = 5.0
        else:
            width = (index[1:] - index[:-1]).min() / 2.5
        left = index - width
        right = index + width

        with gc:
            gc.clip_to_rect(self.x, self.y, self.width, self.height)
            self._render(gc, left, right, *vals)

    def _get_value(self):
        if self.center_values is not None:
            return self.center_values
        elif self.bar_min is not None:
            return self.bar_min
        elif self.bar_max is not None:
            return self.bar_max
Exemple #12
0
class ExperimentDialogHandler(Controller):

    # bits for model initialization
    import_op = Instance(
        'cytoflowgui.workflow.operations.import_op.ImportWorkflowOp')

    # events
    add_tubes = Event
    remove_tubes = Event
    add_variable = Event
    import_csv = Event

    # traits to communicate with the TabularEditor
    selected_tubes = List

    default_view = View(HGroup(
        VGroup(
            Label("Variables"),
            Item('tube_traits',
                 editor=VerticalListEditor(editor=InstanceEditor(),
                                           style='custom',
                                           mutable=False,
                                           deletable=True),
                 show_label=False),
            HGroup(
                Item('handler.add_variable',
                     editor=ButtonEditor(label="Add a variable"),
                     show_label=False))),
        VGroup(
            Label("Tubes"),
            Item(name='tubes',
                 id='table',
                 editor=TableEditor(editable=True,
                                    sortable=True,
                                    auto_size=True,
                                    configurable=False,
                                    selection_mode='rows',
                                    selected='handler.selected_tubes',
                                    columns=[
                                        ObjectColumn(
                                            name='index',
                                            read_only_cell_color='light grey',
                                            editable=False)
                                    ]),
                 enabled_when="object.tubes",
                 show_label=False),
            HGroup(
                Item('handler.add_tubes',
                     editor=ButtonEditor(label="Add tubes..."),
                     show_label=False),
                Item('handler.remove_tubes',
                     editor=ButtonEditor(label="Remove tubes"),
                     show_label=False,
                     enabled_when='object.tubes'),
                Item('handler.import_csv',
                     editor=ButtonEditor(label="Import from CSV..."),
                     show_label=False)))),
                        title='Experiment Setup',
                        buttons=OKCancelButtons,
                        resizable=True,
                        width=0.3,
                        height=0.3)

    # keep a ref to the table editor so we can add columns dynamically
    table_editor = Instance(TableEditorQt)

    updating = Bool(False)

    def init(self, info):

        # save a reference to the table editor
        self.table_editor = info.ui.get_editors('tubes')[0]

        # init the model
        self.model.init(self.import_op)

        return True

    def close(self, info, is_ok):
        """ Handles the user attempting to close a dialog-based user interface.

        This method is called when the user attempts to close a window, by
        clicking an **OK** or **Cancel** button, or clicking a Close control
        on the window). It is called before the window is actually destroyed.
        Override this method to perform any checks before closing a window.

        While Traits UI handles "OK" and "Cancel" events automatically, you
        can use the value of the *is_ok* parameter to implement additional
        behavior.

        Parameters
        ----------
        info : UIInfo object
            The UIInfo object associated with the view
        is_ok : Boolean
            Indicates whether the user confirmed the changes (such as by
            clicking **OK**.)

        Returns
        -------
        allow_close : bool
            A Boolean, indicating whether the window should be allowed to close.
        """
        if is_ok:
            if not self.model.valid:
                error(
                    None,
                    "Each tube must have a unique set of experimental conditions",
                    "Invalid experiment!")
                return False

        if not is_ok:
            # we don't need to "undo" anything, we're throwing this model away
            info.ui.history = None

        return True

    def closed(self, info, is_ok):
        for trait in self.model.tube_traits:
            if trait.type != 'metadata':
                for tube in self.model.tubes:
                    tube.on_trait_change(self._try_multiedit,
                                         trait.name,
                                         remove=True)
        if is_ok:
            self.model.update_import_op(self.import_op)

    @on_trait_change('add_variable')
    def _on_add_variable(self):
        self.model.tube_traits.append(TubeTrait(model=self.model))

    @on_trait_change('import_csv')
    def _on_import(self):
        """
        Import format: CSV, first column is filename, path relative to CSV.
        others are conditions, type is autodetected.  first row is header
        with names.
        """
        file_dialog = FileDialog()
        file_dialog.wildcard = "CSV files (*.csv)|*.csv|"
        file_dialog.action = 'open'
        file_dialog.open()

        if file_dialog.return_code != PyfaceOK:
            return

        csv = pandas.read_csv(file_dialog.path)
        csv_folder = Path(file_dialog.path).parent

        if self.model.tubes or self.model.tube_traits:
            if confirm(
                    parent=None,
                    message="This will clear the current conditions and tubes! "
                    "Are you sure you want to continue?",
                    title="Clear tubes and conditions?") != YES:
                return

        for col in csv.columns[1:]:
            self.model.tube_traits.append(
                TubeTrait(model=self.model,
                          name=util.sanitize_identifier(col),
                          type='category'))

        for _, row in csv.iterrows():
            filename = csv_folder / row[0]

            try:
                metadata, _ = parse_tube(str(filename), metadata_only=True)
            except Exception as e:
                warning(
                    None,
                    "Had trouble loading file {}: {}".format(filename, str(e)))
                continue

            metadata['CF_File'] = Path(filename).stem
            new_tube = Tube(file=str(filename),
                            parent=self.model,
                            metadata=sanitize_metadata(metadata))
            self.model.tubes.append(new_tube)

            for col in csv.columns[1:]:
                new_tube.trait_set(**{util.sanitize_identifier(col): row[col]})

    @on_trait_change('add_tubes')
    def _on_add_tubes(self):
        """
        Handle "Add tubes..." button.  Add tubes to the experiment.
        """

        file_dialog = FileDialog()
        file_dialog.wildcard = "Flow cytometry files (*.fcs *.lmd)|*.fcs *.lmd|"
        file_dialog.action = 'open files'
        file_dialog.open()

        if file_dialog.return_code != PyfaceOK:
            return

        for path in file_dialog.paths:
            try:
                metadata, _ = parse_tube(path, metadata_only=True)
            except Exception as e:
                raise RuntimeError("FCS reader threw an error on tube {0}: {1}"\
                                   .format(path, e.value))

            # if we're the first tube loaded, create a dummy experiment
            # and setup default metadata columns
            if not self.model.dummy_experiment:
                self.model.dummy_experiment = \
                    ImportOp(tubes = [CytoflowTube(file = path)]).apply(metadata_only = True)

            # check the next tube against the dummy experiment
            try:
                check_tube(path, self.model.dummy_experiment)
            except util.CytoflowError as e:
                error(None, e.__str__(), "Error importing tube")
                return

            metadata['CF_File'] = Path(path).stem
            tube = Tube(file=path,
                        parent=self.model,
                        metadata=sanitize_metadata(metadata))

            self.model.tubes.append(tube)

            for trait in self.model.tube_traits:
                if trait.type != 'metadata' and trait.name:
                    tube.on_trait_change(self._try_multiedit, trait.name)

    @on_trait_change('remove_tubes')
    def _on_remove_tubes(self):
        conf = confirm(
            None, "Are you sure you want to remove the selected tube(s)?",
            "Remove tubes?")
        if conf == YES:
            for tube in self.selected_tubes:
                self.model.tubes.remove(tube)

        if not self.model.tubes:
            self.model.dummy_experiment = None

    @on_trait_change('model:tube_traits_items', post_init=True)
    def _tube_traits_changed(self, event):
        for trait in event.added:
            if not trait.name:
                continue

            if self.table_editor:
                self.table_editor.columns.append(
                    ExperimentColumn(name=trait.name,
                                     editable=(trait.type != 'metadata')))
            for tube in self.model.tubes:
                if trait.type != 'metadata' and trait.name:
                    tube.on_trait_change(self._try_multiedit, trait.name)

        for trait in event.removed:
            if not trait.name:
                continue

            table_column = next(
                (x for x in self.table_editor.columns if x.name == trait.name))
            self.table_editor.columns.remove(table_column)

            for tube in self.model.tubes:
                if trait.type != 'metadata' and trait.name:
                    tube.on_trait_change(self._try_multiedit,
                                         trait.name,
                                         remove=True)

    @on_trait_change('model:tube_traits:name')
    def _tube_trait_name_changed(self, trait, _, old_name, new_name):
        if old_name:
            old_table_column = next(
                (x for x in self.table_editor.columns if x.name == old_name))
            column_idx = self.table_editor.columns.index(old_table_column)
        else:
            column_idx = len(self.table_editor.columns)

        if new_name:
            self.table_editor.columns.insert(
                column_idx,
                ExperimentColumn(name=new_name,
                                 editable=(trait.type != 'metadata')))

        if old_name:
            self.table_editor.columns.remove(old_table_column)

        for tube in self.model.tubes:
            if trait.type != 'metadata':
                if old_name:
                    tube.on_trait_change(self._try_multiedit,
                                         old_name,
                                         remove=True)

                if new_name:
                    tube.on_trait_change(self._try_multiedit, new_name)

            if old_name:
                tube.remove_trait(old_name)

        self.model.counter.clear()
        for tube in self.model.tubes:
            tube_hash = tube.conditions_hash()
            if tube_hash in self.model.counter:
                self.model.counter[tube_hash] += 1
            else:
                self.model.counter[tube_hash] = 1

    @on_trait_change('model:tube_traits:type')
    def _tube_trait_type_changed(self, trait, _, old_type, new_type):
        if not trait.name:
            return

        table_column = next(
            (x for x in self.table_editor.columns if x.name == trait.name))

        table_column.editable = (new_type != 'metadata')

        for tube in self.model.tubes:
            if trait.name:
                if old_type != 'metadata':
                    tube.on_trait_change(self._try_multiedit,
                                         trait.name,
                                         remove=True)

                if new_type != 'metadata':
                    tube.on_trait_change(self._try_multiedit, trait.name)

    def _try_multiedit(self, obj, name, old, new):
        """
        See if there are multiple elements selected when a tube's trait changes
        
        and if so, edit the same trait for all the selected tubes.
        """

        if self.updating:
            return

        self.updating = True

        for tube in self.selected_tubes:
            if tube != obj:
                old_hash = tube.conditions_hash()
                self.model.counter[old_hash] -= 1
                if self.model.counter[old_hash] == 0:
                    del self.model.counter[old_hash]

                # update the underlying traits without notifying the editor
                # we do this all here for performance reasons
                tube.trait_setq(**{name: new})
                tube.conditions[name] = new

                new_hash = tube.conditions_hash()
                if new_hash not in self.model.counter:
                    self.model.counter[new_hash] = 1
                else:
                    self.model.counter[new_hash] += 1

        # now refresh the editor all at once
        self.table_editor.refresh_editor()
        self.updating = False
Exemple #13
0
class Tube(HasStrictTraits):
    """
    The model for a tube in an experiment.
    
    I originally wanted to make the Tube in the ImportDialog and the Tube in
    the ImportOp the same, but that fell apart when I tried to implement
    serialization (dynamic traits don't survive pickling when sending tubes to 
    the remote process)  (well, their values do, but neither the trait type nor 
    the metadata do.)
    
    Oh well.
        
    This model depends on duck-typing ("if it walks like a duck, and quacks
    like a duck...").  Because we want to use all of the TableEditor's nice
    features, each row needs to be an instance, and each column a Trait.
    So, each Tube instance represents a single tube, and each experimental
    condition (as well as the tube name, its file, and optional plate row and 
    col) are traits. These traits are dynamically added to Tube INSTANCES 
    (NOT THE TUBE CLASS.)  Then, we add appropriate columns to the table editor
    to access these traits.
    
    We also derive traits from tubes' FCS metadata.  One can make a new column
    from metadata, then convert it into a condition to use in the analysis.
    
    We also use the "transient" flag to specify traits that shouldn't be 
    displayed in the editor.  This matches well with the traits that
    every HasTraits-derived class has by default (all of which are transient.)
    """

    # these are the traits that every tube has.  every other trait is
    # dynamically created.

    # the tube index
    index = Property(Int)

    # the file name.
    file = Str(transient=True)

    # need a link to the model; needed for row coloring
    parent = Instance("ExperimentDialogModel", transient=True)

    # needed for fast hashing
    conditions = Dict

    # metadata from the FCS file
    metadata = Dict

    all_conditions_set = Property(Bool, depends_on="conditions")

    def conditions_hash(self):
        ret = int(0)

        for key, value in self.conditions.items():
            if not ret:
                ret = hash((key, value))
            else:
                ret = ret ^ hash((key, value))

        return ret

    def _anytrait_changed(self, name, old, new):
        if self.parent is not None and name in self.parent.tube_traits_dict and self.parent.tube_traits_dict[
                name].type != 'metadata':
            old_hash = self.conditions_hash()
            self.conditions[name] = new
            new_hash = self.conditions_hash()

            if old_hash in self.parent.counter:
                self.parent.counter[old_hash] -= 1

                if self.parent.counter[old_hash] == 0:
                    del self.parent.counter[old_hash]

                if new_hash not in self.parent.counter:
                    self.parent.counter[new_hash] = 1
                else:
                    self.parent.counter[new_hash] += 1

    @cached_property
    def _get_all_conditions_set(self):
        return len([
            x for x in list(self.conditions.values()) if x is None or x == ""
        ]) == 0

    # magic - gets the value of the 'index' property
    def _get_index(self):
        return self.parent.tubes.index(self)
Exemple #14
0
class ExperimentDialogModel(HasStrictTraits):
    """
    The model for the Experiment setup dialog.
    """

    # the list of Tubes (rows in the table)
    tubes = List(Tube)

    # a list of the traits that have been added to Tube instances
    # (columns in the table)
    tube_traits = List(TubeTrait)
    tube_traits_dict = Dict

    # keeps track of whether a tube is unique or not
    counter = Dict(Int, Int)

    # are all the tubes unique and filled?
    valid = Property(List)

    # a dummy Experiment, with the first Tube and no events, so we can check
    # subsequent tubes for voltage etc. and fail early.
    dummy_experiment = Instance(Experiment)

    # traits to communicate with the traits_view
    fcs_metadata = Property(List, depends_on='tubes')

    def init(self, import_op):

        if 'CF_File' not in import_op.conditions:
            self.tube_traits.append(
                TubeTrait(model=self, type='metadata', name='CF_File'))

        for name, condition in import_op.conditions.items():
            if condition == "category" or condition == "object":
                self.tube_traits.append(
                    TubeTrait(model=self, name=name, type='category'))
            elif condition == "int" or condition == "float":
                self.tube_traits.append(
                    TubeTrait(model=self, name=name, type='float'))
            elif condition == "bool":
                self.tube_traits.append(
                    TubeTrait(model=self, name=name, type='bool'))

        self.dummy_experiment = None

        if import_op.tubes:
            try:
                self.dummy_experiment = import_op.apply(metadata_only=True,
                                                        force=True)
            except Exception as e:
                warning(
                    None, "Had trouble loading some of the experiment's FCS "
                    "files.  You will need to re-add them.\n\n{}".format(
                        str(e)))
                return

            for op_tube in import_op.tubes:
                metadata = self.dummy_experiment.metadata['fcs_metadata'][
                    op_tube.file]
                tube = Tube(file=op_tube.file,
                            parent=self,
                            metadata=sanitize_metadata(metadata))

                self.tubes.append(tube)  # adds the dynamic traits to the tube

                tube.trait_set(**op_tube.conditions)

                for trait in self.tube_traits:
                    if trait.type == 'metadata':
                        tube.trait_set(
                            **{trait.name: tube.metadata[trait.name]})
                    else:
                        tube.conditions[trait.name] = tube.trait_get()[
                            trait.name]

    @on_trait_change('tubes_items')
    def _tubes_items(self, event):
        for tube in event.added:
            for trait in self.tube_traits:
                if not trait.name:
                    continue

                tube.add_trait(trait.name, trait.trait)

                if trait.type == 'metadata':
                    tube.trait_set(**{trait.name: tube.metadata[trait.name]})
                else:
                    tube.trait_set(**{trait.name: trait.trait.default_value})
                    tube.conditions[trait.name] = tube.trait_get()[trait.name]

        self.counter.clear()
        for tube in self.tubes:
            tube_hash = tube.conditions_hash()
            if tube_hash in self.counter:
                self.counter[tube_hash] += 1
            else:
                self.counter[tube_hash] = 1

    @on_trait_change('tube_traits_items')
    def _tube_traits_changed(self, event):
        for trait in event.added:
            if not trait.name:
                continue

            for tube in self.tubes:
                tube.add_trait(trait.name, trait.trait)

                if trait.type == 'metadata':
                    tube.trait_set(**{trait.name: tube.metadata[trait.name]})
                else:
                    tube.trait_set(**{trait.name: trait.trait.default_value})
                    tube.conditions[trait.name] = tube.trait_get()[trait.name]

            self.tube_traits_dict[trait.name] = trait

        for trait in event.removed:
            if not trait.name:
                continue

            for tube in self.tubes:
                tube.remove_trait(trait.name)

                if trait.type != 'metadata':
                    del tube.conditions[trait.name]

            del self.tube_traits_dict[trait.name]

        self.counter.clear()
        for tube in self.tubes:
            tube_hash = tube.conditions_hash()
            if tube_hash in self.counter:
                self.counter[tube_hash] += 1
            else:
                self.counter[tube_hash] = 1

    @on_trait_change('tube_traits:name')
    def _on_trait_name_change(self, trait, _, old_name, new_name):
        for tube in self.tubes:
            trait_value = None

            if old_name:
                # store the value
                trait_value = tube.trait_get()[old_name]

                if trait.type != 'metadata':
                    del tube.conditions[old_name]

                # defer removing the old trait until the handler
                # tube.remove_trait(old_name)

            if new_name:
                if new_name in tube.metadata:
                    trait_value = tube.metadata[new_name]
                elif trait_value is None:
                    trait_value = trait.trait.default_value

                tube.add_trait(new_name, trait.trait)
                tube.trait_set(**{new_name: trait_value})

                if trait.type != 'metadata':
                    tube.conditions[new_name] = trait_value

        if old_name:
            del self.tube_traits_dict[old_name]

        if new_name:
            self.tube_traits_dict[new_name] = trait

        self.counter.clear()
        for tube in self.tubes:
            tube_hash = tube.conditions_hash()
            if tube_hash in self.counter:
                self.counter[tube_hash] += 1
            else:
                self.counter[tube_hash] = 1

    @on_trait_change('tube_traits:type')
    def _on_type_change(self, trait, name, old_type, new_type):
        if not trait.name:
            return

        for tube in self.tubes:
            trait_value = tube.trait_get()[trait.name]
            tube.remove_trait(trait.name)
            if old_type != 'metadata':
                del tube.conditions[trait.name]

            tube.add_trait(trait.name, trait.trait)

            try:
                tube.trait_set(**{trait.name: trait_value})
            except TraitError:
                tube.trait_set(**{trait.name: trait.trait.default_value})
            if new_type != 'metadata':
                tube.conditions[trait.name] = tube.trait_get()[trait.name]

        self.counter.clear()
        for tube in self.tubes:
            tube_hash = tube.conditions_hash()
            if tube_hash in self.counter:
                self.counter[tube_hash] += 1
            else:
                self.counter[tube_hash] = 1

    def update_import_op(self, import_op):
        if not self.tubes:
            return

        assert self.dummy_experiment is not None

        conditions = {
            trait.name: trait.type
            for trait in self.tube_traits if trait.type != 'metadata'
        }

        tubes = []
        events = 0
        for tube in self.tubes:
            op_tube = CytoflowTube(file=tube.file,
                                   conditions=tube.trait_get(
                                       list(conditions.keys())))
            tubes.append(op_tube)
            events += tube.metadata['TOT']

        import_op.ret_events = events

        import_op.conditions = conditions
        import_op.tubes = tubes
        import_op.original_channels = channels = self.dummy_experiment.channels

        all_present = len(import_op.channels_list) > 0
        if len(import_op.channels_list) > 0:
            for c in import_op.channels_list:
                if c.name not in channels:
                    all_present = False

            if not all_present:
                warning(
                    None, "Some of the operation's channels weren't found in "
                    "these FCS files.  Resetting all channel names.",
                    "Resetting channel names")

        if not all_present:
            import_op.reset_channels()

    def is_tube_unique(self, tube):
        tube_hash = tube.conditions_hash()
        if tube_hash in self.counter:
            return self.counter[tube.conditions_hash()] == 1
        else:
            return False

    def _get_valid(self):
        return len(self.tubes) > 0 and \
               len(set(self.counter)) == len(self.tubes) and \
               all([x.all_conditions_set for x in self.tubes])

    # magic: gets the list of FCS metadata for the trait list editor
    @cached_property
    def _get_fcs_metadata(self):
        meta = {}
        for tube in self.tubes:
            for name, val in tube.metadata.items():
                if name not in meta:
                    meta[name] = set()

                meta[name].add(val)

        ret = [x for x in meta.keys() if len(meta[x]) > 1]

        return sorted(ret)
Exemple #15
0
class BaseMagnet(HasTraits):
    dac = Property(Float, depends_on='_dac')
    mass = Float

    _dac = Float
    dacmin = Float(0.0)
    dacmax = Float(10.0)

    massmin = Property(Float, depends_on='_massmin')
    massmax = Property(Float, depends_on='_massmax')
    _massmin = Float(0.0)
    _massmax = Float(200.0)

    settling_time = 0.5
    detector = Instance('pychron.spectrometer.base_detector.BaseDetector')

    dac_changed = Event

    mftable = Instance('pychron.spectrometer.mftable.MagnetFieldTable', ())
    confirmation_threshold_mass = Float
    use_deflection_correction = True

    _suppress_mass_update = False

    def set_dac(self, *args, **kw):
        raise NotImplementedError

    def set_mftable(self, name):
        self.mftable.set_path_name(name)

    def update_field_table(self, *args):
        self.mftable.update_field_table(*args)

    # ===============================================================================
    # persistence
    # ===============================================================================
    def load(self):
        pass

    def finish_loading(self):
        """
        initialize the mftable

        read DAC from device
        :return:
        """
        if self.spectrometer:
            molweights = self.spectrometer.molecular_weights
            name = self.spectrometer.name
        else:
            from pychron.spectrometer.molecular_weights import MOLECULAR_WEIGHTS as molweights

            name = ''
        # self.mftable.molweights = molweights
        self.mftable.initialize(molweights)
        self.mftable.spectrometer_name = name.lower()

        d = self.read_dac()
        if d is not None:
            self._dac = d

    # ===============================================================================
    # mapping
    # ===============================================================================
    def map_dac_to_mass(self, dac, detname):
        """
        convert a DAC value (voltage) to mass for a given detector
        use the mftable

        :param dac: float, voltage (0-10V)
        :param detname: str, name of a detector, e.g H1
        :return: float, mass
        """
        from pychron.spectrometer.mftable import get_detector_name, mass_cal_func
        detname = get_detector_name(detname)

        d = self.mftable.get_table()

        _, xs, ys, p = d[detname]

        def func(x, *args):
            c = list(p)
            c[-1] -= dac
            return mass_cal_func(c, x)

        try:
            mass = optimize.brentq(func, 0, 200)
            return mass

        except ValueError, e:
            import traceback
            traceback.print_exc()
            self.debug(
                'DAC does not map to an isotope. DAC={}, Detector={}'.format(
                    dac, detname))
class Product(ChromatographyData):
    """Represents a product, which is made up of components, impurities, and
    amino acids.

    The product stores the expressions needed to compute the concentration of
    each component, which are used by :class:`SolutionWithProduct` for the
    actual computation, since the solution's overall concentration is needed.
    """

    # -------------------------------------------------------------------------
    # Product traits
    # -------------------------------------------------------------------------

    #: User notes about the product's origin, purpose, ...
    description = Str

    #: The type of product
    product_type = Key()

    #: List of components this product is composed of
    product_components = List(Instance(ProductComponent))

    #: List of component names. Built from the product_components list
    product_component_names = Property(List(Str),
                                       depends_on='product_components')

    #: List of assay names to compute the concentrations of each component
    product_component_assays = List(Str)

    #: Expressions to compute the concentration of each product_component
    #: given the product's concentration and assays results. This should be a
    #: Pandas Series indexed on the component names, and containing string
    #: expressions. Each string should be an eval-able expression involving the
    #: 'product_component_assays' as well as the total `product_concentration`.
    #: (e.g. 'product_concentration * CEX_Acidic_2 / 100')
    product_component_concentration_exps = Instance(pd.Series)

    #: Expressions to compute the assay of each product_component
    #: given the product's component concentration. Each string
    #: should be a valid Python expression that can use the names
    #: in the 'product_component_names' attribute
    #: as well as the name `product_concentration`
    product_component_assay_exps = Property(
        Instance(List(Str)),
        depends_on='product_component_concentration_exps'
    )

    #: List of impurities in the product
    impurity_names = List(Str)

    #: List of the assay names for each impurity
    impurity_assays = List(Str)

    #: Expressions to compute the concentration of each impurity
    #: given the product's concentration and impurity assays. This should be a
    #: Pandas Series indexed on the component names, and containing string
    #: expressions. Each string should be an eval-able expression that can use
    #: the names in the 'impurity_assays' attribute as well as the name
    #: `product_concentration`.
    impurity_concentration_exps = Instance(pd.Series)

    #: Expressions to compute the assay of each product_component given the
    #: product's component concentration. Each string should be a valid Python
    #: expression that can use the names in the 'product_component_names'
    #: attribute as well as the name `product_concentration`
    impurity_assay_exps = Property(
        Instance(List(Str)),
        depends_on='impurity_concentration_exps'
    )

    #: Iso-electric point of the product (in pH unit)
    pI = Instance(UnitScalar)

    # FIXME: Later we may want to consider making a AminoAcid class (issue #33)
    #: List of titrating amino acids in the product
    amino_acids = List(Str)

    # FIXME: Later we may want to consider making a AminoAcid class (issue #33)
    #: Number of each amino acid in the product
    amino_acid_numbers = List(Int)

    #: Number of components the product contains
    num_components = Property(Int, depends_on='product_components')

    # -------------------------------------------------------------------------
    # ChromatographyData traits
    # -------------------------------------------------------------------------

    #: The type of data being represented by this class.
    type_id = Constant(PRODUCT_TYPE)

    #: How to identify a particular product
    _unique_keys = Tuple(('name',))

    # -------------------------------------------------------------------------
    # Product Methods
    # -------------------------------------------------------------------------

    def __init__(self, **traits):
        # Convert lists of str concentration expression to series if needed
        if "product_component_concentration_exps" in traits:
            values = traits["product_component_concentration_exps"]
            if isinstance(values, list):
                index = [comp.name for comp in traits["product_components"]]
                traits["product_component_concentration_exps"] = \
                    pd.Series(values, index=index)

        if "impurity_concentration_exps" in traits:
            values = traits["impurity_concentration_exps"]
            if isinstance(values, list):
                traits["impurity_concentration_exps"] = \
                    pd.Series(values, index=traits["impurity_names"])

        super(Product, self).__init__(**traits)

        # If the component's target product wasn't set, it is set here.
        # Otherwise, its target product is checked:
        for comp in self.product_components:
            if comp.target_product == "":
                comp.target_product = self.name
            elif comp.target_product != self.name:
                msg = "Trying to build product {} with component {} " \
                      "targeting product {}."
                msg = msg.format(self.name, comp.name, comp.target_product)
                logger.exception(msg)
                raise ValueError(msg)

        if traits["name"] == BLANK_PRODUCT_NAME:
            return

        #: check for each set of attributes, the same number was given
        component_attrs = [self.product_components,
                           self.product_component_concentration_exps]
        impurity_attrs = [self.impurity_names, self.impurity_assays,
                          self.impurity_concentration_exps]
        amino_acid_attrs = [self.amino_acids,
                            self.amino_acid_numbers]
        for group in [component_attrs, impurity_attrs, amino_acid_attrs]:
            self._check_lists_lengths(group)

        # Check each expression in product_component_concentration_exps and
        # impurity_concentration_exps
        self._check_expressions()

        self._sanitize_component_names()

    def get_component_with_name(self, name):
        """ Returns component with specified name.

        Raises
        ------
        KeyError:
            If the name requested isn't a valid component name.
        """
        all_names = []
        for comp in self.product_components:
            all_names.append(comp.name)
            if comp.name == name:
                return comp

        msg = "No product component with name {} in product {}. Available " \
              "components names are: {}".format(name, self.name, all_names)
        logger.exception(msg)
        raise KeyError(msg)

    # Private interface -------------------------------------------------------

    def _sanitize_component_names(self):
        """ Change component/impurity names that would break the sympy solver.
        """
        for comp in self.product_components:
            if comp.name in BAD_COMPONENT_NAMES:
                comp.name = BAD_COMPONENT_NAMES[comp.name]

        for i, impurity_name in enumerate(self.impurity_names):
            if impurity_name in BAD_COMPONENT_NAMES:
                self.impurity_names[i] = BAD_COMPONENT_NAMES[comp.name]

    def _check_lists_lengths(self, lists):
        """ Raises ValueError if lists not all same length
        """
        if len(lists) == 0:
            return
        first_length = len(lists[0])
        if any(len(l) != first_length for l in lists):
            msg = ("Attempting to create product {} with lists of different "
                   "lengths ({}).".format(self.name, lists))
            logger.exception(msg)
            raise ValueError(msg)

    def _check_expressions(self):
        """ Check that component concentration expressions to be eval-able.

        Raises
        ------
        ValueError:
            If expressions inside the object's '*_exps' attrs are not valid
            python expressions involving the 'product_concentration' and the
            product's assays.
        """
        for comp_group in COMPONENT_GROUPS:
            exps = getattr(self, comp_group + '_concentration_exps')
            if len(exps) == 0:
                continue
            valid_names = [PROD_CONCENTRATION_VARNAME]
            valid_names += getattr(self, comp_group + '_assays')
            namespace = {name: 1 for name in valid_names}
            for exp in exps:
                try:
                    eval(exp, namespace)
                except NameError as e:
                    msg = "Concentration expression {} is invalid. Does it " \
                          "only involve operations, and the known product " \
                          "concentration and  assays ({})? (Original error " \
                          "was {}.)".format(exp, valid_names, e)
                    logger.exception(msg)
                    raise ValueError(msg)

    # Traits property getters/setters -----------------------------------------

    def _get_assay_exps(self, component_group):
        """ Returns a list of expressions representing the inverse
        'component_group'_concentration_exps (i.e. solved for
        'component_group'_assays)
        """
        expressions = getattr(self, component_group + "_concentration_exps")
        if expressions is None:
            return

        names = getattr(self, component_group + "_names")
        concentration_exps_lhs = [exp + ' - ' + comp for (exp, comp) in
                                  zip(list(expressions), names)]

        unknown_variables = [var(variable) for variable in
                             getattr(self, component_group + "_assays")]
        try:
            temp_assay_exps = solve(concentration_exps_lhs, unknown_variables,
                                    warn=True)
        except TypeError as e:
            msg = ("Sympy's solve function failed with error {}. It could be "
                   "due to a badly chosen component/impurity name that "
                   "collides during solver execution. Expressions are {}.")
            msg = msg.format(e, concentration_exps_lhs)
            logger.exception(msg)
            raise ExpressionComputationError(msg)

        if isinstance(temp_assay_exps, dict):
            assay_exps = \
                [str(temp_assay_exps[var(assay)])
                 for assay in getattr(self, component_group + "_assays")]
        else:
            assay_exps = [str(exp) for exp in temp_assay_exps]

        return assay_exps

    @cached_property
    def _get_product_component_assay_exps(self):
        return self._get_assay_exps(component_group="product_component")

    @cached_property
    def _get_impurity_assay_exps(self):
        return self._get_assay_exps(component_group="impurity")

    @cached_property
    def _get_num_components(self):
        return len(self.product_components)

    @cached_property
    def _get_product_component_names(self):
        return [comp.name for comp in self.product_components]

    # Traits initialization method --------------------------------------------

    def _impurity_concentration_exps_default(self):
        return pd.Series([])

    def _product_component_concentration_exps_default(self):
        return pd.Series([])
Exemple #17
0
class PipelineEngine(Loggable):
    dvc = Instance('pychron.dvc.dvc.DVC')
    browser_model = Instance(
        'pychron.envisage.browser.base_browser_model.BaseBrowserModel')
    interpreted_age_browser_model = Instance(
        'pychron.envisage.browser.base_browser_model.BaseBrowserModel')
    pipeline = Instance(Pipeline, ())
    selected = Instance(BaseNode, ())
    dclicked = Event
    active_editor = Event
    active_inspector_item = Instance(BaseInspectorItem, ())
    selected_editor = Any

    # unknowns = List
    # references = List
    run_needed = Event
    refresh_all_needed = Event
    update_needed = Event
    refresh_table_needed = Event

    # show_group_colors = Bool

    selected_pipeline_template = Str
    available_pipeline_templates = List

    selected_unknowns = List
    selected_references = List
    dclicked_unknowns = Event
    dclicked_references = Event

    recall_analyses_needed = Event
    reset_event = Event

    tag_event = Event
    invalid_event = Event
    recall_event = Event

    state = Instance(EngineState)

    def __init__(self, *args, **kw):
        super(PipelineEngine, self).__init__(*args, **kw)

        self._load_predefined_templates()

    def drop_factory(self, items):

        return self.dvc.make_analyses(items)

    def reset(self):
        # for ni in self.pipeline.nodes:
        #     ni.visited = False
        self.pipeline.reset()
        self.update_needed = True

    def update_detectors(self):
        """
        set valid detectors for FitICFactorNodes

        """
        for p in self.pipeline.nodes:
            if isinstance(p, FitICFactorNode):
                udets = {
                    iso.detector
                    for ai in p.unknowns for iso in ai.isotopes.itervalues()
                }
                rdets = {
                    iso.detector
                    for ai in p.references for iso in ai.isotopes.itervalues()
                }

                p.set_detectors(list(udets.union(rdets)))

    def get_unknowns_node(self):
        nodes = self.get_nodes(UnknownNode)
        if nodes:
            return nodes[0]

    def get_nodes(self, klass):
        return [n for n in self.pipeline.nodes if n.__class__ == klass]

    def unknowns_clear_all_grouping(self):
        self._set_grouping(self.selected.unknowns, 0)

    def unknowns_clear_grouping(self):
        items = self.selected_unknowns
        if not items:
            items = self.selected.unknowns

        self._set_grouping(items, 0)

    def unknowns_group_by_selected(self):
        items = self.selected.unknowns
        max_gid = max([si.group_id for si in items]) + 1

        self._set_grouping(self.selected_unknowns, max_gid)

    def _set_grouping(self, items, gid):
        for si in items:
            si.group_id = gid

        if hasattr(self.selected, 'editor') and self.selected.editor:
            self.selected.editor.refresh_needed = True
        self.refresh_table_needed = True

    def recall_unknowns(self):
        self.debug('recall unks')
        self.recall_analyses_needed = self.selected_unknowns

    def recall_references(self):
        self.debug('recall refs')
        self.recall_analyses_needed = self.selected_references

    def review_node(self, node):
        node.reset()
        # self.run_needed = True
        # if node.review():
        #     self.run_needed = True

    def configure(self, node):
        node.configure()
        osel = self.selected
        self.update_needed = True
        self.selected = osel

    def remove_node(self, node):
        self.pipeline.nodes.remove(node)
        # self.run_needed = True

    def set_template(self, name):
        self.debug('Set template "{}"'.format(name))
        self._set_template(name)

    def get_experiment_ids(self):
        return self.pipeline.get_experiment_ids()

    def set_review_permanent(self, state):
        name = self.selected_pipeline_template
        path, is_user_path = self._get_template_path(name)
        if path:
            with open(path, 'r') as rfile:
                nodes = yaml.load(rfile)

            for i, ni in enumerate(nodes):
                klass = ni['klass']
                if klass == 'ReviewNode':
                    ni['enabled'] = state

            with open(path, 'w') as wfile:
                yaml.dump(nodes, wfile)

            if is_user_path:
                with open(path, 'r') as rfile:
                    paths.update_manifest(name, rfile.read())

    # debugging
    def select_default(self):
        node = self.pipeline.nodes[0]

        self.browser_model.select_project('J-Curve')
        self.browser_model.select_repository('Irradiation-NM-272')
        self.browser_model.select_sample(idx=0)
        records = self.browser_model.get_analysis_records()
        if records:
            analyses = self.dvc.make_analyses(records)
            # print len(records),len(analyses)
            node.unknowns.extend(analyses)
            node._manual_configured = True
            # self.refresh_analyses()

    def add_test_filter(self):
        node = self.pipeline.nodes[-1]
        newnode = FilterNode()
        filt = newnode.filters[0]
        filt.attribute = 'uage'
        filt.comparator = '<'
        filt.criterion = '10'

        self.pipeline.add_after(node, newnode)

    def clear(self):
        self.unknowns = []
        self.references = []
        for ni in self.pipeline.nodes:
            ni.clear_data()

            # self.pipeline.nodes = []
            # self.selected_pipeline_template = ''
            # self._set_template(self.selected_pipeline_template)

    # ============================================================================================================
    # nodes
    # ============================================================================================================
    # data

    def add_data(self, node=None, run=False):
        """

        add a default data node
        :return:
        """

        self.debug('add data node')
        newnode = UnknownNode(dvc=self.dvc, browser_model=self.browser_model)
        node = self._get_last_node(node)
        self.pipeline.add_after(node, newnode)

    def add_references(self, node=None, run=False):
        newnode = ReferenceNode(name='references',
                                dvc=self.dvc,
                                browser_model=self.browser_model)
        node = self._get_last_node(node)
        self.pipeline.add_after(node, newnode)

    # preprocess
    def add_filter(self, node=None, run=True):
        newnode = FilterNode()
        self._add_node(node, newnode, run)

    def add_grouping(self, node=None, run=True):
        newnode = GroupingNode()
        self._add_node(node, newnode, run)

    # find
    def add_find_airs(self, node=None, run=True):
        self._add_find_node(node, run, 'air')

    def add_find_blanks(self, node=None, run=True):
        self._add_find_node(node, run, 'blank_unknown')

    # figures
    def add_spectrum(self, node=None, run=True):
        newnode = SpectrumNode()
        self._add_node(node, newnode, run)

    def add_ideogram(self, node=None, run=True):
        ideo_node = IdeogramNode()
        self._add_node(node, ideo_node, run)

    def add_series(self, node=None, run=True):
        series_node = SeriesNode()
        self._add_node(node, series_node, run)

    # fits
    def add_icfactor(self, node=None, run=True):
        new = FitICFactorNode()
        if new.configure():
            node = self._get_last_node(node)
            self.pipeline.add_after(node, new)
            if new.use_save_node:
                self.add_icfactor_persist(new, run=False)
                # if run:
                #     self.run_needed = True

    def add_blanks(self, node=None, run=True):
        new = FitBlanksNode()
        if new.configure():
            node = self._get_last_node(node)
            self.pipeline.add_after(node, new)
            if new.use_save_node:
                self.add_blanks_persist(new, run=False)
                # if run:
                #     self.run_needed = True

    def add_isotope_evolution(self, node=None, run=True):
        new = FitIsotopeEvolutionNode()
        if new.configure():
            node = self._get_last_node(node)

            self.pipeline.add_after(node, new)
            if new.use_save_node:
                self.add_iso_evo_persist(new, run=False)

                # if run:
                #     self.run_needed = new

    # save
    def add_icfactor_persist(self, node=None, run=True):
        new = ICFactorPersistNode(dvc=self.dvc)
        self._add_node(node, new, run)

    def add_blanks_persist(self, node=None, run=True):
        new = BlanksPersistNode(dvc=self.dvc)
        self._add_node(node, new, run)

    def add_iso_evo_persist(self, node=None, run=True):
        new = IsotopeEvolutionPersistNode(dvc=self.dvc)
        self._add_node(node, new, run)

    def add_pdf_figure(self, node=None, run=True):
        newnode = PDFFigureNode(root='/Users/ross/Sandbox')
        self._add_node(node, newnode, run=run)

    # ============================================================================================================
    def save_pipeline_template(self, path):
        self.info('Saving pipeline to {}'.format(path))
        with open(path, 'w') as wfile:
            obj = self.pipeline.to_template()
            yaml.dump(obj, wfile, default_flow_style=False)

    # def run_persist(self, state):
    #     for node in self.pipeline.iternodes():
    #         if not isinstance(node, (FitNode, PersistNode)):
    #             continue
    #
    #         node.run(state)
    #         if state.canceled:
    #             self.debug('pipeline canceled by {}'.format(node))
    #             return True

    def run_from_pipeline(self):
        if not self.state:
            ret = self.run_pipeline()
        else:
            node = self.selected
            idx = self.pipeline.nodes.index(node)
            idx = max(0, idx - 1)
            node = self.pipeline.nodes[idx]

            ret = self.run_pipeline(run_from=node, state=self.state)
        return ret

    def resume_pipeline(self):
        return self.run_pipeline(state=self.state)

    def refresh_figure_editors(self):
        for ed in self.state.editors:
            if isinstance(ed, FigureEditor):
                ed.refresh_needed = True

    def rerun_with(self, unks, post_run=True):
        if not self.state:
            return

        state = self.state
        state.unknowns = unks

        ost = time.time()
        for idx, node in enumerate(self.pipeline.iternodes(None)):
            if node.enabled:
                with ActiveCTX(node):
                    if not node.pre_run(state, configure=False):
                        self.debug('Pre run failed {}'.format(node))
                        return True

                    node.unknowns = []
                    st = time.time()
                    try:
                        node.run(state)
                        node.visited = True
                        self.selected = node
                    except NoAnalysesError:
                        self.information_dialog('No Analyses in Pipeline!')
                        self.pipeline.reset()
                        return True
                    self.debug('{:02n}: {} Runtime: {:0.4f}'.format(
                        idx, node,
                        time.time() - st))

                    if state.veto:
                        self.debug('pipeline vetoed by {}'.format(node))
                        return

                    if state.canceled:
                        self.debug('pipeline canceled by {}'.format(node))
                        return True

            else:
                self.debug('Skip node {:02n}: {}'.format(idx, node))
        else:
            self.debug('pipeline run finished')
            self.debug('pipeline runtime {}'.format(time.time() - ost))
            if post_run:
                self.post_run(state)
            return True

    def run_pipeline(self, run_from=None, state=None):
        if state is None:
            state = EngineState()
            self.state = state

        ost = time.time()

        start_node = run_from or state.veto
        self.debug('pipeline run started')
        if start_node:
            self.debug('starting at node {} {}'.format(start_node, run_from))
        state.veto = None

        for node in self.pipeline.iternodes(start_node):
            node.visited = False

        for idx, node in enumerate(self.pipeline.iternodes(start_node)):

            if node.enabled:
                node.editor = None

                with ActiveCTX(node):
                    if not node.pre_run(state):
                        self.debug('Pre run failed {}'.format(node))
                        return True

                    st = time.time()
                    try:
                        node.run(state)
                        node.visited = True
                        self.selected = node
                    except NoAnalysesError:
                        self.information_dialog('No Analyses in Pipeline!')
                        self.pipeline.reset()
                        return True
                    self.debug('{:02n}: {} Runtime: {:0.4f}'.format(
                        idx, node,
                        time.time() - st))

                    if state.veto:
                        self.debug('pipeline vetoed by {}'.format(node))
                        return

                    if state.canceled:
                        self.debug('pipeline canceled by {}'.format(node))
                        return True

            else:
                self.debug('Skip node {:02n}: {}'.format(idx, node))
        else:
            self.debug('pipeline run finished')
            self.debug('pipeline runtime {}'.format(time.time() - ost))

            self.post_run(state)

            # self.state = None
            return True

    run = run_pipeline

    def post_run(self, state):
        self.debug('pipeline post run started')
        for idx, node in enumerate(self.pipeline.nodes):
            action = 'skip'
            if node.enabled:
                action = 'post run'
                node.post_run(self, state)

            self.debug('{} node {:02n}: {}'.format(action, idx, node.name))
        self.debug('pipeline post run finished')

        self.update_needed = True
        self.refresh_table_needed = True

    def select_node_by_editor(self, editor):
        for node in self.pipeline.nodes:
            if hasattr(node, 'editor'):
                if node.editor == editor:
                    # print 'selecting',node
                    self.selected = node
                    # self.unknowns = editor.analyses
                    # self.refresh_table_needed = True
                    break

    # private
    def _set_template(self, name):
        self.reset_event = True
        path, _ = self._get_template_path(name)
        pt = PipelineTemplate(name, path)
        pt.render(self.application, self.pipeline, self.browser_model,
                  self.interpreted_age_browser_model, self.dvc)
        self.update_detectors()
        if self.pipeline.nodes:
            self.selected = self.pipeline.nodes[0]

    def _get_template_path(self, name):
        pname = name.replace(' ', '_').lower()
        pname = add_extension(pname, '.yaml')
        path = os.path.join(paths.pipeline_template_dir, pname)
        user_path = False
        if not os.path.isfile(path):
            path = os.path.join(paths.user_pipeline_template_dir, pname)
            user_path = True
            if not os.path.isfile(path):
                self.warning(
                    'Invalid template name "{}". {} does not exist'.format(
                        name, path))
                return

        return path, user_path

    def _load_predefined_templates(self):
        self.debug('load predefined templates')
        templates = []
        user_templates = []

        for temp in list_directory2(paths.pipeline_template_dir,
                                    extension='.yaml',
                                    remove_extension=True):
            templates.append(temp)
        self.debug('loaded {} pychron templates'.format(len(templates)))

        for temp in list_directory2(paths.user_pipeline_template_dir,
                                    extension='.yaml',
                                    remove_extension=True):
            user_templates.append(temp)
        self.debug('loaded {} user templates'.format(len(user_templates)))

        def formatter(t):
            return ' '.join(map(str.capitalize, t.split('_')))

        templates = map(formatter, templates)
        user_templates = map(formatter, user_templates)

        with open(paths.pipeline_template_file, 'r') as rfile:
            tnames = yaml.load(rfile)

        ns = [pt for pt in tnames if pt in templates]
        ns.extend(user_templates)

        self.available_pipeline_templates = ns

    def _add_find_node(self, node, run, analysis_type):
        newnode = FindReferencesNode(dvc=self.dvc, analysis_type=analysis_type)
        if newnode.configure():
            node = self._get_last_node(node)

            self.pipeline.add_after(node, newnode)
            self.add_references(newnode, run=False)

            if run:
                self.run_needed = newnode

    def _add_node(self, node, new, run=True):
        if new.configure():
            node = self._get_last_node(node)

            self.pipeline.add_after(node, new)
            # if run:
            #     self.run_needed = new

    def _get_last_node(self, node=None):
        if node is None:
            if self.pipeline.nodes:
                idx = len(self.pipeline.nodes) - 1

                node = self.pipeline.nodes[idx]
        return node

    # handlers
    def _dclicked_unknowns_changed(self):
        if self.selected_unknowns:
            self.recall_unknowns()

    def _dclicked_references_fired(self):
        if self.selected_references:
            self.recall_references()

    def _selected_pipeline_template_changed(self, new):
        if new:
            self.debug('Pipeline template {} selected'.format(new))
            self._set_template(new)

    def _selected_changed(self, old, new):
        if old:
            old.on_trait_change(self._handle_tag,
                                'unknowns:tag_event,references:tag_event',
                                remove=True)
            old.on_trait_change(
                self._handle_invalid,
                'unknowns:invalid_event,references:invalid_event',
                remove=True)
            old.on_trait_change(
                self._handle_recall,
                'unknowns:recall_event,references:recall_event',
                remove=True)
            old.on_trait_change(self._handle_len_unknowns,
                                'unknowns_items',
                                remove=True)
            old.on_trait_change(self._handle_len_references,
                                'references_items',
                                remove=True)
            old.on_trait_change(self._handle_status,
                                'unknowns:temp_status,references:temp_status',
                                remove=True)

        if new:
            new.on_trait_change(self._handle_tag,
                                'unknowns:tag_event,references:tag_event')
            new.on_trait_change(
                self._handle_invalid,
                'unknowns:invalid_event,references:invalid_event')
            new.on_trait_change(
                self._handle_recall,
                'unknowns:recall_event,references:recall_event')
            new.on_trait_change(self._handle_status,
                                'unknowns:temp_status,references:temp_status')
            new.on_trait_change(self._handle_len_unknowns, 'unknowns_items')
            new.on_trait_change(self._handle_len_references,
                                'references_items')
            # new.on_trait_change(self._handle_unknowns, 'unknowns[]')

        # self.show_group_colors = False
        if isinstance(new, FigureNode):
            # self.show_group_colors = True
            if new.editor:
                editor = new.editor
                self.selected_editor = editor
                self.active_editor = editor

    # _suppress_handle_unknowns = False
    # def _handle_unknowns(self, obj, name, new):
    #     if self._suppress_handle_unknowns:
    #         return
    #
    #     items = obj.unknowns
    #
    #     for i, item in enumerate(items):
    #         if not isinstance(item, DVCAnalysis):
    #             self._suppress_handle_unknowns = True
    #             nitem = self.dvc.make_analyses((item,))[0]
    #             obj.unknowns.pop(i)
    #             obj.unknowns.insert(i, nitem)
    #
    #     self._suppress_handle_unknowns = False
    #     print 'asdfasdfasfsafs', obj, new

    def refresh_unknowns(self, unks, refresh_editor=False):
        self.selected.unknowns = unks
        self.selected.editor.set_items(unks, refresh=refresh_editor)

    _len_unknowns_cnt = 0
    _len_unknowns_removed = 0
    _len_references_cnt = 0
    _len_references_removed = 0

    def _handle_len_unknowns(self, new):
        # self._handle_len('unknowns', lambda e: e.set_items(self.selected.unknowns))
        def func(editor):
            vs = self.selected.unknowns
            editor.set_items(vs)
            self.state.unknowns = vs
            for node in self.pipeline.nodes:
                if isinstance(node, UnknownNode) and node is not self.selected:
                    node.unknowns = vs

        self._handle_len('unknowns', func)

    def _handle_len_references(self, new):
        def func(editor):
            vs = self.selected.references
            editor.set_references(vs)
            self.state.references = vs

            for node in self.pipeline.nodes:
                if isinstance(node,
                              ReferenceNode) and node is not self.selected:
                    node.references = vs

        self._handle_len('references', func)

    def _handle_len(self, k, func):
        lr = '_len_{}_removed'.format(k)
        lc = '_len_{}_cnt'.format(k)

        editor = None
        if hasattr(self.selected, 'editor'):
            editor = self.selected.editor

        if editor:
            n = len(getattr(self, 'selected_{}'.format(k)))
            if not n:
                setattr(self, lc, getattr(self, lc) + 1)

            else:
                setattr(self, lc, 0)
                setattr(self, lr, n)

            if getattr(self, lc) >= getattr(self, lr) or n == 1:
                setattr(self, lc, 0)
                # self._len_references_cnt = 0
                func(editor)

                # editor.set_references(self.selected.references)
                editor.refresh_needed = True

    def _handle_status(self, new):
        self.refresh_table_needed = True

    def _handle_recall(self, new):
        self.recall_event = new

    def _handle_tag(self, new):
        self.tag_event = new

    def _handle_invalid(self, new):
        self.invalid_event = new

    def _dclicked_changed(self, new):
        self.configure(new)
        # self.update_needed = True

    @on_trait_change(
        'selected_editor:figure_model:panels:[figures:[inspector_event]]')
    def _handle_inspector_event(self, obj, name, old, new):
        self.active_inspector_item = new
Exemple #18
0
class BaseXYPlot(AbstractPlotRenderer):
    """ Base class for simple X-vs-Y plots that consist of a single index
    data array and a single value data array.

    Subclasses handle the actual rendering, but this base class takes care of
    most of making sure events are wired up between mappers and data or screen
    space changes, etc.
    """

    #------------------------------------------------------------------------
    # Data-related traits
    #------------------------------------------------------------------------

    #: The data source to use for the index coordinate.
    index = Instance(ArrayDataSource)

    #: The data source to use as value points.
    value = Instance(AbstractDataSource)

    #: Screen mapper for index data.
    index_mapper = Instance(AbstractMapper)
    #: Screen mapper for value data
    value_mapper = Instance(AbstractMapper)


    # Convenience properties that correspond to either index_mapper or
    # value_mapper, depending on the orientation of the plot.

    #: Corresponds to either **index_mapper** or **value_mapper**, depending on
    #: the orientation of the plot.
    x_mapper = Property
    #: Corresponds to either **value_mapper** or **index_mapper**, depending on
    #: the orientation of the plot.
    y_mapper = Property

    #: Convenience property for accessing the index data range.
    index_range = Property
    #: Convenience property for accessing the value data range.
    value_range = Property

    #: The type of hit-testing that is appropriate for this renderer.
    #:
    #: * 'line': Computes Euclidean distance to the line between the
    #:   nearest adjacent points.
    #: * 'point': Checks for adjacency to a marker or point.
    hittest_type = Enum("point", "line")

    #------------------------------------------------------------------------
    # Appearance-related traits
    #------------------------------------------------------------------------

    #: The orientation of the index axis.
    orientation = Enum("h", "v")

    #: Overall alpha value of the image. Ranges from 0.0 for transparent to 1.0
    alpha = Range(0.0, 1.0, 1.0)

    #------------------------------------------------------------------------
    # Convenience readonly properties for common annotations
    #------------------------------------------------------------------------

    #: Read-only property for horizontal grid.
    hgrid = Property
    #: Read-only property for vertical grid.
    vgrid = Property
    #: Read-only property for x-axis.
    x_axis = Property
    #: Read-only property for y-axis.
    y_axis = Property
    #: Read-only property for labels.
    labels = Property


    #------------------------------------------------------------------------
    # Other public traits
    #------------------------------------------------------------------------

    #: Does the plot use downsampling?
    #: This is not used right now.  It needs an implementation of robust, fast
    #: downsampling, which does not exist yet.
    use_downsampling = Bool(False)

    #: Does the plot use a spatial subdivision structure for fast hit-testing?
    #: This makes data updates slower, but makes hit-tests extremely fast.
    use_subdivision = Bool(False)

    #: Overrides the default background color trait in PlotComponent.
    bgcolor = "transparent"

    # This just turns on a simple drawing of the X and Y axes... not a long
    # term solution, but good for testing.

    #: Defines the origin axis color, for testing.
    origin_axis_color = black_color_trait
    #: Defines a the origin axis width, for testing.
    origin_axis_width = Float(1.0)
    #: Defines the origin axis visibility, for testing.
    origin_axis_visible = Bool(False)

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    # Are the cache traits valid? If False, new ones need to be compute.
    _cache_valid = Bool(False)

    # Cached array of (x,y) data-space points; regardless of self.orientation,
    # these points are always stored as (index_pt, value_pt).
    _cached_data_pts = Array

    # Cached array of (x,y) screen-space points.
    _cached_screen_pts = Array

    # Does **_cached_screen_pts** contain the screen-space coordinates
    # of the points currently in **_cached_data_pts**?
    _screen_cache_valid = Bool(False)

    # Reference to a spatial subdivision acceleration structure.
    _subdivision = Any

    #------------------------------------------------------------------------
    # Abstract methods that subclasses must implement
    #------------------------------------------------------------------------

    def _render(self, gc, points):
        """ Abstract method for rendering points.

        Parameters
        ----------
        gc : graphics context
            Target for drawing the points
        points : List of Nx2 arrays
            Screen-space points to render
        """
        raise NotImplementedError

    def _gather_points(self):
        """ Abstract method to collect data points that are within the range of
        the plot, and cache them.
        """
        raise NotImplementedError

    def _downsample(self):
        """ Abstract method that gives the renderer a chance to downsample in
        screen space.
        """
        # By default, this just does a mapscreen and returns the result
        raise NotImplementedError

    #------------------------------------------------------------------------
    # Concrete methods below
    #------------------------------------------------------------------------

    def __init__(self, **kwtraits):
        # Handling the setting/initialization of these traits manually because
        # they should be initialized in a certain order.
        kwargs_tmp = {"trait_change_notify": False}
        for trait_name in ("index", "value", "index_mapper", "value_mapper"):
            if trait_name in kwtraits:
                kwargs_tmp[trait_name] = kwtraits.pop(trait_name)
        self.trait_set(**kwargs_tmp)
        AbstractPlotRenderer.__init__(self, **kwtraits)
        if self.index is not None:
            self.index.on_trait_change(self._either_data_changed, "data_changed")
            self.index.on_trait_change(self._either_metadata_changed, "metadata_changed")
        if self.index_mapper:
            self.index_mapper.on_trait_change(self._mapper_updated_handler, "updated")
        if self.value is not None:
            self.value.on_trait_change(self._either_data_changed, "data_changed")
            self.value.on_trait_change(self._either_metadata_changed, "metadata_changed")
        if self.value_mapper:
            self.value_mapper.on_trait_change(self._mapper_updated_handler, "updated")

        # If we are not resizable, we will not get a bounds update upon layout,
        # so we have to manually update our mappers
        if self.resizable == "":
            self._update_mappers()
        return

    def hittest(self, screen_pt, threshold=7.0, return_distance=False):
        """ Performs proximity testing between a given screen point and the
        plot.

        Parameters
        ----------
        screen_pt : (x,y)
            A point to test.
        threshold : integer
            Optional maximum screen space distance (pixels) between
            *screen_pt* and the plot.
        return_distance : Boolean
            If True, also return the distance.

        Returns
        -------
        If self.hittest_type is 'point', then this method returns the screen
        coordinates of the closest point on the plot as a tuple (x,y)

        If self.hittest_type is 'line', then this method returns the screen
        endpoints of the line segment closest to *screen_pt*, as
        ((x1,y1), (x2,y2))

        If *screen_pt* does not fall within *threshold* of the plot, then this
        method returns None.

        If return_distance is True, return the (x, y, d), where d is the
        distance between the distance between the input point and
        the closest point (x, y), in screen coordinates.
        """
        if self.hittest_type == "point":
            tmp = self.get_closest_point(screen_pt, threshold)
        elif self.hittest_type == "line":
            tmp = self.get_closest_line(screen_pt, threshold)
        else:
            raise ValueError("Unknown hittest type '%s'" % self.hittest_type)

        if tmp is not None:
            if return_distance:
                return tmp
            else:
                return tmp[:-1]
        else:
            return None

    def get_closest_point(self, screen_pt, threshold=7.0):
        """ Tests for proximity in screen-space.

        This method checks only data points, not the line segments connecting
        them; to do the latter use get_closest_line() instead.

        Parameters
        ----------
        screen_pt : (x,y)
            A point to test.
        threshold : integer
            Optional maximum screen space distance (pixels) between
            *screen_pt* and the plot.  If 0.0, then no threshold tests
            are performed, and the nearest point is returned.

        Returns
        -------
        (x, y, distance) of a datapoint nearest to *screen_pt*.
        If no data points are within *threshold* of *screen_pt*, returns None.
        """
        ndx = self.map_index(screen_pt, threshold)
        if ndx is not None:
            x = self.x_mapper.map_screen(self.index.get_data()[ndx])
            y = self.y_mapper.map_screen(self.value.get_data()[ndx])
            return (x, y, sqrt((x-screen_pt[0])**2 + (y-screen_pt[1])**2))
        else:
            return None

    def get_closest_line(self, screen_pt, threshold=7.0):
        """ Tests for proximity in screen-space against lines connecting the
        points in this plot's dataset.

        Parameters
        ----------
        screen_pt : (x,y)
            A point to test.
        threshold : integer
            Optional maximum screen space distance (pixels) between
            the line and the plot.  If 0.0, then the method returns the closest
            line regardless of distance from the plot.

        Returns
        -------
        (x1, y1, x2, y2, dist) of the endpoints of the line segment
        closest to *screen_pt*.  The *dist* element is the perpendicular
        distance from *screen_pt* to the line.  If there is only a single point
        in the renderer's data, then the method returns the same point twice.

        If no data points are within *threshold* of *screen_pt*, returns None.
        """
        ndx = self.map_index(screen_pt, threshold=0.0)
        if ndx is None:
            return None

        index_data = self.index.get_data()
        value_data = self.value.get_data()
        x = self.x_mapper.map_screen(index_data[ndx])
        y = self.y_mapper.map_screen(value_data[ndx])

        # We need to find another index so we have two points; in the
        # even that we only have 1 point, just return that point.
        datalen = len(index_data)
        if datalen == 1:
            dist = (x, y, sqrt((x-screen_pt[0])**2 + (y-screen_pt[1])**2))
            if (threshold == 0.0) or (dist <= threshold):
                return (x, y, x, y, dist)
            else:
                return None
        else:
            if (ndx == 0) or (screen_pt[0] >= x):
                ndx2 = ndx + 1
            elif (ndx == datalen - 1) or (screen_pt[0] <= x):
                ndx2 = ndx - 1
            x2 = self.x_mapper.map_screen(index_data[ndx2])
            y2 = self.y_mapper.map_screen(value_data[ndx2])
            dist = point_line_distance(screen_pt, (x,y), (x2,y2))
            if (threshold == 0.0) or (dist <= threshold):
                return (x, y, x2, y2, dist)
            else:
                return None


    #------------------------------------------------------------------------
    # AbstractPlotRenderer interface
    #------------------------------------------------------------------------

    def map_screen(self, data_array):
        """ Maps an array of data points into screen space and returns it as
        an array.

        Implements the AbstractPlotRenderer interface.
        """
        # data_array is Nx2 array
        if len(data_array) == 0:
            return []

        x_ary, y_ary = transpose(data_array)

        sx = self.index_mapper.map_screen(x_ary)
        sy = self.value_mapper.map_screen(y_ary)
        if self.orientation == "h":
            return transpose(array((sx,sy)))
        else:
            return transpose(array((sy,sx)))

    def map_data(self, screen_pt, all_values=False):
        """ Maps a screen space point into the "index" space of the plot.

        Implements the AbstractPlotRenderer interface.

        If *all_values* is True, returns an array of (index, value) tuples;
        otherwise, it returns only the index values.
        """
        x, y = screen_pt
        if self.orientation == 'v':
                x, y = y, x
        if all_values:
            return array((self.index_mapper.map_data(x),
                          self.value_mapper.map_data(y)))
        else:
            return self.index_mapper.map_data(x)

    def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True,
                  index_only=False):
        """ Maps a screen space point to an index into the plot's index array(s).

        Implements the AbstractPlotRenderer interface.

        Parameters
        ----------
        screen_pt :
            Screen space point

        threshold : float
            Maximum distance from screen space point to plot data point.
            A value of 0.0 means no threshold (any distance will do).

        outside_returns_none : bool
            If True, a screen space point outside the data range returns None.
            Otherwise, it returns either 0 (outside the lower range) or
            the last index (outside the upper range)

        index_only : bool
            If True, the threshold is measured on the distance between the
            index values, otherwise as Euclidean distance between the (x,y)
            coordinates.
        """

        data_pt = self.map_data(screen_pt)
        if ((data_pt < self.index_mapper.range.low) or
            (data_pt > self.index_mapper.range.high)) and outside_returns_none:
            return None
        index_data = self.index.get_data()
        value_data = self.value.get_data()

        if len(value_data) == 0 or len(index_data) == 0:
            return None

        try:
            # find the closest point to data_pt in index_data
            ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order)
        except IndexError:
            # if reverse_map raises this exception, it means that data_pt is
            # outside the range of values in index_data.
            if outside_returns_none:
                return None
            else:
                if data_pt < index_data[0]:
                    return 0
                else:
                    return len(index_data) - 1

        if threshold == 0.0:
            # Don't do any threshold testing
            return ndx

        x = index_data[ndx]
        y = value_data[ndx]
        if isnan(x) or isnan(y):
            return None

        # transform x,y in a 1x2 array, which is the preferred format of
        # map_screen. this makes it robust against differences in
        # the map_screen methods of logmapper and linearmapper
        # when passed a scalar
        xy = array([[x,y]])
        sx, sy = self.map_screen(xy).T
        if index_only and (threshold == 0.0 or screen_pt[0]-sx < threshold):
            return ndx
        elif ((screen_pt[0]-sx)**2 + (screen_pt[1]-sy)**2
              < threshold*threshold):
            return ndx
        else:
            return None

    def get_screen_points(self):
        """Returns the currently visible screen-space points.

        Intended for use with overlays.
        """
        self._gather_points()
        if self.use_downsampling:
            # The BaseXYPlot implementation of _downsample doesn't actually
            # do any downsampling.
            return self._downsample()
        else:
            return self.map_screen(self._cached_data_pts)


    #------------------------------------------------------------------------
    # PlotComponent interface
    #------------------------------------------------------------------------

    def _draw_plot(self, gc, view_bounds=None, mode="normal"):
        """ Draws the 'plot' layer.
        """
        self._draw_component(gc, view_bounds, mode)
        return

    def _draw_component(self, gc, view_bounds=None, mode="normal"):
        # This method should be folded into self._draw_plot(), but is here for
        # backwards compatibilty with non-draw-order stuff.

        pts = self.get_screen_points()
        self._render(gc, pts)
        return

    def _draw_default_axes(self, gc):
        if not self.origin_axis_visible:
            return

        with gc:
            gc.set_stroke_color(self.origin_axis_color_)
            gc.set_line_width(self.origin_axis_width)
            gc.set_line_dash(None)

            for range in (self.index_mapper.range, self.value_mapper.range):
                if (range.low < 0) and (range.high > 0):
                    if range == self.index_mapper.range:
                        dual = self.value_mapper.range
                        data_pts = array([[0.0,dual.low], [0.0, dual.high]])
                    else:
                        dual = self.index_mapper.range
                        data_pts = array([[dual.low,0.0], [dual.high,0.0]])
                    start,end = self.map_screen(data_pts)
                    start = around(start)
                    end = around(end)
                    gc.move_to(int(start[0]), int(start[1]))
                    gc.line_to(int(end[0]), int(end[1]))
                    gc.stroke_path()
        return

    def _post_load(self):
        super(BaseXYPlot, self)._post_load()
        self._update_mappers()
        self.invalidate_draw()
        self._cache_valid = False
        self._screen_cache_valid = False
        return

    def _update_subdivision(self):

        return

    #------------------------------------------------------------------------
    # Properties
    #------------------------------------------------------------------------

    def _get_index_range(self):
        return self.index_mapper.range

    def _set_index_range(self, val):
        self.index_mapper.range = val

    def _get_value_range(self):
        return self.value_mapper.range

    def _set_value_range(self, val):
        self.value_mapper.range = val

    def _get_x_mapper(self):
        if self.orientation == "h":
            return self.index_mapper
        else:
            return self.value_mapper

    def _get_y_mapper(self):
        if self.orientation == "h":
            return self.value_mapper
        else:
            return self.index_mapper

    def _get_hgrid(self):
        for obj in self.underlays+self.overlays:
            if isinstance(obj, PlotGrid) and obj.orientation=="horizontal":
                return obj
        else:
            return None

    def _get_vgrid(self):
        for obj in self.underlays+self.overlays:
            if isinstance(obj, PlotGrid) and obj.orientation=="vertical":
                return obj
        else:
            return None

    def _get_x_axis(self):
        for obj in self.underlays+self.overlays:
            if isinstance(obj, PlotAxis) and obj.orientation in ("bottom", "top"):
                return obj
        else:
            return None

    def _get_y_axis(self):
        for obj in self.underlays+self.overlays:
            if isinstance(obj, PlotAxis) and obj.orientation in ("left", "right"):
                return obj
        else:
            return None

    def _get_labels(self):
        labels = []
        for obj in self.underlays+self.overlays:
            if isinstance(obj, PlotLabel):
                labels.append(obj)
        return labels

    #------------------------------------------------------------------------
    # Event handlers
    #------------------------------------------------------------------------

    def _update_mappers(self):
        x_mapper = self.index_mapper
        y_mapper = self.value_mapper

        if self.orientation == "v":
            x_mapper, y_mapper = y_mapper, x_mapper

        x = self.x
        x2 = self.x2
        y = self.y
        y2 = self.y2

        if "left" in self.origin:
            x_mapper.screen_bounds = (x, x2)
        else:
            x_mapper.screen_bounds = (x2, x)

        if "bottom" in self.origin:
            y_mapper.screen_bounds = (y, y2)
        else:
            y_mapper.screen_bounds = (y2, y)

        self.invalidate_draw()
        self._cache_valid = False
        self._screen_cache_valid = False

    def _bounds_changed(self, old, new):
        super(BaseXYPlot, self)._bounds_changed(old, new)
        self._update_mappers()

    def _bounds_items_changed(self, event):
        super(BaseXYPlot, self)._bounds_items_changed(event)
        self._update_mappers()

    def _position_changed(self):
        self._update_mappers()

    def _position_items_changed(self):
        self._update_mappers()

    def _orientation_changed(self):
        self._update_mappers()

    def _index_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._either_data_changed, "data_changed", remove=True)
            old.on_trait_change(self._either_metadata_changed, "metadata_changed",
                                remove=True)
        if new is not None:
            new.on_trait_change(self._either_data_changed, "data_changed")
            new.on_trait_change(self._either_metadata_changed, "metadata_changed")
        self._either_data_changed()
        return

    def _either_data_changed(self):
        self.invalidate_draw()
        self._cache_valid = False
        self._screen_cache_valid = False
        self.request_redraw()
        return

    def _either_metadata_changed(self):
        # By default, don't respond to metadata change events.
        pass

    def _value_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._either_data_changed, "data_changed", remove=True)
            old.on_trait_change(self._either_metadata_changed, "metadata_changed",
                                remove=True)
        if new is not None:
            new.on_trait_change(self._either_data_changed, "data_changed")
            new.on_trait_change(self._either_metadata_changed, "metadata_changed")
        self._either_data_changed()
        return

    def _origin_changed(self, old, new):
        # origin switch from left to right or vice versa?
        if old.split()[1] != new.split()[1]:
            xm = self.x_mapper
            xm.low_pos, xm.high_pos = xm.high_pos, xm.low_pos
        # origin switch from top to bottom or vice versa?
        if old.split()[0] != new.split()[0]:
            ym = self.y_mapper
            ym.low_pos, ym.high_pos = ym.high_pos, ym.low_pos

        self.invalidate_draw()
        self._screen_cache_valid = False
        return

    def _index_mapper_changed(self, old, new):
        self._either_mapper_changed(self, "index_mapper", old, new)
        if self.orientation == "h":
            self.trait_property_changed("x_mapper", old, new)
        else:
            self.trait_property_changed("y_mapper", old, new)
        return

    def _value_mapper_changed(self, old, new):
        self._either_mapper_changed(self, "value_mapper", old, new)
        if self.orientation == "h":
            self.trait_property_changed("y_mapper", old, new)
        else:
            self.trait_property_changed("x_mapper", old, new)
        return

    def _either_mapper_changed(self, obj, name, old, new):
        if old is not None:
            old.on_trait_change(self._mapper_updated_handler, "updated", remove=True)
        if new is not None:
            new.on_trait_change(self._mapper_updated_handler, "updated")
        self.invalidate_draw()
        self._screen_cache_valid = False
        return

    def _mapper_updated_handler(self):
        self._cache_valid = False
        self._screen_cache_valid = False
        self.invalidate_draw()
        self.request_redraw()
        return

    def _visible_changed(self, old, new):
        if new:
            self._layout_needed = True

    def _bgcolor_changed(self):
        self.invalidate_draw()

    def _use_subdivision_changed(self, old, new):
        if new:
            self._set_up_subdivision()
        return

    #------------------------------------------------------------------------
    # Persistence
    #------------------------------------------------------------------------

    def __getstate__(self):
        state = super(BaseXYPlot,self).__getstate__()
        for key in ['_cache_valid', '_cached_data_pts', '_screen_cache_valid',
                    '_cached_screen_pts']:
            if key in state:
                del state[key]

        return state

    def __setstate__(self, state):
        super(BaseXYPlot, self).__setstate__(state)
        if self.index is not None:
            self.index.on_trait_change(self._either_data_changed, "data_changed")
        if self.value is not None:
            self.value.on_trait_change(self._either_data_changed, "data_changed")

        self.invalidate_draw()
        self._cache_valid = False
        self._screen_cache_valid = False
        self._update_mappers()
        return
Exemple #19
0
class Editor(HasPrivateTraits):
    """ Represents an editing control for an object trait in a Traits-based
        user interface.
    """

    #---------------------------------------------------------------------------
    #  Trait definitions:
    #---------------------------------------------------------------------------

    # The UI (user interface) this editor is part of:
    ui = Instance('traitsui.ui.UI')

    # Full name of the object the editor is editing (e.g. 'object.link1.link2'):
    object_name = Str('object')

    # The object this editor is editing (e.g. object.link1.link2):
    object = Instance(HasTraits)

    # The name of the trait this editor is editing (e.g. 'value'):
    name = ReadOnly

    # The context object the editor is editing (e.g. object):
    context_object = Property

    # The extended name of the object trait being edited. That is,
    # 'object_name.name' minus the context object name at the beginning. For
    # example: 'link1.link2.value':
    extended_name = Property

    # Original value of object.name (e.g. object.link1.link2.value):
    old_value = Any

    # Text description of the object trait being edited:
    description = ReadOnly

    # The Item object used to create this editor:
    item = Instance(Item, ())

    # The GUI widget defined by this editor:
    control = Any

    # The GUI label (if any) defined by this editor:
    label_control = Any

    # Is the underlying GUI widget enabled?
    enabled = Bool(True)

    # Is the underlying GUI widget visible?
    visible = Bool(True)

    # Is the underlying GUI widget scrollable?
    scrollable = Bool(False)

    # The EditorFactory used to create this editor:
    factory = factory_trait

    # Is the editor updating the object.name value?
    updating = Bool(False)

    # Current value for object.name:
    value = Property

    # Current value of object trait as a string:
    str_value = Property

    # The trait the editor is editing (not its value, but the trait itself):
    value_trait = Property

    # The current editor invalid state status:
    invalid = Bool(False)

    #---------------------------------------------------------------------------
    #  Initializes the object:
    #---------------------------------------------------------------------------

    def __init__(self, parent, **traits):
        """ Initializes the editor object.
        """
        HasPrivateTraits.__init__(self, **traits)
        try:
            self.old_value = getattr(self.object, self.name)
        except AttributeError:
            ctrait = self.object.base_trait(self.name)
            if ctrait.type == 'event' or self.name == 'spring':
                # Getting the attribute will fail for 'Event' traits:
                self.old_value = Undefined
            else:
                raise

        # Synchronize the application invalid state status with the editor's:
        self.sync_value(self.factory.invalid, 'invalid', 'from')

    #---------------------------------------------------------------------------
    #  Finishes editor set-up:
    #---------------------------------------------------------------------------

    def prepare(self, parent):
        """ Finishes setting up the editor.
        """
        name = self.extended_name
        if name != 'None':
            self.context_object.on_trait_change(self._update_editor,
                                                name,
                                                dispatch='ui')
        self.init(parent)
        self._sync_values()
        self.update_editor()

    #---------------------------------------------------------------------------
    #  Finishes initializing the editor by creating the underlying toolkit
    #  widget:
    #---------------------------------------------------------------------------

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        raise NotImplementedError

    #---------------------------------------------------------------------------
    #  Assigns focus to the editor's underlying toolkit widget:
    #---------------------------------------------------------------------------

    def set_focus(self):
        """ Assigns focus to the editor's underlying toolkit widget.
        """
        raise NotImplementedError

    #---------------------------------------------------------------------------
    #  Disposes of the contents of an editor:
    #---------------------------------------------------------------------------

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        if self.ui is None:
            return

        name = self.extended_name
        if name != 'None':
            self.context_object.on_trait_change(self._update_editor,
                                                name,
                                                remove=True)

        if self._user_from is not None:
            for name, handler in self._user_from:
                self.on_trait_change(handler, name, remove=True)

        if self._user_to is not None:
            for object, name, handler in self._user_to:
                object.on_trait_change(handler, name, remove=True)

        # Break linkages to references we no longer need:
        self.object = self.ui = self.item = self.factory = self.control = \
        self.label_control = self.old_value = self._context_object = None

    #---------------------------------------------------------------------------
    #  Returns the context object the editor is using (Property implementation):
    #---------------------------------------------------------------------------

    @cached_property
    def _get_context_object(self):
        """ Returns the context object the editor is using (Property
            implementation).
        """
        object_name = self.object_name
        context_key = object_name.split('.', 1)[0]
        if (object_name != '') and (context_key in self.ui.context):
            return self.ui.context[context_key]

        # This handles the case of a 'ListItemProxy', which is not in the
        # ui.context, but is the editor 'object':
        return self.object

    #---------------------------------------------------------------------------
    #  Returns the extended trait name being edited (Property implementation):
    #---------------------------------------------------------------------------

    @cached_property
    def _get_extended_name(self):
        """ Returns the extended trait name being edited (Property
            implementation).
        """
        return ('%s.%s' % (self.object_name, self.name)).split('.', 1)[1]

    #---------------------------------------------------------------------------
    #  Returns the trait the editor is editing (Property implementation):
    #---------------------------------------------------------------------------

    def _get_value_trait(self):
        """ Returns the trait the editor is editing (Property implementation).
        """
        return self.object.trait(self.name)

    #---------------------------------------------------------------------------
    #  Gets/Sets the associated object trait's value:
    #---------------------------------------------------------------------------

    def _get_value(self):
        return getattr(self.object, self.name, Undefined)

    def _set_value(self, value):
        if self.ui and self.name != 'None':
            self.ui.do_undoable(self.__set_value, value)

    def __set_value(self, value):
        self._no_update = True
        try:
            try:
                handler = self.ui.handler
                obj_name = self.object_name
                name = self.name
                method = (getattr(handler, '%s_%s_setattr' %
                                  (obj_name, name), None)
                          or getattr(handler, '%s_setattr' % name, None)
                          or getattr(handler, 'setattr'))
                method(self.ui.info, self.object, name, value)
            except TraitError as excp:
                self.error(excp)
                raise
        finally:
            self._no_update = False

    #---------------------------------------------------------------------------
    #  Returns the text representation of a specified object trait value:
    #---------------------------------------------------------------------------

    def string_value(self, value, format_func=None):
        """ Returns the text representation of a specified object trait value.

            If the **format_func** attribute is set on the editor factory, then
            this method calls that function to do the formatting.  If the
            **format_str** attribute is set on the editor factory, then this
            method uses that string for formatting. If neither attribute is
            set, then this method just calls the built-in unicode() function.
        """
        factory = self.factory
        #print "editor", value.decode(encoding='UTF-16',errors='strict')
        if factory.format_func is not None:
            return factory.format_func(value)

        if factory.format_str != '':
            return factory.format_str % value

        if format_func is not None:
            return format_func(value)
        """if value.startswith(b'\xFF\xD8')and value.endswith(b'\xFF\xD9'):
            from base64 import b64encode
            mime='image/jpeg'
            fmt = 'data:{mime};base64,{data}'
            b64 = b64encode(value).decode('ascii')
            return  fmt.format(mime=mime, data=b64)"""

        return unicode(value)

    #---------------------------------------------------------------------------
    #  Returns the text representation of the object trait:
    #---------------------------------------------------------------------------

    def _get_str_value(self):
        """ Returns the text representation of the object trait.
        """
        return self.string_value(getattr(self.object, self.name, Undefined))

    #---------------------------------------------------------------------------
    #  Returns the text representation of a specified value:
    #---------------------------------------------------------------------------

    def _str(self, value):
        """ Returns the text representation of a specified value.
        """
        # In Unicode!
        return unicode(value)

    #---------------------------------------------------------------------------
    #  Handles an error that occurs while setting the object's trait value:
    #
    #  (Should normally be overridden in a subclass)
    #---------------------------------------------------------------------------

    def error(self, excp):
        """ Handles an error that occurs while setting the object's trait value.
        """
        pass

    #---------------------------------------------------------------------------
    #  Performs updates when the object trait changes:
    #---------------------------------------------------------------------------

    def _update_editor(self, object, name, old_value, new_value):
        """ Performs updates when the object trait changes.
        """
        # If background threads have modified the trait the editor is bound to,
        # their trait notifications are queued to the UI thread. It is possible
        # that by the time the UI thread dispatches these events, the UI the
        # editor is part of has already been closed. So we need to check if we
        # are still bound to a live UI, and if not, exit immediately:
        if self.ui is None:
            return

        # If the notification is for an object different than the one actually
        # being edited, it is due to editing an item of the form:
        # object.link1.link2.name, where one of the 'link' objects may have
        # been modified. In this case, we need to rebind the current object
        # being edited:
        if object is not self.object:
            self.object = eval(self.object_name, globals(), self.ui.context)

        # If the editor has gone away for some reason, disconnect and exit:
        if self.control is None:
            self.context_object.on_trait_change(self._update_editor,
                                                self.extended_name,
                                                remove=True)
            return

        # Log the change that was made (as long as it is not for an event):
        if object.base_trait(name).type != 'event':
            self.log_change(self.get_undo_item, object, name, old_value,
                            new_value)

        # If the change was not caused by the editor itself:
        if not self._no_update:
            # Update the editor control to reflect the current object state:
            self.update_editor()

    #---------------------------------------------------------------------------
    #  Logs a change made in the editor:
    #---------------------------------------------------------------------------

    def log_change(self, undo_factory, *undo_args):
        """ Logs a change made in the editor.
        """
        # Indicate that the contents of the user interface have been changed:
        ui = self.ui
        ui.modified = True

        # Create an undo history entry if we are maintaining a history:
        undoable = ui._undoable
        if undoable >= 0:
            history = ui.history
            if history is not None:
                item = undo_factory(*undo_args)
                if item is not None:
                    if undoable == history.now:
                        # Create a new undo transaction:
                        history.add(item)
                    else:
                        # Extend the most recent undo transaction:
                        history.extend(item)

    #---------------------------------------------------------------------------
    #  Updates the editor when the object trait changes external to the editor:
    #
    #  (Should normally be overridden in a subclass)
    #---------------------------------------------------------------------------

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        pass

    #---------------------------------------------------------------------------
    #  Creates an undo history entry:
    #
    #  (Can be overridden in a subclass for special value types)
    #---------------------------------------------------------------------------

    def get_undo_item(self, object, name, old_value, new_value):
        """ Creates an undo history entry.
        """
        return UndoItem(object=object,
                        name=name,
                        old_value=old_value,
                        new_value=new_value)

    #---------------------------------------------------------------------------
    #  Returns a tuple of the form ( context_object, name[.name...], callable )
    #  for a specified extended name of the form: name or
    #  context_object_name.name[.name...]:
    #---------------------------------------------------------------------------

    def parse_extended_name(self, name):
        """ Returns a tuple of the form ( context_object, 'name[.name...],
            callable ) for a specified extended name of the form: 'name' or
            'context_object_name.name[.name...]'.
        """
        col = name.find('.')
        if col < 0:
            object = self.context_object
        else:
            object, name = self.ui.context[name[:col]], name[col + 1:]

        return (object, name, eval("lambda obj=object: obj." + name))

    #---------------------------------------------------------------------------
    #  Initializes and synchronizes (as needed) editor traits with the value of
    #  corresponding factory traits:
    #---------------------------------------------------------------------------

    def _sync_values(self):
        """ Initializes and synchronizes (as needed) editor traits with the
            value of corresponding factory traits.
        """
        factory = self.factory
        for name, trait in factory.traits(sync_value=not_none):
            value = getattr(factory, name)
            if isinstance(value, ContextValue):
                self_trait = self.trait(name)
                self.sync_value(value.name, name, self_trait.sync_value
                                or trait.sync_value,
                                self_trait.is_list is True)
            elif value is not Undefined:
                setattr(self, name, value)

    #---------------------------------------------------------------------------
    #  Sets/Unsets synchronization between an editor trait and a user object
    #  trait:
    #---------------------------------------------------------------------------

    def sync_value(self, user_name, editor_name, mode='both', is_list=False):
        """ Sets or unsets synchronization between an editor trait and a user
            object trait.
        """
        if user_name != '':
            key = '%s:%s' % (user_name, editor_name)

            if self._no_trait_update is None:
                self._no_trait_update = {}

            user_ref = 'user_object'
            col = user_name.find('.')
            if col < 0:
                user_object = self.context_object
                xuser_name = user_name
            else:
                user_object = self.ui.context[user_name[:col]]
                user_name = xuser_name = user_name[col + 1:]
                col = user_name.rfind('.')
                if col >= 0:
                    user_ref += ('.' + user_name[:col])
                    user_name = user_name[col + 1:]

            user_value = compile('%s.%s' % (user_ref, user_name), '<string>',
                                 'eval')
            user_ref = compile(user_ref, '<string>', 'eval')

            if mode in ('from', 'both'):

                def user_trait_modified(new):
                    # Need this to include 'user_object' in closure:
                    user_object
                    if key not in self._no_trait_update:
                        self._no_trait_update[key] = None
                        try:
                            setattr(self, editor_name, new)
                        except:
                            from traitsui.api import raise_to_debug
                            raise_to_debug()
                        del self._no_trait_update[key]

                user_object.on_trait_change(user_trait_modified, xuser_name)

                if self._user_to is None:
                    self._user_to = []
                self._user_to.append(
                    (user_object, xuser_name, user_trait_modified))

                if is_list:

                    def user_list_modified(event):
                        if isinstance(event, TraitListEvent):
                            if key not in self._no_trait_update:
                                self._no_trait_update[key] = None
                                n = event.index
                                try:
                                    getattr(self, editor_name
                                            )[n:n +
                                              len(event.removed)] = event.added
                                except:
                                    from traitsui.api import raise_to_debug
                                    raise_to_debug()
                                del self._no_trait_update[key]

                    user_object.on_trait_change(user_list_modified,
                                                xuser_name + '_items')
                    self._user_to.append((user_object, xuser_name + '_items',
                                          user_list_modified))

                try:
                    setattr(self, editor_name, eval(user_value))
                except:
                    from traitsui.api import raise_to_debug
                    raise_to_debug()

            if mode in ('to', 'both'):

                def editor_trait_modified(new):
                    # Need this to include 'user_object' in closure:
                    user_object
                    if key not in self._no_trait_update:
                        self._no_trait_update[key] = None
                        try:
                            setattr(eval(user_ref), user_name, new)
                        except:
                            from traitsui.api import raise_to_debug
                            raise_to_debug()
                        del self._no_trait_update[key]

                self.on_trait_change(editor_trait_modified, editor_name)

                if self._user_from is None:
                    self._user_from = []
                self._user_from.append((editor_name, editor_trait_modified))

                if is_list:

                    def editor_list_modified(event):
                        # Need this to include 'user_object' in closure:
                        user_object
                        if key not in self._no_trait_update:
                            self._no_trait_update[key] = None
                            n = event.index
                            try:
                                eval(user_value
                                     )[n:n + len(event.removed)] = event.added
                            except:
                                from traitsui.api import raise_to_debug
                                raise_to_debug()
                            del self._no_trait_update[key]

                    self.on_trait_change(editor_list_modified,
                                         editor_name + '_items')
                    self._user_from.append(
                        (editor_name + '_items', editor_list_modified))

                if mode == 'to':
                    try:
                        setattr(eval(user_ref), user_name,
                                getattr(self, editor_name))
                    except:
                        from traitsui.api import raise_to_debug
                        raise_to_debug()

    #-- UI preference save/restore interface -----------------------------------

    #---------------------------------------------------------------------------
    #  Restores any saved user preference information associated with the
    #  editor:
    #---------------------------------------------------------------------------

    def restore_prefs(self, prefs):
        """ Restores any saved user preference information associated with the
            editor.
        """
        pass

    #---------------------------------------------------------------------------
    #  Returns any user preference information associated with the editor:
    #---------------------------------------------------------------------------

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        return None
Exemple #20
0
class SourceMixer( SamplesGenerator ):
    """
    Mixes the signals from several sources. 
    """

    #: List of :class:`~acoular.sources.SamplesGenerator` objects
    #: to be mixed.
    sources = List( Instance(SamplesGenerator, ()) ) 

    #: Sampling frequency of the signal.
    sample_freq = Trait( SamplesGenerator().sample_freq )
    
    #: Number of channels.
    numchannels = Trait( SamplesGenerator().numchannels )
               
    #: Number of samples.
    numsamples = Trait( SamplesGenerator().numsamples )
    
    # internal identifier
    ldigest = Property( depends_on = ['sources.digest', ])

    # internal identifier
    digest = Property( depends_on = ['ldigest', '__class__'])

    traits_view = View(
        Item('sources', style='custom')
                    )

    @cached_property
    def _get_ldigest( self ):
        res = ''
        for s in self.sources:
            res += s.digest
        return res

    @cached_property
    def _get_digest( self ):
        return digest(self)

    @on_trait_change('sources')
    def validate_sources( self ):
        """ Validates if sources fit together. """
        if self.sources:
            self.sample_freq = self.sources[0].sample_freq
            self.numchannels = self.sources[0].numchannels
            self.numsamples = self.sources[0].numsamples
            for s in self.sources[1:]:
                if self.sample_freq != s.sample_freq:
                    raise ValueError("Sample frequency of %s does not fit" % s)
                if self.numchannels != s.numchannels:
                    raise ValueError("Channel count of %s does not fit" % s)
                if self.numsamples != s.numsamples:
                    raise ValueError("Number of samples of %s does not fit" % s)


    def result(self, num):
        """
        Python generator that yields the output block-wise.
        The outputs from the sources in the list are being added.
        
        Parameters
        ----------
        num : integer
            This parameter defines the size of the blocks to be yielded
            (i.e. the number of samples per block).
        
        Returns
        -------
        Samples in blocks of shape (num, numchannels). 
            The last block may be shorter than num.
        """
        gens = [i.result(num) for i in self.sources[1:]]
        for temp in self.sources[0].result(num):
            sh = temp.shape[0]
            for g in gens:
                temp1 = next(g)
                if temp.shape[0] > temp1.shape[0]:
                    temp = temp[:temp1.shape[0]]
                temp += temp1[:temp.shape[0]]
            yield temp
            if sh > temp.shape[0]:
                break
Exemple #21
0
class TableModel(GridModel):
    """ Model for table data.
    """

    # -------------------------------------------------------------------------
    #  Trait definitions:
    # -------------------------------------------------------------------------

    #: The editor that created this model
    editor = Instance(Editor)

    #: The current filter
    filter = Instance(TableFilter, allow_none=True)

    #: Current filter summary message
    filter_summary = Str("All items")

    #: Display the table items in reverse order?
    reverse = Bool(False)

    #: Event fired when the table has been sorted
    sorted = Event

    #: The current 'auto_add' row
    auto_add_row = Any

    def __init__(self, **traits):
        """ Initializes the object.
        """
        super(TableModel, self).__init__(**traits)

        # Attach trait handlers to the list object:
        editor = self.editor
        object = editor.context_object
        name = " " + editor.extended_name

        # Set up listeners for any of the model data changing:
        object.on_trait_change(self._on_data_changed, name, dispatch="ui")
        object.on_trait_change(
            self.fire_content_changed, name + ".-", dispatch="ui"
        )

        # Set up listeners for any column definitions changing:
        editor.on_trait_change(self.update_columns, "columns", dispatch="ui")
        editor.on_trait_change(
            self.update_columns, "columns_items", dispatch="ui"
        )

        # Initialize the current filter from the editor's default filter:
        self.filter = editor.filter

        # If we are using 'auto_add' mode, create the first 'auto_add' row:
        if editor.auto_add:
            self.auto_add_row = row = editor.create_new_row()
            if row is not None:
                row.on_trait_change(self.on_auto_add_row, dispatch="ui")

    # -- TableModel Interface -------------------------------------------------

    def dispose(self):
        """ Disposes of the model when it is no longer needed.
        """
        editor = self.editor
        object = editor.context_object
        name = " " + editor.extended_name

        # Remove listeners for any of the model data changing:
        object.on_trait_change(self._on_data_changed, name, remove=True)
        object.on_trait_change(
            self.fire_content_changed, name + ".-", remove=True
        )

        # Remove listeners for any column definitions changing:
        editor.on_trait_change(self.update_columns, "columns", remove=True)
        editor.on_trait_change(
            self.update_columns, "columns_items", remove=True
        )

        # Make sure we have removed listeners from the current filter also:
        if self.filter is not None:
            self.filter.on_trait_change(self._filter_modified, remove=True)

        # Clean-up any links that should be broken:
        self.editor = None

    def get_filtered_items(self):
        """ Returns all model items matching the current filter.
        """
        return self.__filtered_items()

    def get_filtered_item(self, index=0):
        """ Returns a single specified item from those items matching the
            current filter.
        """
        try:
            return self.__filtered_items()[index]
        except:
            logger.error(
                "TableModel error: Request for invalid row %d out of "
                "%d" % (index, len(self.__filtered_items()))
            )
            return None

    def raw_index_of(self, row):
        """ Returns the raw, unfiltered index corresponding to a specified
            filtered index.
        """
        if self._filtered_cache is None:
            return row

        return self.editor.filtered_indices[row]

    def insert_filtered_item_after(self, index, item):
        """ Inserts an object after a specified filtered index.
        """
        mapped_index = 0
        n = len(self.editor.filtered_indices)
        if index >= n:
            if (index != 0) or (n != 0):
                raise IndexError
        elif index >= 0:
            mapped_index = self.editor.filtered_indices[index] + 1
        self.__items().insert(mapped_index, item)
        sorted = self._sort_model()
        if sorted:
            mapped_index = self.__items().index(item)
        self._filtered_cache = None
        return (mapped_index, sorted)

    def delete_filtered_item_at(self, index):
        """ Deletes the object at the specified filtered index.
        """
        if index >= len(self.editor.filtered_indices):
            raise IndexError

        mapped_index = self.editor.filtered_indices[index]
        items = self.__items()
        object = items[mapped_index]
        del items[mapped_index]
        self._filtered_cache = None
        return (mapped_index, object)

    def update_columns(self):
        """ Updates the table view when columns have been changed.
        """
        self._columns = None
        self.fire_structure_changed()
        self.editor.refresh()

    def no_column_sort(self):
        """ Resets any sorting being performed on the underlying model.
        """
        self._sorter = self._filtered_cache = None
        self.column_sorted = GridSortEvent(index=-1)
        # self.fire_structure_changed()

    # -- Event Handlers -------------------------------------------------------

    @on_trait_change("filter.+")
    def _filter_modified(self):
        """ Handles the contents of the filter being changed.
        """
        self._filtered_cache = None
        self.fire_structure_changed()
        self.editor.filter_modified()

    def _click_changed(self, event):
        """ Handles the grid firing a 'click' event.
        """
        row, col = event

        # Fire the same event on the editor after mapping it to a model object
        # and column name:
        object = self.get_filtered_item(row)
        column = self.__get_column(col)
        self.editor.click = (object, column)

        # Check to see if the column has a view to display:
        view = column.get_view(object)
        if view is not None:
            column.get_object(object).edit_traits(
                view=view, parent=self._bounds_for(row, col)
            )

        # Invoke the column's click handler:
        column.on_click(object)

    def _dclick_changed(self, event):
        """ Handles the grid firing a 'dclick' event.
        """
        row, col = event

        # Fire the same event on the editor after mapping it to a model object
        # and column name:
        object = self.get_filtered_item(row)
        column = self.__get_column(col)
        self.editor.dclick = (object, column)

        # Invoke the column's double-click handler:
        column.on_dclick(object)

    def on_auto_add_row(self):
        """ Handles the user modifying the current 'auto_add' mode row.
        """
        object = self.auto_add_row
        object.on_trait_change(self.on_auto_add_row, remove=True)

        self.auto_add_row = row = self.editor.create_new_row()
        if row is not None:
            row.on_trait_change(self.on_auto_add_row, dispatch="ui")

        do_later(
            self.editor.add_row, object, len(self.get_filtered_items()) - 2
        )

    # -- GridModel Interface --------------------------------------------------

    def get_column_count(self):
        """ Returns the number of columns for this table.
        """
        return len(self.__get_columns())

    def get_column_name(self, index):
        """ Returns the label of the column specified by the (zero-based) index.
        """
        return self.__get_column(index).get_label()

    def get_column_size(self, index):
        """ Returns the size in pixels of the column indexed by *index*.
            A value of -1 or None means to use the default.
        """
        return self.__get_column(index).get_width()

    def get_cols_drag_value(self, cols):
        """ Returns the value to use when the specified columns are dragged or
            copied and pasted. The parameter *cols* is a list of column indexes.
        """
        return [self.__get_data_column(col) for col in cols]

    def get_cols_selection_value(self, cols):
        """ Returns a list of TraitGridSelection objects containing the
            objects corresponding to the grid rows and the traits corresponding
            to the specified columns.
        """
        values = []
        for obj in self.__items(False):
            values.extend(
                [
                    TraitGridSelection(
                        obj=obj, name=self.__get_column_name(col)
                    )
                    for col in cols
                ]
            )
        return values

    def sort_by_column(self, col, reverse=False):
        """ Sorts the model data by the column indexed by *col*.
        """
        # Make sure we allow sorts by column:
        factory = self.editor.factory
        if not factory.sortable:
            return

        # Flush the object cache:
        self._filtered_cache = None

        # Cache the sorting information for later:
        self._sorter = self.__get_column(col).key
        self._reverse = reverse

        # If model sorting is requested, do it now:
        self._sort_model()

        # Indicate the we have been sorted:
        self.sorted = True

        self.column_sorted = GridSortEvent(index=col, reversed=reverse)

    def is_column_read_only(self, index):
        """ Returns True if the column specified by the zero-based *index* is
            read-only.
        """
        return not self.__get_column(index).editable

    def get_row_count(self):
        """ Return the number of rows for this table.
        """
        return len(self.__filtered_items())

    def get_row_name(self, index):
        """ Return the name of the row specified by the (zero-based) *index*.
        """
        return "<undefined>"

    def get_rows_drag_value(self, rows):
        """ Returns the value to use when the specified rows are dragged or
            copied and pasted. The parameter *rows* is a list of row indexes.
            If there is only one row listed, then return the corresponding trait
            object. If more than one row is listed, then return a list of objects.
        """
        items = self.__filtered_items()
        return [items[row] for row in rows]

    def get_rows_selection_value(self, rows):
        """ Returns a list of TraitGridSelection objects containing the
            object corresponding to the selected rows.
        """
        items = self.__filtered_items()
        return [TraitGridSelection(obj=items[row]) for row in rows]

    def is_row_read_only(self, index):
        """ Returns True if the row specified by the zero-based *index* is
            read-only.
        """
        return False

    def get_cell_editor(self, row, col):
        """ Returns the editor for the specified cell.
        """

        if self.editor is None:
            return None

        column = self.__get_column(col)
        object = self.get_filtered_item(row)
        editor = column.get_editor(object)
        if editor is None:
            return None

        editor._ui = self.editor.ui

        target, name = column.target_name(object)

        return TraitGridCellAdapter(
            editor,
            target,
            name,
            "",
            context=self.editor.ui.context,
            style=column.get_style(object),
            width=column.get_edit_width(object),
            height=column.get_edit_height(object),
        )

    def get_cell_renderer(self, row, col):
        """ Returns the renderer for the specified cell.
        """
        return self.__get_column(col).get_renderer(self.get_filtered_item(row))

    def get_cell_drag_value(self, row, col):
        """ Returns the value to use when the specified cell is dragged or
            copied and pasted.
        """
        return self.__get_column(col).get_drag_value(
            self.get_filtered_item(row)
        )

    def get_cell_selection_value(self, row, col):
        """ Returns a TraitGridSelection object specifying the data stored
            in the table at (*row*, *col*).
        """
        return TraitGridSelection(
            obj=self.get_filtered_item(row), name=self.__get_column_name(col)
        )

    def resolve_selection(self, selection_list):
        """ Returns a list of (row, col) grid-cell coordinates that
            correspond to the objects in *selection_list*. For each coordinate,
            if the row is -1, it indicates that the entire column is selected.
            Likewise coordinates with a column of -1 indicate an entire row
            that is selected. For the TableModel, the objects in
            *selection_list* must be TraitGridSelection objects.
        """
        items = self.__filtered_items()
        cells = []
        for selection in selection_list:
            row = -1
            if selection.obj is not None:
                try:
                    row = items.index(selection.obj)
                except ValueError:
                    continue

            column = -1
            if selection.name != "":
                column = self._get_column_index_by_trait(selection.name)
                if column is None:
                    continue

            cells.append((row, column))

        return cells

    def get_cell_context_menu(self, row, col):
        """ Returns a Menu object that generates the appropriate context
            menu for this cell.
        """
        column = self.__get_column(col)
        menu = column.get_menu(self.get_filtered_item(row))
        editor = self.editor

        if menu is None:
            menu = editor.factory.menu

        if menu is None:
            menu_name = editor.factory.menu_name
            if menu_name:
                menu = getattr(self.editor.object, menu_name, None)

        if menu is not None:
            editor.prepare_menu(row, column)

            return (menu, editor)

        return None

    def get_value(self, row, col):
        """ Returns the value stored in the table at (*row*, *col*).
        """
        object = self.get_filtered_item(row)
        if object is self.auto_add_row:
            return ""

        value = self.__get_column(col).get_value(object)
        formats = self.__get_column_formats(col)

        if (value is not None) and (formats is not None):
            format = formats.get(type(value))
            if format is not None:
                try:
                    if callable(format):
                        value = format(value)
                    else:
                        value = format % value
                except:
                    pass

        return value

    def is_valid_cell_value(self, row, col, value):
        """ Tests whether *value* is valid for the cell at (*row*, *col*).
        Returns True if value is acceptable, and False otherwise. """
        return self.__get_column(col).is_droppable(
            self.get_filtered_item(row), value
        )

    def is_cell_empty(self, row, col):
        """ Returns True if the cell at (*row*, *col*) has a None value, and
            False otherwise.
        """
        return self.get_value(row, col) is None

    def is_cell_read_only(self, row, col):
        """ Returns True if the cell at (*row*, *col*) is read-only, and False
            otherwise.
        """
        return not self.__get_column(col).is_editable(
            self.get_filtered_item(row)
        )

    def get_cell_bg_color(self, row, col):
        """ Returns a wxColour object specifying the background color
            of the specified cell.
        """
        return self.__get_column(col).get_cell_color(
            self.get_filtered_item(row)
        )

    def get_cell_text_color(self, row, col):
        """ Returns a wxColour object specifying the text color of the
            specified cell.
        """
        column = self.__get_column(col)
        item = self.get_filtered_item(row)
        return column.get_text_color(item)

    def get_cell_font(self, row, col):
        """ Returns a wxFont object specifying the font of the specified cell.
        """
        return self.__get_column(col).get_text_font(
            self.get_filtered_item(row)
        )

    def get_cell_halignment(self, row, col):
        """ Returns a string specifying the horizontal alignment of the
            specified cell.

            Returns 'left' for left alignment, 'right' for right alignment,
            or 'center' for center alignment.
        """
        return self.__get_column(col).get_horizontal_alignment(
            self.get_filtered_item(row)
        )

    def get_cell_valignment(self, row, col):
        """ Returns a string specifying the vertical alignment of the
            specified cell.

            Returns 'top' for top alignment, 'bottom' for bottom alignment,
            or 'center' for center alignment.
        """
        return self.__get_column(col).get_vertical_alignment(
            self.get_filtered_item(row)
        )

    def _insert_rows(self, pos, num_rows):
        """ Inserts *num_rows* at *pos*; fires an event only if a factory
        method for new rows is defined or the model is not empty. Otherwise,
        it returns 0.
        """
        count = 0

        factory = self.editor.factory.row_factory
        if factory is None:
            items = self.__items(False)
            if len(items) > 0:
                factory = items[0].__class__

        if factory is not None:
            new_data = [
                x
                for x in [factory() for i in range(num_rows)]
                if x is not None
            ]
            if len(new_data) > 0:
                count = self._insert_rows_into_model(pos, new_data)
                self.rows_added = ("added", pos, new_data)

        return count

    def _delete_rows(self, pos, num_rows):
        """ Removes rows *pos* through *pos* + *num_rows* from the model.
        """
        row_count = self.get_rows_count()
        if (pos + num_rows) > row_count:
            num_rows = row_count - pos

        return self._delete_rows_from_model(pos, num_rows)

    def _set_value(self, row, col, value):
        """ Sets the value of the cell at (*row*, *col*) to *value*.

            Raises a ValueError if the value is vetoed or the cell at
            the specified position does not exist.
        """
        new_rows = 0
        column = self.__get_column(col)
        obj = None
        try:
            obj = self.get_filtered_item(row)
        except:
            # Add a new row:
            new_rows = self._insert_rows(self.get_row_count(), 1)
            if new_rows > 0:
                # Now set the value on the new object:
                try:
                    obj = self.get_filtered_item(self.get_row_count() - 1)
                except:
                    # fixme: what do we do in this case? veto the set somehow?
                    # raise an exception?
                    pass

        if obj is not None:
            self._set_data_on_row(obj, column, value)

        return new_rows

    def _move_column(self, frm, to):
        """ Moves a specified **frm** column to before the specified **to**
            column. Returns **True** if successful; **False** otherwise.
        """
        to_column = None
        if to < len(self.__get_columns()):
            to_column = self.__get_column(to)

        return self.editor.move_column(self.__get_column(frm), to_column)

    def _set_data_on_row(self, row, column, value):
        """ Sets the cell specified by (*row*, *col*) to *value, which
            can be either a member of the row object, or a no-argument method
            on that object.
        """
        column.set_value(row, value)

    def _insert_rows_into_model(self, pos, new_data):
        """ Inserts the given new rows into the model.
        """
        raw_pos = self.raw_index_of(pos)
        self.__items()[raw_pos:raw_pos] = new_data

    def _delete_rows_from_model(self, pos, num_rows):
        """ Deletes the specified rows from the model.
        """
        raw_rows = sorted(
            [self.raw_index_of(i) for i in range(pos, pos + num_rows)]
        )
        raw_rows.reverse()
        items = self.__items()
        for row in raw_rows:
            del items[row]

        return num_rows

    def _on_data_changed(self):
        """ Forces the grid to refresh when the underlying list changes.
        """
        # Invalidate the current cache (if any):
        self._filtered_cache = None

        self.fire_structure_changed()

    def _mouse_cell_changed(self, new):
        """ Handles the user mousing over a specified cell.
        """
        row, col = new
        column = self.__get_column(col)
        object = self.get_filtered_item(row)

        # Update the tooltip if necessary:
        tooltip = column.get_tooltip(object)
        if tooltip != self._tooltip:
            self._tooltip = tooltip
            self.editor.grid._grid_window.SetToolTip(wx.ToolTip(tooltip))

        if column.is_auto_editable(object):
            x, y, dx, dy = self._bounds_for(row, col)
            if column.is_editable(object):
                view = View(
                    Item(
                        name=column.name,
                        editor=column.get_editor(object),
                        style=column.get_style(object),
                        show_label=False,
                        padding=-4,
                    ),
                    kind="info",
                    width=dx,
                    height=dy,
                )
            else:
                view = column.get_view(object)
                if view is None:
                    return

            column.get_object(object).edit_traits(
                view=view, parent=(x, y, dx, dy)
            )

    def _bounds_for(self, row, col):
        """ Returns the coordinates and size of the specified cell in the form:
            ( x, y, dx, dy ).
        """
        grid = self.editor.grid
        coords = wxg.GridCellCoords(row, col)
        x, y, dx, dy = grid._grid.BlockToDeviceRect(coords, coords)
        x, y = grid._grid_window.ClientToScreenXY(x, y)

        return (x, y, dx, dy)

    def _sort_model(self):
        """ Sorts the underlying model if that is what the user requested.
        """
        editor = self.editor
        sorted = editor.factory.sort_model and (self._sorter is not None)
        if sorted:
            items = self.__items(False)[:]
            items.sort(key=self._sorter)
            if self.reverse ^ self._reverse:
                items.reverse()
            editor.value = items
        return sorted

    def __items(self, ordered=True):
        """ Returns the raw list of model objects.
        """
        result = self.editor.value
        if not isinstance(result, SequenceTypes):
            return [result]

        if ordered and self.reverse:
            return ReversedList(result)

        return result

    def __filtered_items(self):
        """ Returns the list of all model objects that pass the current filter.
        """
        fc = self._filtered_cache
        if fc is None:
            items = self.__items()
            filter = self.filter
            if filter is None:
                nitems = [nitem for nitem in enumerate(items)]
                self.filter_summary = "All %s items" % len(nitems)
            else:
                if not callable(filter):
                    filter = filter.filter
                nitems = [
                    nitem for nitem in enumerate(items) if filter(nitem[1])
                ]
                self.filter_summary = "%s of %s items" % (
                    len(nitems),
                    len(items),
                )
            sorter = self._sorter
            if sorter is not None:
                nitems.sort(key=lambda x: sorter(x[1]))
                if self._reverse:
                    nitems.reverse()

            self.editor.filtered_indices = [x[0] for x in nitems]
            self._filtered_cache = fc = [x[1] for x in nitems]
            if self.auto_add_row is not None:
                self._filtered_cache.append(self.auto_add_row)

        return fc

    def __get_data_column(self, col):
        """ Returns a list of model data from the column indexed by *col*.
        """
        column = self.__get_column(col)
        return [column.get_value(item) for item in self.__filtered_items()]

    def __get_columns(self):
        columns = self._columns
        if columns is None:
            self._columns = columns = [
                c for c in self.editor.columns if c.visible
            ]
        return columns

    def __get_column(self, col):
        try:
            return self.__get_columns()[col]
        except:
            return self.__get_columns()[0]

    def __get_column_name(self, col):
        return self.__get_column(col).name

    def __get_column_formats(self, col):
        return None  # Not used/implemented currently

    def _get_column_index_by_trait(self, name):
        for i, col in enumerate(self.__get_columns()):
            if name == col.name:
                return i
Exemple #22
0
class TimeSamples( SamplesGenerator ):
    """
    Container for time data in `*.h5` format.
    
    This class loads measured data from h5 files and
    and provides information about this data.
    It also serves as an interface where the data can be accessed
    (e.g. for use in a block chain) via the :meth:`result` generator.
    """

    #: Full name of the .h5 file with data.
    name = File(filter=['*.h5'], 
        desc="name of data file")

    #: Basename of the .h5 file with data, is set automatically.
    basename = Property( depends_on = 'name', #filter=['*.h5'], 
        desc="basename of data file")
    
    #: Calibration data, instance of :class:`~acoular.calib.Calib` class, optional .
    calib = Trait( Calib, 
        desc="Calibration data")
    
    #: Number of channels, is set automatically / read from file.
    numchannels = CLong(0, 
        desc="number of input channels")

    #: Number of time data samples, is set automatically / read from file.
    numsamples = CLong(0, 
        desc="number of samples")

    #: The time data as array of floats with dimension (numsamples, numchannels).
    data = Any( transient = True, 
        desc="the actual time data array")

    #: HDF5 file object
    h5f = Instance(tables.File, transient = True)
    
    # internal identifier
    digest = Property( depends_on = ['basename', 'calib.digest'])

    traits_view = View(
        ['name{File name}', 
            ['sample_freq~{Sampling frequency}', 
            'numchannels~{Number of channels}', 
            'numsamples~{Number of samples}', 
            '|[Properties]'], 
            '|'
        ], 
        title='Time data', 
        buttons = OKCancelButtons
                    )

    @cached_property
    def _get_digest( self ):
        return digest(self)
    
    @cached_property
    def _get_basename( self ):
        return path.splitext(path.basename(self.name))[0]
    
    @on_trait_change('basename')
    def load_data( self ):
        """ 
        Open the .h5 file and set attributes.
        """
        if not path.isfile(self.name):
            # no file there
            self.numsamples = 0
            self.numchannels = 0
            self.sample_freq = 0
            raise IOError("No such file: %s" % self.name)
        if self.h5f != None:
            try:
                self.h5f.close()
            except IOError:
                pass
        self.h5f = tables.open_file(self.name)
        self.data = self.h5f.root.time_data
        self.sample_freq = self.data.get_attr('sample_freq')
        (self.numsamples, self.numchannels) = self.data.shape

    def result(self, num=128):
        """
        Python generator that yields the output block-wise.
                
        Parameters
        ----------
        num : integer, defaults to 128
            This parameter defines the size of the blocks to be yielded
            (i.e. the number of samples per block) .
        
        Returns
        -------
        Samples in blocks of shape (num, numchannels). 
            The last block may be shorter than num.
        """
        if self.numsamples == 0:
            raise IOError("no samples available")
        i = 0
        if self.calib:
            if self.calib.num_mics == self.numchannels:
                cal_factor = self.calib.data[newaxis]
            else:
                raise ValueError("calibration data not compatible: %i, %i" % \
                            (self.calib.num_mics, self.numchannels))
            while i < self.numsamples:
                yield self.data[i:i+num]*cal_factor
                i += num
        else:
            while i < self.numsamples:
                yield self.data[i:i+num]
                i += num
Exemple #23
0
class TestCase(Mayavi):
    """
    This class is to be subclassed when you write a test.
    """

    # Interact with the user after test is done?  Normally tests just
    # exit after completion, this prevents that.
    interact = Bool(False)

    # Always use offscreen rendering to generate images -- even if
    # `self.compare_image` was called in the test..
    offscreen = Bool(False)

    # Use the standalone mode. This mode does not use the envisage Mayavi
    # application.
    standalone = Bool(True)

    app_window = Instance('pyface.api.ApplicationWindow')

    gui = Instance('pyface.gui.GUI')

    # An exception info if an exception was raised by a test.
    exception_info = Any

    ######################################################################
    # `Mayavi` interface.
    ######################################################################
    def main(self, argv=None, plugins=None):
        """Overridden main method that sets the argv to sys.argv[1:] by
        default.  Call this to run the test.
        """
        if argv is None:
            argv = sys.argv[1:]

        if not is_running_with_nose():
            self.parse_command_line(argv)

        if self.standalone:
            self.run_standalone()
        else:
            # Call the superclass main method.
            super(TestCase, self).main(argv, plugins)

    def setup_logger(self):
        """Overridden logger setup."""
        if self.standalone:
            path = os.path.join(ETSConfig.application_data, 'mayavi_e3',
                                'mayavi-test.log')
            path = os.path.abspath(path)
            log_path = os.path.dirname(path)
            if not os.path.exists(log_path):
                os.makedirs(log_path)
        else:
            path = 'mayavi-test.log'
        setup_logger(logger, path, mode=self.log_mode)

    def run_standalone(self):
        from mayavi.core.engine import Engine
        from mayavi.plugins.script import Script
        from pyface.api import ApplicationWindow, GUI

        self.setup_logger()
        if self.offscreen:
            engine = Engine(scene_factory=off_screen_viewer)
        else:
            engine = Engine()
        engine.start()

        self.exception_info = None
        self.script = Script(engine=engine)
        self.gui = g = GUI()
        self.app_window = a = ApplicationWindow()
        a.open()
        a.show(False)
        g.invoke_later(self.run)
        g.start_event_loop()
        if self.exception_info is not None:
            type, value, tb = self.exception_info
            raise type, value, tb

    def run(self):
        """This starts everything up and runs the test.  Call main to
        run the test."""
        # Calls the users test code.
        try:
            self.do()
        except Exception, e:
            type, value, tb = sys.exc_info()
            if is_running_with_nose():
                self.exception_info = type, value, tb
            else:
                # To mimic behavior of unittest.
                sys.stderr.write('\nfailures=1\n')
                info = traceback.extract_tb(tb)
                filename, lineno, function, text = info[-1]  # last line only
                exc_msg = "%s\nIn %s:%d\n%s: %s (in %s)" %\
                        ('Exception', filename, lineno, type.__name__, str(value),
                        function)
                sys.stderr.write(exc_msg + '\n')
                # Log the message.
                logger.exception(exc_msg)
                if not self.interact:
                    sys.exit(1)
        finally:
Exemple #24
0
class Pipeline(HasTraits):
    """ Function used to build pipelines for helper functions """
    #doc = ''
    _source_function = Callable()

    _pipeline = List()

    # Traits here only for documentation purposes
    figure = Instance('mayavi.core.scene.Scene', help='Figure to populate.')

    def __call__(self, *args, **kwargs):
        """ Calls the logics of the factory, but only after disabling
            rendering, if needed.
        """
        # First retrieve the scene, if any.
        if 'figure' in kwargs:
            figure = kwargs['figure']
            assert isinstance(figure, (Scene, None))
            scene = figure.scene
        else:
            scene = tools.gcf().scene
        if scene is not None:
            self._do_redraw = not scene.disable_render
            scene.disable_render = True
        # Then call the real logic
        output = self.__call_internal__(*args, **kwargs)
        # And re-enable the rendering, if needed.
        if scene is not None:
            scene.disable_render = not self._do_redraw
        return output

    def __call_internal__(self, *args, **kwargs):
        """ Builds the source and runs through the pipeline, returning
        the last object created by the pipeline."""
        self.store_kwargs(kwargs)
        self.source = self._source_function(*args, **kwargs)
        # Copy the pipeline so as not to modify it for the next call
        self.pipeline = self._pipeline[:]
        return self.build_pipeline()

    def store_kwargs(self, kwargs):
        """ Merges the given keyword argument, with traits default and
            store the resulting dictionary in self.kwargs."""
        kwargs = kwargs.copy()
        all_traits = self.get_all_traits()
        if not set(kwargs.keys()).issubset(list(all_traits.keys())):
            raise ValueError("Invalid keyword arguments : %s" % \
                    ', '.join(
                        str(k) for k in
                        set(kwargs.keys()).difference(list(all_traits.keys()))))
        traits = self.get(self.class_trait_names())
        [traits.pop(key) for key in list(traits.keys()) if key[0] == '_']
        traits.update(kwargs)
        self.kwargs = traits

    def build_pipeline(self):
        """ Runs through the pipeline, applying pipe after pipe. """
        object = self.source
        for pipe in self.pipeline:
            keywords = set(pipe.class_trait_names())
            keywords.remove('trait_added')
            keywords.remove('trait_modified')
            this_kwargs = {}
            for key, value in self.kwargs.items():
                if key in keywords:
                    this_kwargs[key] = value
            object = pipe(object, **this_kwargs)._target
        return object

    def get_all_traits(self):
        """ Returns all the traits of class, and the classes in the pipeline.
        """
        traits = {}
        for pipe in self._pipeline:
            traits.update(pipe.class_traits())
        traits.update(self.class_traits())
        traits.pop('trait_added')
        traits.pop('trait_modified')
        return traits
class BleedthroughLinearDiagnostic(HasStrictTraits):
    """
    Plots a scatterplot of each channel vs every other channel and the 
    bleedthrough line
    
    Attributes
    ----------
    op : Instance(BleedthroughPiecewiseOp)
        The operation whose parameters we're viewing.  If you made the instance
        with :meth:`BleedthroughPLinearOp.default_view`, this is set for you
        already.
        
    subset : str
        If set, only plot this subset of the underlying data.
        
    """
    
    # traits   
    id = Constant("edu.mit.synbio.cytoflow.view.autofluorescencediagnosticview")
    friendly_id = Constant("Autofluorescence Diagnostic") 
    
    subset = Str
    
    # TODO - why can't I use BleedthroughPiecewiseOp here?
    op = Instance(IOperation)
    
    def plot(self, experiment = None, **kwargs):
        """
        Plot a diagnostic of the bleedthrough model computation.
        """
        
        if experiment is None:
            raise util.CytoflowViewError('experiment',
                                         "No experiment specified")

        if not self.op.controls:
            raise util.CytoflowViewError('op',
                                         "No controls specified")
        
        if not self.op.spillover:
            raise util.CytoflowViewError('op',
                                         "No spillover matrix specified")
        
        kwargs.setdefault('histtype', 'stepfilled')
        kwargs.setdefault('alpha', 0.5)
        kwargs.setdefault('antialiased', True)
         
        plt.figure()
        
        # the completely arbitrary ordering of the channels
        channels = list(set([x for (x, _) in list(self.op.spillover.keys())]))
        num_channels = len(channels)
        
        for from_idx, from_channel in enumerate(channels):
            for to_idx, to_channel in enumerate(channels):
                if from_idx == to_idx:
                    continue
                
                check_tube(self.op.controls[from_channel], experiment)
                tube_exp = ImportOp(tubes = [Tube(file = self.op.controls[from_channel])],
                                    channels = {experiment.metadata[c]["fcs_name"] : c for c in experiment.channels},
                                    name_metadata = experiment.metadata['name_metadata']).apply()
                
                # apply previous operations
                for op in experiment.history:
                    tube_exp = op.apply(tube_exp)
                    
                # subset it
                if self.subset:
                    try:
                        tube_exp = tube_exp.query(self.subset)
                    except Exception as e:
                        raise util.CytoflowViewError('subset',
                                                   "Subset string '{0}' isn't valid"
                                              .format(self.subset)) from e
                                    
                    if len(tube_exp.data) == 0:
                        raise util.CytoflowViewError('subset',
                                                   "Subset string '{0}' returned no events"
                                              .format(self.subset))
                    
                tube_data = tube_exp.data
                
                # for ReadTheDocs, which doesn't have swig
                import sys
                if sys.modules['cytoflow.utility.logicle_ext.Logicle'].__name__ != 'cytoflow.utility.logicle_ext.Logicle':
                    scale_name = 'log'
                else:
                    scale_name = 'logicle'
                
                xscale = util.scale_factory(scale_name, tube_exp, channel = from_channel)
                yscale = util.scale_factory(scale_name, tube_exp, channel = to_channel)

                plt.subplot(num_channels, 
                            num_channels, 
                            from_idx + (to_idx * num_channels) + 1)
                plt.xscale(scale_name, **xscale.mpl_params)
                plt.yscale(scale_name, **yscale.mpl_params)
                plt.xlabel(from_channel)
                plt.ylabel(to_channel)
                plt.scatter(tube_data[from_channel],
                            tube_data[to_channel],
                            alpha = 0.1,
                            s = 1,
                            marker = 'o')
                
                xs = np.logspace(-1, math.log(tube_data[from_channel].max(), 10))
                ys = xs * self.op.spillover[(from_channel, to_channel)]
          
                plt.plot(xs, ys, 'g-', lw=3)
                
        plt.tight_layout(pad = 0.8)
Exemple #26
0
class VideoServer(Loggable):
    video = Instance(Video)
    port = Int(1084)
    quality = Int(75)
    _started = False
    use_color = True
    start_button = Button
    start_label = Property(depends_on='_started')
    _started = Bool(False)

    def _get_start_label(self):
        return 'Start' if not self._started else 'Stop'

    def _start_button_fired(self):
        if self._started:
            self.stop()
        else:
            self.start()

    def traits_view(self):
        v = View(
            Item('start_button',
                 editor=ButtonEditor(label_value='start_label')))
        return v

    def _video_default(self):
        return Video(swap_rb=True)

    def stop(self):
        #        if self._started:
        self.info('stopping video server')
        self._stop_signal.set()
        self._started = False

    def start(self):
        self.info('starting video server')
        self._new_frame_ready = Event()
        self._stop_signal = Event()

        self.video.open(user='******')
        bt = Thread(name='broadcast', target=self._broadcast)
        bt.start()

        self.info('video server started')
        self._started = True

    def _broadcast(self):
        #        new_frame = self._new_frame_ready
        self.info('video broadcast thread started')

        context = zmq.Context()
        #         sock = context.socket(zmq.PUB)
        sock = context.socket(zmq.REP)
        sock.bind('tcp://*:{}'.format(self.port))

        poll = zmq.Poller()
        poll.register(sock, zmq.POLLIN)

        self.request_reply(sock, poll)
#        if use_color:
#            kw = dict(swap_rb=True)
#            depth = 3
#        else:
#            kw = dict(gray=True)
#            depth = 1

#         pt = time.time()

    def request_reply(self, sock, poll):
        stop = self._stop_signal
        video = self.video
        fps = 10
        import Image
        from cStringIO import StringIO
        quality = self.quality
        while not stop.isSet():

            socks = dict(poll.poll(100))
            if socks.get(sock) == zmq.POLLIN:
                resp = sock.recv()
                if resp == 'FPS':
                    buf = str(fps)
                elif resp.startswith('QUALITY'):
                    quality = int(resp[7:])
                    buf = ''
                else:
                    f = video.get_frame()

                    #            new_frame.clear()

                    im = Image.fromarray(array(f))
                    s = StringIO()
                    im.save(s, 'JPEG', quality=quality)
                    buf = s.getvalue()

                sock.send(buf)

    def publisher(self, sock):
        stop = self._stop_signal
        video = self.video
        use_color = self.use_color
        fps = 10
        import Image
        from cStringIO import StringIO
        while not stop.isSet():

            f = video.get_frame(gray=False)
            #            new_frame.clear()
            im = Image.fromarray(array(f))
            s = StringIO()
            im.save(s, 'JPEG')

            sock.send(str(fps))
            sock.send(s.getvalue())

            time.sleep(1.0 / fps)
Exemple #27
0
class TaskWindowBackend(MTaskWindowBackend):
    """ The toolkit-specific implementation of a TaskWindowBackend.

    See the ITaskWindowBackend interface for API documentation.
    """

    #### Private interface ####################################################

    _main_window_layout = Instance(MainWindowLayout)

    ###########################################################################
    # 'ITaskWindowBackend' interface.
    ###########################################################################

    def create_contents(self, parent):
        """ Create and return the TaskWindow's contents.
        """
        QtGui.QApplication.instance().focusChanged.connect(
            self._focus_changed_signal)
        return QtGui.QStackedWidget(parent)

    def destroy(self):
        """ Destroy the backend.
        """
        QtGui.QApplication.instance().focusChanged.disconnect(
            self._focus_changed_signal)

    def hide_task(self, state):
        """ Assuming the specified TaskState is active, hide its controls.
        """
        # Save the task's layout in case it is shown again later.
        self.window._active_state.layout = self.get_layout()

        # Now hide its controls.
        self.control.centralWidget().removeWidget(state.central_pane.control)
        for dock_pane in state.dock_panes:
            # Warning: The layout behavior is subtly different (and wrong!) if
            # the order of these two statement is switched.
            dock_pane.control.hide()
            self.control.removeDockWidget(dock_pane.control)

    def show_task(self, state):
        """ Assumming no task is currently active, show the controls of the
            specified TaskState.
        """
        # Show the central pane.
        self.control.centralWidget().addWidget(state.central_pane.control)

        # Show the dock panes.
        self._layout_state(state)

        # OSX-specific: if there is only a single tool bar, it doesn't matter if
        # the user can drag it around or not. Therefore, we can combine it with
        # the title bar, which is idiomatic on the Mac.
        self.control.setUnifiedTitleAndToolBarOnMac(
            len(state.tool_bar_managers) <= 1)

    #### Methods for saving and restoring the layout ##########################

    def get_layout(self):
        """ Returns a TaskLayout for the current state of the window.
        """
        # Extract the layout from the main window.
        layout = TaskLayout(id=self.window._active_state.task.id)
        self._main_window_layout.state = self.window._active_state
        self._main_window_layout.get_layout(layout)

        # Extract the window's corner configuration.
        for name, corner in CORNER_MAP.iteritems():
            area = INVERSE_AREA_MAP[int(self.control.corner(corner))]
            setattr(layout, name + '_corner', area)

        return layout

    def set_layout(self, layout):
        """ Applies a TaskLayout (which should be suitable for the active task)
            to the window.
        """
        self.window._active_state.layout = layout
        self._layout_state(self.window._active_state)

    ###########################################################################
    # Private interface.
    ###########################################################################

    def _layout_state(self, state):
        """ Layout the dock panes in the specified TaskState using its
            TaskLayout.
        """
        # Assign the window's corners to the appropriate dock areas.
        for name, corner in CORNER_MAP.iteritems():
            area = getattr(state.layout, name + '_corner')
            self.control.setCorner(corner, AREA_MAP[area])

        # Add all panes in the TaskLayout.
        self._main_window_layout.state = state
        self._main_window_layout.set_layout(state.layout)

        # Add all panes not assigned an area by the TaskLayout.
        for dock_pane in state.dock_panes:
            if dock_pane.control not in self._main_window_layout.consumed:
                self.control.addDockWidget(AREA_MAP[dock_pane.dock_area],
                                           dock_pane.control)
                # By default, these panes are not visible. However, if a pane
                # has been explicitly set as visible, honor that setting.
                if dock_pane.visible:
                    dock_pane.control.show()

    #### Trait initializers ###################################################

    def __main_window_layout_default(self):
        return TaskWindowLayout(control=self.control)

    #### Signal handlers ######################################################

    def _focus_changed_signal(self, old, new):
        if self.window.active_task:
            panes = [self.window.central_pane] + self.window.dock_panes
            for pane in panes:
                if new and pane.control.isAncestorOf(new):
                    pane.has_focus = True
                elif old and pane.control.isAncestorOf(old):
                    pane.has_focus = False
Exemple #28
0
class DSCUSBUI(DSCUSB, HasTraits):
    """An enhanced version of :class:`~.dscusb.DSCUSB`. It defines a traits
    based user interface. For function definition see :class:`~.dscusb.DSCUSB`
    
    .. note:: 
        
        HasTraits define set and get functions, so these are
        overridden by DSCUSB's set and get functions. 
        
    Examples
    --------
    
    For simple DSCUSB redout display do:
        
    >>> dsc = DSCUSBUI()
    >>> dsc.configure_traits() # doctest: +SKIP
    
    %s
    """
    #: DSCUSB settable parameters are defined in this class
    settings = Instance(DSCUSBSettings, ())
    #: station number
    STN = Range(0, 999, 1, desc='station number')
    #: serialUI instance
    serial = Instance(SerialUI, (), transient=True)
    _initialized = Bool(False, desc='device status')

    #: a string representing serial number of the device
    serial_number = Str(' Unknown ', desc='device serial number')
    #: a stringe representing firmware version of the device
    firmware_version = Str(' -.--  ', desc='firmware version')
    #: here the measured data is written for display
    output = Str(desc='measured output')
    #: here the measured temperature is written for display
    temp = Str('', desc='measured temperature')
    #: defines output mode SYS: Force [mN], MVV: Strain [mV/V], CELL: Cell output
    output_mode = Trait('Force [mN]', {
        'Force [mN]': 'SYS',
        'Strain [mV/V]': 'MVV',
        'Cell output': 'CELL'
    },
                        desc='different output modes. ')
    #: STAT flag is written here, for status messages
    stat = Int(desc='STAT flags.')
    #: status messages are written here
    status_message = Str
    #: a bool that determines if there is a problem with output data
    output_alarm = Bool(False)
    #: a bool that determines if there is a problem with temperature
    temp_alarm = Bool(False)

    timer = Any
    #: interval for data readout
    interval = Range(low=READOUT_INTERVAL_MIN,
                     high=READOUT_INTERVAL_MAX,
                     value=READOUT_INTERVAL,
                     desc='data acquisition interval')

    timer_fast = Any

    readout = List([])

    offset = Float(desc='force measurement offset')
    port_button = Button('Port', desc='port settings')
    settings_button = Button('Settings', desc='settings')
    init_button = Button('On/Off', desc='initialize action')
    set_offset_button = Button('Set', desc='set offset action')

    #    zero_button = Button('Set zero', desc = 'set force to zero')

    view = dscusbui_view

    def _serial_default(self):
        return DSCSerial(timeout=0.06, baudrate=BAUDRATE_DEFAULT)

    def __initialized_changed(self, value):
        if value == True:
            self.serial_number, self.firmware_version = self.get_info()
            self._stat_changed(self.stat)  #update message
            self.start_readout(self.interval)
        else:
            self.stop_readout()
            self._reset_output_data()
            self.status_message = ''

    def _interval_changed(self, value):
        if self._initialized:
            self.stop_readout()
            self.start_readout(value)

    def _timer_function(self):
        try:
            self.stat = self.get_stat()
            if self.readout != []:
                value, self.readout = np.median(self.readout), []
                self.output = '%.4f' % value
                temp = self.get_temp()
                self.temp = '%.2f' % temp
                return value, temp
        except:
            self.close()
            raise

    def _timer_fast_function(self):
        try:
            value = self.get(self.output_mode_)
            if self.output_mode_ == 'SYS':
                value -= self.offset
            self.readout.append(value)
        except:
            self.close()
            raise

    def start_readout(self, interval=1.):
        """Starts readout process. GUI must be running for this to work...
        """
        try:
            self.timer.Start(interval * 1000)
            self.timer_fast.Start(self._interval_min * 1000)
        except AttributeError:
            #self._timer_function()
            self.timer = Timer(interval * 1000, self._timer_function)
            self.timer_fast = Timer(self._interval_min * 1000,
                                    self._timer_fast_function)

    def stop_readout(self):
        """Stops readout process
        """
        self.timer.Stop()
        self.timer_fast.Stop()

    def _reset_output_data(self):
        self.status_message = ''
        self.output = ''
        self.temp = ''
        self.serial_number, self.firmware_version = ('Unknown', '-.--')

    def _stat_changed(self, stat):
        stat = stat_to_dict(stat)
        self.output_alarm = stat['ECOMUR'] or stat['ECOMOR'] or \
                            stat['SYSUR'] or stat['SYSOR'] or \
                            stat['CRAWUR'] or stat['CRAWOR'] or \
                            stat['SCALON'] or stat['LCINTEG']
        self.temp_alarm = stat['TEMPUR'] or stat['TEMPOR']
        self.status_message = get_status_message(stat)

    @display_on_exception(logger, 'Could not initialize')
    def _init_button_fired(self):
        if self._initialized:
            self.close()
        else:
            self.init()

    @display_on_exception(logger, 'Could not change settings')
    def _settings_button_fired(self):
        self.stop_readout()
        d = self.settings.get()
        d.pop('_poweruser')
        for key in d:
            value = self.get(key)
            setattr(self.settings, key, value)
            d[key] = getattr(self.settings, key)
        self.settings.edit_traits(kind='modal')
        reset = False
        STN = self.STN
        for key in d:
            value = getattr(self.settings, key)
            if d[key] != value:
                if key == 'BAUD':
                    self.set_baudrate(BAUDRATES[baudrate])
                else:
                    self.set(key, value)
                if key in RESET_SETTINGS:
                    reset = True
                if key == 'STN':
                    if STN != value:
                        STN = value

        if reset == True:
            self._initialized = False
            self.reset()
            self.STN = STN
        self.start_readout(self.interval)

    @display_on_exception(logger, 'Could not change port settings')
    def _port_button_fired(self):
        self.close()
        self.serial.edit_traits(kind='livemodal')

    @display_on_exception(logger, 'Could not change offset')
    def _set_offset_button_fired(self):
        self.calibrate()
Exemple #29
0
class FMPModel(BaseModule):
    """基于特征因子模拟组合的绩效分析模型"""
    #Portfolio = Enum(None, arg_type="SingleOption", label="策略组合", order=0)
    #BenchmarkPortfolio = Enum("无", arg_type="SingleOption", label="基准组合", order=1)
    AttributeFactors = ListStr(arg_type="MultiOption",
                               label="特征因子",
                               order=2,
                               option_range=())
    #IndustryFactor = Enum("无", arg_type="SingleOption", label="行业因子", order=3)
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=4)
    RiskTable = Instance(RiskTable, arg_type="RiskTable", label="风险表", order=5)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=6)

    def __init__(self,
                 factor_table,
                 name="因子模拟组合绩效分析模型",
                 sys_args={},
                 **kwargs):
        self._FactorTable = factor_table
        return super().__init__(name=name,
                                sys_args=sys_args,
                                config_file=None,
                                **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "Portfolio",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="策略组合",
                 order=0))
        self.add_trait(
            "BenchmarkPortfolio",
            Enum(*(["无"] + DefaultNumFactorList),
                 arg_type="SingleOption",
                 label="基准组合",
                 order=1))
        self.add_trait(
            "AttributeFactors",
            ListStr(arg_type="MultiOption",
                    label="特征因子",
                    order=2,
                    option_range=tuple(DefaultNumFactorList)))
        self.AttributeFactors.append(DefaultNumFactorList[-1])
        self.add_trait(
            "IndustryFactor",
            Enum(*(["无"] + DefaultStrFactorList),
                 arg_type="SingleOption",
                 label="行业因子",
                 order=3))
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=4))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ['价', 'Price', 'price'])

    def _normalizePortfolio(self, portfolio):
        NegMask = (portfolio < 0)
        TotalNegWeight = portfolio[NegMask].sum()
        if TotalNegWeight != 0:
            portfolio[NegMask] = portfolio[NegMask] / TotalNegWeight
        PosMask = (portfolio > 0)
        TotalPosWeight = portfolio[PosMask].sum()
        if TotalPosWeight != 0:
            portfolio[PosMask] = portfolio[PosMask] / TotalPosWeight
            portfolio[
                NegMask] = portfolio[NegMask] * TotalNegWeight / TotalPosWeight
        return portfolio

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self.RiskTable.start(dts=dts)
        self._Output = {}
        self._Output["因子暴露"] = pd.DataFrame(columns=self.AttributeFactors)
        self._Output["风险调整的因子暴露"] = pd.DataFrame(columns=self.AttributeFactors)
        self._Output["风险贡献"] = pd.DataFrame(columns=self.AttributeFactors +
                                            ["Alpha"])
        self._Output["收益贡献"] = pd.DataFrame(columns=self.AttributeFactors +
                                            ["Alpha"])
        self._Output["因子收益"] = pd.DataFrame(columns=self.AttributeFactors)
        self._CurCalcInd = 0
        self._IDs = self._FactorTable.getID()
        return (self._FactorTable, )

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        PreDT = None
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            if self._CurCalcInd > 0: PreDT = self.CalcDTs[self._CurCalcInd - 1]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            if self._CurCalcInd > 0:
                PreDT = self._Model.DateTimeSeries[self._CurCalcInd - 1]
        if PreDT is None: return 0
        Portfolio = self._FactorTable.readData(factor_names=[self.Portfolio],
                                               dts=[PreDT],
                                               ids=self._IDs).iloc[0, 0]
        Portfolio = self._normalizePortfolio(Portfolio[pd.notnull(Portfolio)
                                                       & (Portfolio != 0)])
        if self.BenchmarkPortfolio != "无":
            BenchmarkPortfolio = self._FactorTable.readData(
                factor_names=[self.BenchmarkPortfolio],
                dts=[PreDT],
                ids=self._IDs).iloc[0, 0]
            BenchmarkPortfolio = self._normalizePortfolio(
                BenchmarkPortfolio[pd.notnull(BenchmarkPortfolio)
                                   & (BenchmarkPortfolio != 0)])
            IDs = Portfolio.index.union(BenchmarkPortfolio.index)
            if Portfolio.shape[0] > 0:
                Portfolio = Portfolio.loc[IDs]
                Portfolio.fillna(0.0, inplace=True)
            else:
                Portfolio = pd.Series(0.0, index=IDs)
            if BenchmarkPortfolio.shape[0] > 0:
                BenchmarkPortfolio = BenchmarkPortfolio.loc[IDs]
                BenchmarkPortfolio.fillna(0.0, inplace=True)
            else:
                BenchmarkPortfolio = pd.Series(0.0, index=IDs)
            Portfolio = Portfolio - BenchmarkPortfolio
        # 计算因子模拟组合
        self.RiskTable.move(PreDT, **kwargs)
        CovMatrix = dropRiskMatrixNA(
            self.RiskTable.readCov(dts=[PreDT],
                                   ids=Portfolio.index.tolist()).iloc[0])
        FactorExpose = self._FactorTable.readData(
            factor_names=list(self.AttributeFactors), ids=IDs,
            dts=[PreDT]).iloc[:, 0].dropna(axis=0)
        IDs = FactorExpose.index.intersection(CovMatrix.index).tolist()
        CovMatrix, FactorExpose = CovMatrix.loc[IDs,
                                                IDs], FactorExpose.loc[IDs, :]
        if self.IndustryFactor != "无":
            IndustryData = self._FactorTable.readData(
                factor_names=[self.IndustryFactor], ids=IDs,
                dts=[PreDT]).iloc[0, 0, :]
            DummyData = DummyVarTo01Var(IndustryData, ignore_nonstring=True)
            FactorExpose = pd.merge(FactorExpose,
                                    DummyData,
                                    left_index=True,
                                    right_index=True)
        CovMatrixInv = np.linalg.inv(CovMatrix.values)
        FMPHolding = np.dot(
            np.dot(
                np.linalg.inv(
                    np.dot(np.dot(FactorExpose.values.T, CovMatrixInv),
                           FactorExpose.values)), FactorExpose.values.T),
            CovMatrixInv)
        # 计算持仓对因子模拟组合的投资组合
        Portfolio = self._normalizePortfolio(Portfolio.loc[IDs])
        Beta = np.dot(
            np.dot(
                np.dot(
                    np.linalg.inv(
                        np.dot(np.dot(FMPHolding, CovMatrix.values),
                               FMPHolding.T)), FMPHolding), CovMatrix.values),
            Portfolio.values)
        Price = self._FactorTable.readData(factor_names=[self.PriceFactor],
                                           dts=[PreDT, idt],
                                           ids=IDs).iloc[0]
        Return = Price.iloc[1] / Price.iloc[0] - 1
        # 计算各统计指标
        if FactorExpose.shape[1] > self._Output["因子暴露"].shape[1]:
            FactorNames = FactorExpose.columns.tolist()
            self._Output["因子暴露"] = self._Output["因子暴露"].loc[:, FactorNames]
            self._Output["风险调整的因子暴露"] = self._Output[
                "风险调整的因子暴露"].loc[:, FactorNames]
            self._Output["风险贡献"] = self._Output["风险贡献"].loc[:, FactorNames +
                                                            ["Alpha"]]
            self._Output["收益贡献"] = self._Output["收益贡献"].loc[:, FactorNames +
                                                            ["Alpha"]]
            self._Output["因子收益"] = self._Output["因子收益"].loc[:, FactorNames]
        self._Output["因子暴露"].loc[PreDT, FactorExpose.columns] = Beta
        self._Output["风险调整的因子暴露"].loc[PreDT, FactorExpose.columns] = np.sqrt(
            np.diag(np.dot(np.dot(FMPHolding, CovMatrix.values),
                           FMPHolding.T))) * Beta
        RiskContribution = np.dot(np.dot(
            FMPHolding, CovMatrix.values), Portfolio.values) / np.sqrt(
                np.dot(np.dot(Portfolio.values, CovMatrix.values),
                       Portfolio.values)) * Beta
        self._Output["风险贡献"].loc[idt, FactorExpose.columns] = RiskContribution
        self._Output["风险贡献"].loc[idt, "Alpha"] = np.sqrt(
            np.dot(np.dot(Portfolio.values, CovMatrix),
                   Portfolio.values)) - np.nansum(RiskContribution)
        self._Output["因子收益"].loc[idt, FactorExpose.columns] = np.nansum(
            Return.values * FMPHolding, axis=1)
        self._Output["收益贡献"].loc[idt, FactorExpose.columns] = self._Output[
            "因子收益"].loc[idt, FactorExpose.columns] * self._Output["因子暴露"].loc[
                PreDT, FactorExpose.columns]
        self._Output["收益贡献"].loc[idt, "Alpha"] = (Portfolio * Return).sum(
        ) - self._Output["收益贡献"].loc[idt, FactorExpose.columns].sum()
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        self.RiskTable.end()
        self._Output["风险贡献占比"] = self._Output["风险贡献"].divide(
            self._Output["风险贡献"].sum(axis=1), axis=0)
        self._Output["历史均值"] = pd.DataFrame(
            columns=["因子暴露", "风险调整的因子暴露", "风险贡献", "风险贡献占比", "收益贡献"],
            index=self._Output["收益贡献"].columns)
        self._Output["历史均值"]["因子暴露"] = self._Output["因子暴露"].mean(axis=0)
        self._Output["历史均值"]["风险调整的因子暴露"] = self._Output["风险调整的因子暴露"].mean(
            axis=0)
        self._Output["历史均值"]["风险贡献"] = self._Output["风险贡献"].mean(axis=0)
        self._Output["历史均值"]["风险贡献占比"] = self._Output["风险贡献占比"].mean(axis=0)
        self._Output["历史均值"]["收益贡献"] = self._Output["收益贡献"].mean(axis=0)
        self._IDs = None
        return 0

    def genMatplotlibFig(self, file_path=None):
        nRow, nCol = 2, 3
        Fig = plt.figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow))
        AxesGrid = gridspec.GridSpec(nRow, nCol)
        xData = np.arange(1, self._Output["历史均值"].shape[0])
        xTickLabels = [str(iInd) for iInd in self._Output["历史均值"].index]
        PercentageFormatter = FuncFormatter(_QS_formatMatplotlibPercentage)
        FloatFormatter = FuncFormatter(lambda x, pos: '%.2f' % (x, ))
        _QS_plotStatistics(plt.subplot(AxesGrid[0, 0]), xData[:-1],
                           xTickLabels[:-1],
                           self._Output["历史均值"]["因子暴露"].iloc[:-1],
                           FloatFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[0, 1]), xData[:-1],
                           xTickLabels[:-1],
                           self._Output["历史均值"]["风险调整的因子暴露"].iloc[:-1],
                           FloatFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[0, 2]), xData[:-1],
                           xTickLabels[:-1],
                           self._Output["历史均值"]["因子收益"].iloc[:-1],
                           PercentageFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[1, 0]), xData, xTickLabels,
                           self._Output["历史均值"]["收益贡献"], PercentageFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[1, 1]), xData, xTickLabels,
                           self._Output["历史均值"]["风险贡献"], FloatFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[1, 2]), xData, xTickLabels,
                           self._Output["历史均值"]["风险贡献占比"], PercentageFormatter)
        if file_path is not None:
            Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig

    def _repr_html_(self):
        if len(self.ArgNames) > 0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName == "风险表":
                    HTML += "<li>" + iArgName + ": " + self.RiskTable.Name + "</li>"
                elif iArgName != "计算时点":
                    HTML += "<li>" + iArgName + ": " + str(
                        self.Args[iArgName]) + "</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>" + iArgName + ": 自定义时点</li>"
                else:
                    HTML += "<li>" + iArgName + ": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        Formatters = [lambda x: '{0:.4f}'.format(x)] * 2 + [
            _QS_formatPandasPercentage
        ] * 2 + [lambda x: '{0:.4f}'.format(x), _QS_formatPandasPercentage]
        iHTML = self._Output["历史均值"].to_html(formatters=Formatters)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:]
        Fig = self.genMatplotlibFig()
        # figure 保存为二进制文件
        Buffer = BytesIO()
        plt.savefig(Buffer, bbox_inches='tight')
        PlotData = Buffer.getvalue()
        # 图像数据转化为 HTML 格式
        ImgStr = "data:image/png;base64," + base64.b64encode(PlotData).decode()
        HTML += ('<img src="%s">' % ImgStr)
        return HTML
class VarTree(Variable):
    """ A Variable for a :class:`VariableTree` of a particular type. """

    def __init__(self, default_value, allow_none=True, **metadata):
        from openmdao.main.vartree import VariableTree # Break import loop on VariableTree
        if isinstance(default_value, VariableTree):
            klass = default_value.__class__
            if 'iotype' in metadata:
                default_value._iotype = metadata['iotype']
            else:
                metadata['iotype'] = default_value.iotype
        else:
            raise TypeError('default_value must be an instance of VariableTree'
                            ' or subclass')

        metadata.setdefault('copy', 'deep')
        self._allow_none = allow_none
        self.klass = klass
        self._instance = Instance(klass=klass, allow_none=False, factory=None,
                                  args=None, kw=None, **metadata)
        self._instance.default_value = default_value
        super(VarTree, self).__init__(default_value, **metadata)

    def validate(self, obj, name, value):
        """ Validates that a specified value is valid for this trait. """
        if value is None:
            if self._allow_none:
                return value
            self.validate_failed(obj, name, value)

        try:
            value = self._instance.validate(obj, name, value)
        except Exception:
            obj.raise_exception('%r must be an instance of %s.%s' %
                                (name, self._instance.klass.__module__,
                                 self._instance.klass.__name__), TypeError)
        return value

    def post_setattr(self, obj, name, value):
        """ VariableTrees must know their place within the hierarchy, so set
        their parent here.  This keeps side effects out of validate(). """
        if value.parent is not obj:
            value.parent = obj
        value._iotype = self.iotype

    def get_attribute(self, name, value, trait, meta):
        """Return the attribute dictionary for this variable. This dict is
        used by the GUI to populate the edit UI. Slots also return an
        attribute dictionary for the slot pane.

        name: str
          Name of variable

        value: object
          The value of the variable

        trait: CTrait
          The variable's trait

        meta: dict
          Dictionary of metadata for this variable
        """
        io_attr = {}
        io_attr['name'] = name
        io_attr['type'] = trait.trait_type.klass.__name__
        io_attr['ttype'] = 'vartree'

        for field in meta:
            if field not in gui_excludes:
                io_attr[field] = meta[field]

        return io_attr, None
Exemple #31
0
class WXWindow(TreeNodeObject):

    #---------------------------------------------------------------------------
    #  Trait definitions:
    #---------------------------------------------------------------------------

    window = Instance(wx.Window)
    name = Property
    position = Property
    size = Property
    sizer = Property
    min_size = Property
    best_size = Property
    items = Property(List)
    result = Property(Code)
    evaluate = Str

    #---------------------------------------------------------------------------
    #  Traits view definitions:
    #---------------------------------------------------------------------------

    view = View(VSplit(VGroup('position~',
                              'size~',
                              'sizer~',
                              'min_size~',
                              'best_size~',
                              label='Information',
                              dock='horizontal'),
                       Item('items',
                            label='Sizer Children',
                            show_label=False,
                            editor=sizer_item_table_editor,
                            dock='horizontal'),
                       VGroup('evaluate',
                              'result#~',
                              label='Evaluate',
                              dock='horizontal'),
                       id='splitter'),
                id='etsdevtools.developer.tools.ui_debugger.item',
                kind='subpanel')

    #---------------------------------------------------------------------------
    #  Handles 'evaluate' being changed:
    #---------------------------------------------------------------------------

    def _evaluate_changed(self):
        self.trait_property_changed('result', None, None)

    #---------------------------------------------------------------------------
    #  Implementation of the various trait properties:
    #---------------------------------------------------------------------------

    def _get_name(self):
        w = self.window
        try:
            label = w.GetLabel()
        except:
            try:
                label = w.GetValue()
            except:
                try:
                    label = w.GetTitle()
                except:
                    label = ''
        if label != '':
            label = ' (%s)' % label

        return self.window.__class__.__name__ + label

    def _get_size(self):
        dx, dy = self.window.GetSizeTuple()

        return str((dx, dy))

    def _get_position(self):
        x, y = self.window.GetPositionTuple()

        return str((x, y))

    def _get_sizer(self):
        sizer = self.window.GetSizer()
        if sizer is None:
            return ''
        dx, dy = sizer.CalcMin()

        return '%s( %d, %d )' % (sizer.__class__.__name__, dx, dy)

    def _get_min_size(self):
        dx, dy = self.window.GetMinSize()

        return str((dx, dy))

    def _get_best_size(self):
        dx, dy = self.window.GetEffectiveMinSize()

        return str((dx, dy))

    def _get_result(self):
        try:
            result = eval(self.evaluate, {
                '_': self.window,
                '__': self.window.GetSizer()
            })
            if isinstance(result, (list, tuple)):
                return '\n'.join(
                    ['[%d]: %s' % (i, str(x)) for i, x in enumerate(result)])
            return str(result)
        except:
            return '???'

    def _get_items(self):
        sizer = self.window.GetSizer()
        if sizer is None:
            return []
        items = []
        try:
            for i in range(10000):
                item = sizer.GetItem(i)
                if item is None:
                    break
                items.append(WXSizerItem(item=item))
        except:
            pass

        return items

    #---------------------------------------------------------------------------
    #  Returns whether chidren of this object are allowed or not:
    #---------------------------------------------------------------------------

    def tno_allows_children(self, node):
        """ Returns whether chidren of this object are allowed or not.
        """
        return True

    #---------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #---------------------------------------------------------------------------

    def tno_has_children(self, node=None):
        """ Returns whether or not the object has children.
        """
        return (len(self.window.GetChildren()) > 0)

    #---------------------------------------------------------------------------
    #  Gets the object's children:
    #---------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        return [
            WXWindow(window=window) for window in self.window.GetChildren()
        ]

    #---------------------------------------------------------------------------
    #  Returns the 'draggable' version of a specified object:
    #---------------------------------------------------------------------------

    def tno_get_drag_object(self, node):
        """ Returns the 'draggable' version of a specified object.
        """
        return self.window
Exemple #32
0
class Graph(Viewable, ContextMenuMixin):
    """
    """
    name = Str
    plotcontainer = Instance(BasePlotContainer)
    container_dict = Dict
    plots = List(Plot)

    selected_plotid = Property(depends_on='selected_plot')
    selected_plot = Instance(Plot)
    window_title = ''
    window_width = 600
    window_height = 500
    window_x = 500
    window_y = 250

    width = 300
    height = 300
    resizable = True

    line_inspectors_write_metadata = False

    _title = Str
    _title_font = None
    _title_size = None

    _convert_index = None

    status_text = Str

    view_identifier = None

    close_func = Callable
    x_limits_changed = Event

    def __init__(self, *args, **kw):
        """
        """
        super(Graph, self).__init__(*args, **kw)
        self.clear()

        pc = self.plotcontainer
        if self.use_context_menu:
            menu = ContextualMenuTool(parent=self, component=pc)

            pc.tools.append(menu)

    def set_time_xaxis(self, plotid=None):
        if plotid is None:
            plotid = len(self.plots) - 1

        p = self.plots[plotid]
        p.x_axis.tick_generator = ScalesTickGenerator(
            scale=CalendarScaleSystem())

    def add_point_inspector(self, scatter, convert_index=None):
        point_inspector = PointInspector(scatter, convert_index=convert_index)
        pinspector_overlay = PointInspectorOverlay(component=scatter,
                                                   tool=point_inspector)
        #
        scatter.overlays.append(pinspector_overlay)
        scatter.tools.append(point_inspector)

    def closed(self, *args):
        if self.close_func:
            self.close_func()

    def update_group_attribute(self, plot, attr, value, dataid=0):
        pass

    def get_plotid_by_ytitle(self, *args, **kw):
        plot = self.get_plot_by_ytitle(*args, **kw)
        if plot is not None:
            return self.plots.index(plot)

    def get_plot_by_ytitle(self, txt, startswith=False):
        """
            iso: str

            return None or Plot with y_axis.title equal to iso
            if startswith is True title only has to start with iso
        """
        if startswith:
            is_equal = lambda x: x.startswith(txt)
        else:
            is_equal = lambda x: x.__eq__(txt)

        plot = None
        for po in self.plots:
            if is_equal(po.y_axis.title):
                plot = po
                break

        return plot

    def get_num_plots(self):
        """
        """
        return len(self.plots)

    def get_num_series(self, plotid):
        """
        """
        return len(self.series[plotid])

    def get_data(self, plotid=0, series=0, axis=0):
        """

        """
        s = self.series[plotid][series]
        p = self.plots[plotid]
        return p.data.get_data(s[axis])

    def get_aux_data(self, plotid=0, series=1):
        plot = self.plots[plotid]

        si = plot.plots['aux{:03d}'.format(series)][0]

        oi = si.index.get_data()
        ov = si.value.get_data()
        return oi, ov

    def save_png(self, path=None):
        """
        """
        self._save_(type_='pic', path=path)

    def save_pdf(self, path=None):
        """
        """
        self._save_(type_='pdf', path=path)

    def save(self, path=None):
        """
        """
        self._save_(path=path)

    # def export_raw_data(self, path=None, header=None, plotid=0):
    #     """
    #     """
    #     if path is None:
    #         path = self._path_factory()
    #     if path is not None:
    #         self._export_raw_data(path, header, plotid)

    def export_data(self, path=None, plotid=None):
        """
        """
        if path is None:
            path = self._path_factory()

        if path is not None:
            path = add_extension(path, '.csv')
            self._export_data(path, plotid)

    def read_xy(self, p, header=False, series=0, plotid=0):
        """
        """
        x = []
        y = []
        with open(p, 'r') as f:
            reader = csv.reader(f)
            if header:
                reader.next()
            for line in reader:
                if line[0].startswith('#'):
                    continue
                if len(line) == 2:
                    x.append(float(line[0]))
                    y.append(float(line[1]))

        self.set_data(x, plotid, series)
        self.set_data(y, plotid, series, axis=1)

    #    def close(self):
    #        '''
    #            close the window
    #        '''
    #        if self.ui is not None:
    #            do_after_timer(1, self.ui.dispose)
    #        self.ui = None

    def remove_rulers(self, plotid=0):
        plot = self.plots[plotid]
        for o in plot.overlays:
            if isinstance(o, GuideOverlay):
                plot.overlays.remove(o)

    def clear_plots(self):
        x = range(len(self.plots))

        self.xdataname_generators = [
            self._name_generator_factory('x') for _ in x
        ]
        self.ydataname_generators = [
            self._name_generator_factory('y') for _ in x
        ]
        self.yerdataname_generators = [
            self._name_generator_factory('yer') for _ in x
        ]

        self.color_generators = [color_generator() for _ in x]
        #         for po in x:

        self.series = [[] for _ in x]
        self.data_len = [[] for _ in x]
        self.data_limits = [[] for _ in x]

        #         print '====== {}'.format(self)
        #         print 'len plots {}'.format(len(self.plots))
        for pi in self.plots:
            #             print 'len pi.renderers {}'.format(len(pi.plots.keys()))
            for k, pp in pi.plots.items():
                for renderer in pp:
                    try:
                        pi.remove(renderer)
                    except RuntimeError:
                        print 'failed removing {}'.format(renderer)

                pi.plots.pop(k)
                #             print 'len psss.renderers {}'.format(len(pi.plots.keys()))

        self.clear_data()

    def clear(self, clear_container=True):
        """
        """
        self.clear_plots()

        self.plots = []

        self.xdataname_generators = [self._name_generator_factory('x')]
        self.ydataname_generators = [self._name_generator_factory('y')]
        self.yerdataname_generators = [self._name_generator_factory('yer')]

        self.color_generators = [color_generator()]

        self.series = []
        self.data_len = []
        self.data_limits = []

        if clear_container:
            self.plotcontainer = pc = self.container_factory()
            if self.use_context_menu:
                menu = ContextualMenuTool(parent=self, component=pc)

                pc.tools.append(menu)

        self.selected_plot = None

    def set_axis_label_color(self, *args, **kw):
        """
        """

        kw['attr'] = 'title'
        self._set_axis_color(*args, **kw)

    def set_axis_tick_color(self, *args, **kw):
        """
        """
        attrs = ['tick_label', 'tick']
        if 'attrs' in kw:
            attrs = kw['attrs']

        for a in attrs:
            kw['attr'] = a
            self._set_axis_color(*args, **kw)

    def set_aux_data(self, x, y, plotid=0, series=1):
        p = self.plots[plotid].plots['aux{:03d}'.format(series)][0]
        p.index.set_data(x)
        p.value.set_data(y)

    def clear_data(self, plotid=None, **kw):
        if plotid is None:
            for i, p in enumerate(self.plots):
                for s in self.series[i]:
                    for k in s:
                        p.data.set_data(k, [])
        else:
            self.set_data([], **kw)

    def set_data(self, d, plotid=0, series=0, axis=0):
        """
        """
        if isinstance(series, int):
            n = self.series[plotid][series]
            series = n[axis]

        self.plots[plotid].data.set_data(series, d)

    def set_axis_traits(self, plotid=0, axis='x', **kw):
        """
        """
        plot = self.plots[plotid]

        attr = getattr(plot, '{}_axis'.format(axis))
        attr.trait_set(**kw)

    def set_grid_traits(self, plotid=0, grid='x', **kw):
        """
        """
        plot = self.plots[plotid]

        attr = getattr(plot, '{}_grid'.format(grid))
        attr.trait_set(**kw)

    def set_series_traits(self, d, plotid=0, series=0):
        """
        """
        plot = self.plots[plotid].plots['plot%i' % series][0]
        plot.trait_set(**d)
        self.plotcontainer.request_redraw()

    def get_series_color(self, plotid=0, series=0):
        if isinstance(series, int):
            series = 'plot{:03d}'.format(series)

        p = self.plots[plotid].plots[series][0]
        return p.color

    #    def set_series_type(self, t, plotid = 0, series = 0):
    #        '''

    #        '''
    #        p = self.plots[plotid]
    #        s = 'plot%i' % series
    #
    #        ss = p.plots[s][0]
    #        x = ss.index.get_data()
    #        y = ss.value.get_data()
    #
    #        p.delplot(s)
    #
    #        series,plot=self.new_series(x = x, y = y, color = ss.color, plotid = plotid, type = t, add = True)
    #        self.plotcontainer.invalidate_and_redraw()
    #        return series
    #        #self.refresh_editor()
    def get_series_label(self, plotid=0, series=0):
        """
        """
        r = ''
        legend = self.plots[plotid].legend
        if isinstance(series, str):
            if series in legend.labels:
                return series
            return

        try:
            r = legend.labels[series]
        except IndexError:
            pass

        return r

    def set_series_label(self, label, plotid=0, series=None):
        """

            A chaco update requires that the legends labels match the keys in the plot dict

            save the plots from the dict
            resave them with label as the key

        """

        legend = self.plots[plotid].legend
        if series is None:
            n = len(self.plots[plotid].plots.keys())
            series = n - 1

        if isinstance(series, int):
            series = 'plot{}'.format(series)

        try:
            legend.labels[series] = label
        except Exception, e:
            legend.labels.append(label)

        try:
            plots = self.plots[plotid].plots[series]
        except KeyError:
            print self.plots[plotid].plots.keys()
            raise

        self.plots[plotid].plots[label] = plots
        self.plots[plotid].plots.pop(series)
class Tree(Widget):
    """ A tree control with a model/ui architecture. """

    # The default tree style.
    STYLE = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN

    #### 'Tree' interface #####################################################

    # The tree's filters (empty if no filtering is required).
    filters = List(Filter)

    # Mode for lines connecting tree nodes which emphasize hierarchy:
    # 'appearance' - only on when lines look good,
    # 'on' - always on, 'off' - always off
    # NOTE: on and off are ignored in favor of show_lines for now
    lines_mode = Enum('appearance', 'on', 'off')

    # The model that provides the data for the tree.
    model = Instance(TreeModel, ())

    # The root of the tree (this is for convenience, it just delegates to
    # the tree's model).
    root = Property(Any)

    # The objects currently selected in the tree.
    selection = List

    # Selection mode.
    selection_mode = Enum('single', 'extended')

    # Should an image be shown for each node?
    show_images = Bool(True)

    # Should lines be drawn between levels in the tree.
    show_lines = Bool(True)

    # Should the root of the tree be shown?
    show_root = Bool(True)

    # The tree's sorter (None if no sorting is required).
    sorter = Instance(Sorter)

    #### Events ####

    # A right-click occurred on the control (not a node!).
    control_right_clicked = Event  #(Point)

    # A key was pressed while the tree has focus.
    key_pressed = Event(KeyPressedEvent)

    # A node has been activated (ie. double-clicked).
    node_activated = Event  #(Any)

    # A drag operation was started on a node.
    node_begin_drag = Event  #(Any)

    # A (non-leaf) node has been collapsed.
    node_collapsed = Event  #(Any)

    # A (non-leaf) node has been expanded.
    node_expanded = Event  #(Any)

    # A left-click occurred on a node.
    #
    # Tuple(node, point).
    node_left_clicked = Event  #(Tuple)

    # A right-click occurred on a node.
    #
    # Tuple(node, point)
    node_right_clicked = Event  #(Tuple)

    #### Private interface ####################################################

    # A name to distinguish the tree for debugging!
    #
    # fixme: This turns out to be kinda useful... Should 'Widget' have a name
    # trait?
    _name = Str('Anonymous tree')

    # An optional callback to detect the end of a label edit.  This is
    # useful because the callback will be invoked even if the node label was
    # not actually changed.
    _label_edit_callback = Trait(None, Callable, None)

    # Flag for allowing selection events to be ignored
    _ignore_selection_events = Bool(False)

    ###########################################################################
    # 'object' interface.
    ###########################################################################

    def __init__(self, parent, image_size=(16, 16), **traits):
        """ Creates a new tree.

        'parent' is the toolkit-specific control that is the tree's parent.

        'image_size' is a tuple in the form (int width, int height) that
        specifies the size of the images (if required) displayed in the tree.

        """

        # Base class constructors.
        super(Tree, self).__init__(**traits)

        # Get our wx Id.
        wxid = wx.NewId()

        # Create the toolkit-specific control.
        self.control = tree = _Tree(self,
                                    parent,
                                    wxid,
                                    style=self._get_style())

        # Wire up the wx tree events.
        tree.Bind(wx.EVT_CHAR, self._on_char)
        tree.Bind(wx.EVT_LEFT_DOWN, self._on_left_down)
        # fixme: This is not technically correct as context menus etc should
        # appear on a right up (or right click).  Unfortunately,  if we
        # change this to 'EVT_RIGHT_UP' wx does not fire the event unless the
        # right mouse button is double clicked 8^()  Sad,  but true!
        tree.Bind(wx.EVT_RIGHT_DOWN, self._on_right_down)
        # fixme: This is not technically correct as we would really like to use
        # 'EVT_TREE_ITEM_ACTIVATED'. Unfortunately, (in 2.6 at least), it
        # throws an exception when the 'Enter' key is pressed as the wx tree
        # item Id in the event seems to be invalid. It also seems to cause
        # any child frames that my be created in response to the event to
        # appear *behind* the parent window, which is, errrr, not great ;^)
        tree.Bind(wx.EVT_LEFT_DCLICK, self._on_tree_item_activated)
        #wx.EVT_TREE_ITEM_ACTIVATED(tree, wxid, self._on_tree_item_activated)
        tree.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self._on_tree_item_collapsing)
        tree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self._on_tree_item_collapsed)
        tree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
        tree.Bind(wx.EVT_TREE_ITEM_EXPANDED, self._on_tree_item_expanded)
        tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self._on_tree_begin_label_edit)
        tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self._on_tree_end_label_edit)
        tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self._on_tree_begin_drag)
        tree.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_sel_changed)
        tree.Bind(wx.EVT_TREE_DELETE_ITEM, self._on_tree_delete_item)

        # Enable the tree as a drag and drop target.
        self.control.SetDropTarget(PythonDropTarget(self))

        # The image list is a wxPython-ism that caches all images used in the
        # control.
        self._image_list = ImageList(image_size[0], image_size[1])
        if self.show_images:
            tree.AssignImageList(self._image_list)

        # Mapping from node to wx tree item Ids.
        self._node_to_id_map = {}

        # Add the root node.
        if self.root is not None:
            self._add_root_node(self.root)

        # Listen for changes to the model.
        self._add_model_listeners(self.model)

        return

    ###########################################################################
    # 'Tree' interface.
    ###########################################################################

    #### Properties ###########################################################

    def _get_root(self):
        """ Returns the root node of the tree. """

        return self.model.root

    def _set_root(self, root):
        """ Sets the root node of the tree. """

        self.model.root = root

        return

    #### Methods ##############################################################

    def collapse(self, node):
        """ Collapses the specified node. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self.control.Collapse(wxid)

        return

    def edit_label(self, node, callback=None):
        """ Edits the label of the specified node.

        If a callback is specified it will be called when the label edit
        completes WHETHER OR NOT the label was actually changed.

        The callback must take exactly 3 arguments:- (tree, node, label)

        """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self._label_edit_callback = callback
            self.control.EditLabel(wxid)

        return

    def expand(self, node):
        """ Expands the specified node. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self.control.Expand(wxid)

        return

    def expand_all(self):
        """ Expands every node in the tree. """

        if self.show_root:
            self._expand_item(self._get_wxid(self.root))

        else:
            for child in self._get_children(self.root):
                self._expand_item(self._get_wxid(child))

        return

    def get_parent(self, node):
        """ Returns the parent of a node.

        This will only work iff the node has been displayed in the tree.  If it
        hasn't then None is returned.

        """

        # Has the node actually appeared in the tree yet?
        wxid = self._get_wxid(node)
        if wxid is not None:
            pid = self.control.GetItemParent(wxid)

            # The item data is a tuple.  The first element indicates whether or
            # not we have already populated the item with its children.  The
            # second element is the actual item data.
            populated, parent = self.control.GetItemData(pid)

        else:
            parent = None

        return parent

    def is_expanded(self, node):
        """ Returns True if the node is expanded, otherwise False. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            # If the root node is hidden then it is always expanded!
            if node is self.root and not self.show_root:
                is_expanded = True

            else:
                is_expanded = self.control.IsExpanded(wxid)

        else:
            is_expanded = False

        return is_expanded

    def is_selected(self, node):
        """ Returns True if the node is selected, otherwise False. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            is_selected = self.control.IsSelected(wxid)

        else:
            is_selected = False

        return is_selected

    def refresh(self, node):
        """ Refresh the tree starting from the specified node.

        Call this when the structure of the content has changed DRAMATICALLY.

        """

        # Has the node actually appeared in the tree yet?
        pid = self._get_wxid(node)
        if pid is not None:
            # Delete all of the node's children and re-add them.
            self.control.DeleteChildren(pid)
            self.control.SetItemData(pid, (False, node))

            # Does the node have any children?
            has_children = self._has_children(node)
            self.control.SetItemHasChildren(pid, has_children)

            # fixme: At least on Windows, wx does not fire an expanding
            # event for a hidden root node, so we have to populate the node
            # manually.
            if node is self.root and not self.show_root:
                # Add the child nodes.
                for child in self._get_children(node):
                    self._add_node(pid, child)

            else:
                # Expand it.
                if self.control.IsExpanded(pid):
                    self.control.Collapse(pid)

                self.control.Expand(pid)

        return

    def select(self, node):
        """ Selects the specified node. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self.control.SelectItem(wxid)

        return

    def set_selection(self, list):
        """ Selects the specified list of nodes. """
        logger.debug('Setting selection to [%s] within Tree [%s]', list, self)

        # Update the control to reflect the target list by unselecting
        # everything and then selecting each item in the list.  During this
        # process, we want to avoid changing our own selection.
        self._ignore_selection_events = True
        self.control.UnselectAll()
        for node in list:
            try:
                self.select(node)
            except:
                logger.exception('Unable to select node [%s]', node)

        self._ignore_selection_events = False

        # Update our selection to reflect the final selection state.
        self.selection = self._get_selection()

    ###########################################################################
    # 'PythonDropTarget' interface.
    ###########################################################################

    def on_drag_over(self, x, y, obj, default_drag_result):
        """ Called when a node is dragged over the tree. """

        result = wx.DragNone

        # Find the node that we are dragging over...
        node = self._get_drag_drop_node(x, y)
        if node is not None:
            # Ask the model if the node allows the object to be dropped onto
            # it.
            if self.model.can_drop(node, obj):
                result = default_drag_result

        return result

    def on_drop(self, x, y, obj, default_drag_result):
        """ Called when a node is dropped on the tree. """

        # Find the node that we are dragging over...
        node = self._get_drag_drop_node(x, y)
        if node is not None:
            self.model.drop(node, obj)

        return default_drag_result

    ###########################################################################
    # Private interface.
    ###########################################################################

    def _get_wxid(self, node):
        """ Returns the wxid for the specified node.

        Returns None if the node has not yet appeared in the tree.

        """

        # The model must generate a unique key for each node (unique within the
        # model).
        key = self.model.get_key(node)

        return self._node_to_id_map.get(key, None)

    def _set_wxid(self, node, wxid):
        """ Sets the wxid for the specified node. """

        # The model must generate a unique key for each node (unique within the
        # model).
        key = self.model.get_key(node)

        self._node_to_id_map[key] = wxid

        return

    def _remove_wxid(self, node):
        """ Removes the wxid for the specified node. """

        # The model must generate a unique key for each node (unique within the
        # model).
        key = self.model.get_key(node)

        try:
            del self._node_to_id_map[key]

        except KeyError:
            # fixme: No, really, this is a serious one... How do we get in this
            # situation.  It came up when using the canvas stuff...
            logger.warn('removing node: %s' % str(node))

        return

    def _get_style(self):
        """ Returns the wx style flags for creating the tree control. """

        # Start with the default flags.
        style = self.STYLE

        # Turn lines off for appearance on *nix.
        # ...for now, show_lines determines if lines are on or off, but
        # eventually lines_mode may eliminate the need for show_lines
        if self.lines_mode == 'appearance' and os.name == 'posix':
            self.show_lines = False

        if not self.show_lines:
            style = style | wx.TR_NO_LINES

        if not self.show_root:
            # fixme: It looks a little weird, but it we don't have the
            # 'lines at root' style then wx won't draw the expand/collapse
            # image on non-leaf nodes at the root level 8^()
            style = style | wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT

        if self.selection_mode != 'single':
            style = style | wx.TR_MULTIPLE | wx.TR_EXTENDED

        return style

    def _add_model_listeners(self, model):
        """ Adds listeners for model changes. """

        # Listen for changes to the model.
        model.on_trait_change(self._on_root_changed, 'root')
        model.on_trait_change(self._on_nodes_changed, 'nodes_changed')
        model.on_trait_change(self._on_nodes_inserted, 'nodes_inserted')
        model.on_trait_change(self._on_nodes_removed, 'nodes_removed')
        model.on_trait_change(self._on_nodes_replaced, 'nodes_replaced')
        model.on_trait_change(self._on_structure_changed, 'structure_changed')

        return

    def _remove_model_listeners(self, model):
        """ Removes listeners for model changes. """

        # Unhook the model event listeners.
        model.on_trait_change(self._on_root_changed, 'root', remove=True)

        model.on_trait_change(self._on_nodes_changed,
                              'nodes_changed',
                              remove=True)

        model.on_trait_change(self._on_nodes_inserted,
                              'nodes_inserted',
                              remove=True)

        model.on_trait_change(self._on_nodes_removed,
                              'nodes_removed',
                              remove=True)

        model.on_trait_change(self._on_nodes_replaced,
                              'nodes_replaced',
                              remove=True)

        model.on_trait_change(self._on_structure_changed,
                              'structure_changed',
                              remove=True)

        return

    def _add_root_node(self, node):
        """ Adds the root node. """

        # Get the tree item image index and the label text.
        image_index = self._get_image_index(node)
        text = self._get_text(node)

        # Add the node.
        wxid = self.control.AddRoot(text, image_index, image_index)

        # This gives the model a chance to wire up trait handlers etc.
        self.model.add_listener(node)

        # If the root node is hidden, get its children.
        if not self.show_root:
            # Add the child nodes.
            for child in self._get_children(node):
                self._add_node(wxid, child)

        # Does the node have any children?
        has_children = self._has_children(node)
        self.control.SetItemHasChildren(wxid, has_children)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data (which in our case is an arbitrary
        # Python object provided by the tree model).
        if self.show_root:
            self.control.SetItemData(wxid, (not self.show_root, node))

        # Make sure that we can find the node's Id.
        self._set_wxid(node, wxid)

        # Automatically expand the root.
        if self.show_root:
            self.control.Expand(wxid)

        return

    def _add_node(self, pid, node):
        """ Adds 'node' as a child of the node identified by 'pid'.

        If 'pid' is None then we are adding the root node.

        """

        # Get the tree item image index and the label text.
        image_index = self._get_image_index(node)
        text = self._get_text(node)

        # Add the node.
        wxid = self.control.AppendItem(pid, text, image_index, image_index)

        # This gives the model a chance to wire up trait handlers etc.
        self.model.add_listener(node)

        # Does the node have any children?
        has_children = self._has_children(node)
        self.control.SetItemHasChildren(wxid, has_children)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data (which in our case is an arbitrary
        # Python object provided by the tree model).
        self.control.SetItemData(wxid, (False, node))

        # Make sure that we can find the node's Id.
        self._set_wxid(node, wxid)

        return

    def _insert_node(self, pid, node, index):
        """ Inserts 'node' as a child of the node identified by 'pid'.

        If 'pid' is None then we are adding the root node.

        """

        # Get the tree item image index and the label text.
        image_index = self._get_image_index(node)
        text = self._get_text(node)

        # Add the node.
        wxid = self.control.Sizer.InsertBefore(pid, index, text, image_index,
                                               image_index)

        # This gives the model a chance to wire up trait handlers etc.
        self.model.add_listener(node)

        # Does the node have any children?
        has_children = self._has_children(node)
        self.control.SetItemHasChildren(wxid, has_children)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data (which in our case is an arbitrary
        # Python object provided by the tree model).
        self.control.SetItemData(wxid, (False, node))

        # Make sure that we can find the node's Id.
        self._set_wxid(node, wxid)

        return

    def _remove_node(self, wxid, node):
        """ Removes a node from the tree. """

        # This gives the model a chance to remove trait handlers etc.
        self.model.remove_listener(node)

        # Remove the reference to the item's data.
        self._remove_wxid(node)
        self.control.SetItemData(wxid, None)

        return

    def _update_node(self, wxid, node):
        """ Updates the image and text of the specified node. """

        # Get the tree item image index.
        image_index = self._get_image_index(node)
        self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Normal)
        self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Selected)

        # Get the tree item text.
        text = self._get_text(node)
        self.control.SetItemText(wxid, text)

        return

    def _has_children(self, node):
        """ Returns True if a node has children. """

        # fixme: To be correct we *should* apply filtering here, but that
        # seems to blow a hole throught models that have some efficient
        # mechanism for determining whether or not they have children.  There
        # is also a precedent for doing it this way in Windoze, where a node
        # gets marked as though it can be expanded, even thought when the
        # expansion occurs, no children are present!
        return self.model.has_children(node)

    def _get_children(self, node):
        """ Get the children of a node. """

        children = self.model.get_children(node)

        # Filtering....
        filtered_children = []
        for child in children:
            for filter in self.filters:
                if not filter.select(self, node, child):
                    break

            else:
                filtered_children.append(child)

        # Sorting...
        if self.sorter is not None:
            self.sorter.sort(self, node, filtered_children)

        return filtered_children

    def _get_image_index(self, node):
        """ Returns the tree item image index for a node. """

        expanded = self.is_expanded(node)
        selected = self.is_selected(node)

        # Get the image used to represent the node.
        image = self.model.get_image(node, selected, expanded)
        if image is not None:
            image_index = self._image_list.GetIndex(image)

        else:
            image_index = -1

        return image_index

    def _get_drag_drop_node(self, x, y):
        """ Returns the node that is being dragged/dropped on.

        Returns None if the cursor is not over the icon or label of a node.

        """

        data, wxid, flags, point = self._hit_test((x, y))
        if data is not None:
            populated, node = data

        else:
            node = None

        return node

    def _get_text(self, node):
        """ Returns the tree item text for a node. """

        text = self.model.get_text(node)
        if text is None:
            text = ''

        return text

    def _unpack_event(self, event, wxid=None):
        """ Unpacks the event to see whether a tree item was involved. """

        try:
            point = event.GetPosition()

        except:
            point = event.GetPoint()

        return self._hit_test(point, wxid)

    def _hit_test(self, point, wxid=None):
        """ Determines whether a point is within a node's label or icon. """

        flags = wx.TREE_HITTEST_ONITEMLABEL
        if (wxid is None) or (not wxid.IsOk()):
            wxid, flags = self.control.HitTest(point)

        # Warning: On GTK we have to check the flags before we call 'GetPyData'
        # because if we call it when the hit test returns 'nowhere' it will
        # barf (on Windows it simply returns 'None' 8^()
        if flags & wx.TREE_HITTEST_NOWHERE:
            data = None

        elif flags & wx.TREE_HITTEST_ONITEMICON \
             or flags & wx.TREE_HITTEST_ONITEMLABEL:

            data = self.control.GetItemData(wxid)

        # fixme: Not sure why 'TREE_HITTEST_NOWHERE' doesn't catch everything!
        else:
            data = None

        return data, wxid, flags, point

    def _get_selection(self):
        """ Returns a list of the selected nodes """

        selection = []
        for wxid in self.control.GetSelections():
            data = self.control.GetItemData(wxid)
            if data is not None:
                populated, node = data
                selection.append(self.model.get_selection_value(node))

        return selection

    def _expand_item(self, wxid):
        """ Recursively expand a tree item. """

        self.control.Expand(wxid)

        cid, cookie = self.control.GetFirstChild(wxid)
        while cid.IsOk():
            self._expand_item(cid)
            cid, cookie = self.control.GetNextChild(wxid, cookie)

        return

    #### Trait event handlers #################################################

    def _on_root_changed(self, root):
        """ Called when the root of the model has changed. """

        # Delete everything...
        if self.control is not None:
            self.control.DeleteAllItems()

            self._node_to_id_map = {}

            # ... and then add the root item back in.
            if root is not None:
                self._add_root_node(root)

        return

    def _on_nodes_changed(self, event):
        """ Called when nodes have been changed. """

        self._update_node(self._get_wxid(event.node), event.node)

        for child in event.children:
            cid = self._get_wxid(child)
            if cid is not None:
                self._update_node(cid, child)

        return

    def _on_nodes_inserted(self, event):
        """ Called when nodes have been inserted. """

        parent = event.node
        children = event.children
        index = event.index

        # Has the node actually appeared in the tree yet?
        pid = self._get_wxid(parent)
        if pid is not None:
            # The item data is a tuple.  The first element indicates whether or
            # not we have already populated the item with its children.  The
            # second element is the actual item data.
            if self.show_root or parent is not self.root:
                populated, node = self.control.GetItemData(pid)

            else:
                populated = True

            # If the node is not yet populated then just get the children and
            # add them.
            if not populated:
                for child in self._get_children(parent):
                    self._add_node(pid, child)

            # Otherwise, insert them.
            else:
                # An index of -1 means append!
                if index == -1:
                    index = self.control.GetChildrenCount(pid, False)

                for child in children:
                    self._insert_node(pid, child, index)
                    index += 1

            # The element is now populated!
            if self.show_root or parent is not self.root:
                self.control.SetItemData(pid, (True, parent))

            # Does the node have any children now?
            has_children = self.control.GetChildrenCount(pid) > 0
            self.control.SetItemHasChildren(pid, has_children)

            # If the node is not expanded then expand it.
            if not self.is_expanded(parent):
                self.expand(parent)

        return

    def _on_nodes_removed(self, event):
        """ Called when nodes have been removed. """

        parent = event.node
        children = event.children

        # Has the node actually appeared in the tree yet?
        pid = self._get_wxid(parent)
        if pid is not None:
            for child in event.children:
                cid = self._get_wxid(child)
                if cid is not None:
                    self.control.Delete(cid)

            # Does the node have any children left?
            has_children = self.control.GetChildrenCount(pid) > 0
            self.control.SetItemHasChildren(pid, has_children)

        return

    def _on_nodes_replaced(self, event):
        """ Called when nodes have been replaced. """

        for old_child, new_child in zip(event.old_children, event.children):
            cid = self._get_wxid(old_child)
            if cid is not None:
                # Remove listeners from the old node.
                self.model.remove_listener(old_child)

                # Delete all of the node's children.
                self.control.DeleteChildren(cid)

                # Update the visual appearance of the node.
                self._update_node(cid, new_child)

                # Update the node data.
                #
                # The item data is a tuple.  The first element indicates
                # whether or not we have already populated the item with its
                # children. The second element is the actual item data (which
                # in our case is an arbitrary Python object provided by the
                # tree model).
                self.control.SetItemData(cid, (False, new_child))

                # Remove the old node from the node to Id map.
                self._remove_wxid(old_child)

                # Add the new node to the node to Id map.
                self._set_wxid(new_child, cid)

                # Add listeners to the new node.
                self.model.add_listener(new_child)

                # Does the new node have any children?
                has_children = self._has_children(new_child)
                self.control.SetItemHasChildren(cid, has_children)

        # Update the tree's selection (in case the old node that was replaced
        # was selected, the selection should now include the new node).
        self.selection = self._get_selection()
        return

    def _on_structure_changed(self, event):
        """ Called when the structure of a node has changed drastically. """

        self.refresh(event.node)

        return

    #### wx event handlers ####################################################

    def _on_char(self, event):
        """ Called when a key is pressed when the tree has focus. """

        self.key_pressed = KeyPressedEvent(alt_down=event.AltDown(),
                                           control_down=event.ControlDown(),
                                           shift_down=event.ShiftDown(),
                                           key_code=event.KeyCode)

        event.Skip()

        return

    def _on_left_down(self, event):
        """ Called when the left mouse button is clicked on the tree. """

        data, id, flags, point = self._unpack_event(event)

        # Save point for tree_begin_drag method to workaround a bug in ?? when
        # wx.TreeEvent.GetPoint returns only (0,0).  This happens under linux
        # when using wx-2.4.2.4, for instance.
        self._point_left_clicked = point

        # Did the left click occur on a tree item?
        if data is not None:
            populated, node = data

            # Trait event notification.
            self.node_left_clicked = node, point

        # Give other event handlers a chance.
        event.Skip()

        return

    def _on_right_down(self, event):
        """ Called when the right mouse button is clicked on the tree. """

        data, id, flags, point = self._unpack_event(event)

        # Did the right click occur on a tree item?
        if data is not None:
            populated, node = data

            # Trait event notification.
            self.node_right_clicked = node, point

        # Otherwise notify that the control itself was clicked
        else:
            self.control_right_clicked = point

        # Give other event handlers a chance.
        event.Skip()

        return

    def _on_tree_item_activated(self, event):
        """ Called when a tree item is activated (i.e., double clicked). """

        # fixme: See the comment where the events are wired up for more
        # information.

        ##         # Which item was activated?
        ##         wxid = event.GetItem()

        # Which item was activated.
        point = event.GetPosition()
        wxid, flags = self.control.HitTest(point)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Trait event notiification.
        self.node_activated = node

        return

    def _on_tree_item_collapsing(self, event):
        """ Called when a tree item is about to collapse. """

        # Which item is collapsing?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Give the model a chance to veto the collapse.
        if not self.model.is_collapsible(node):
            event.Veto()

        return

    def _on_tree_item_collapsed(self, event):
        """ Called when a tree item has been collapsed. """

        # Which item was collapsed?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Make sure that the item's 'closed' icon is displayed etc.
        self._update_node(wxid, node)

        # Trait event notification.
        self.node_collapsed = node

        return

    def _on_tree_item_expanding(self, event):
        """ Called when a tree item is about to expand. """

        # Which item is expanding?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Give the model a chance to veto the expansion.
        if self.model.is_expandable(node):
            # Lazily populate the item's children.
            if not populated:
                # Add the child nodes.
                for child in self._get_children(node):
                    self._add_node(wxid, child)

                # The element is now populated!
                self.control.SetItemData(wxid, (True, node))

        else:
            event.Veto()

        return

    def _on_tree_item_expanded(self, event):
        """ Called when a tree item has been expanded. """

        # Which item was expanded?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Make sure that the node's 'open' icon is displayed etc.
        self._update_node(wxid, node)

        # Trait event notification.
        self.node_expanded = node

        return

    def _on_tree_begin_label_edit(self, event):
        """ Called when the user has started editing an item's label. """

        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Give the model a chance to veto the edit.
        if not self.model.is_editable(node):
            event.Veto()

        return

    def _on_tree_end_label_edit(self, event):
        """ Called when the user has finished editing am item's label. """

        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetItemData(wxid)

        # Give the model a chance to veto the edit.
        label = event.GetLabel()

        # Making sure the new label is not an empty string

        if label is not None and len(label) > 0 and \
            self.model.can_set_text(node, label):

            def end_label_edit():
                """ Called to complete the label edit. """

                # Set the node's text.
                self.model.set_text(node, label)

                # If a label edit callback was specified (in the call to
                # 'edit_label'), then call it).
                if self._label_edit_callback is not None:
                    self._label_edit_callback(self, node, label)

                return

            # We use a deffered call here, because a name change can trigger
            # the structure of a node to change, and hence the actual tree
            # nodes might get moved/deleted before the label edit operation has
            # completed.  When this happens wx gets very confused!  By using
            # 'invoke_later' we allow the label edit to complete.
            GUI.invoke_later(end_label_edit)

        else:
            event.Veto()

            # If a label edit callback was specified (in the call to
            # 'edit_label'), then call it).
            if self._label_edit_callback is not None:
                self._label_edit_callback(self, node, label)

        return

    def _on_tree_begin_drag(self, event):
        """ Called when a drag operation is starting on a tree item. """

        # Get the node, its id and the point where the event occurred.
        data, wxid, flags, point = self._unpack_event(event, event.GetItem())

        if point == (0, 0):
            # Apply workaround for GTK.
            point = self.point_left_clicked
            wxid, flags = self.HitTest(point)
            data = self.control.GetItemData(wxid)

        if data is not None:
            populated, node = data

            # Give the model a chance to veto the drag.
            if self.model.is_draggable(node):
                # We ask the model for the actual value to drag.
                drag_value = self.model.get_drag_value(node)

                # fixme: This is a terrible hack to get the binding x passed
                # during a drag operation.  Bindings should probably *always*
                # be dragged and our drag and drop mechanism should allow
                # extendable ways to extract the actual data.
                from pyface.wx.drag_and_drop import clipboard
                clipboard.node = [node]

                # Make sure that the tree selection is updated before we start
                # the drag. If we don't do this then if the first thing a
                # user does is drag a tree item (i.e., without a separate click
                # to select it first) then the selection appears empty.
                self.selection = self._get_selection()

                # Start the drag.
                PythonDropSource(self.control, drag_value, self)

                # Trait event notification.
                self.node_begin_drag = node

            else:
                event.Veto()

        return

    # fixme: This is part of the drag and drop hack...
    def on_dropped(self):
        """ Callback invoked when a drag/drop operation has completed. """

        from pyface.wx.drag_and_drop import clipboard
        clipboard.node = None

        return

    def _on_tree_sel_changed(self, event):
        """ Called when the selection is changed. """

        # Update our record of the selection to whatever was selected in the
        # tree UNLESS we are ignoring selection events.
        if not self._ignore_selection_events:

            # Trait notification.
            self.selection = self._get_selection()

        return

    def _on_tree_delete_item(self, event):
        """ Called when a tree item is being been deleted. """

        # Which item is being deleted?
        wxid = event.GetItem()

        # Check if GetPyData() returned a valid to tuple to unpack
        # ...if so, remove the node from the tree, otherwise just return
        #
        # fixme: Whoever addeed this code (and the comment above) didn't say
        # when this was occurring. This is method is called in response to a wx
        # event to delete an item and hence the item data should never be None
        # surely?!? Was it happening just on one platform?!?
        try:
            data = self.control.GetItemData(wxid)
        except:
            data = None
            pass
        if data is not None:
            # The item data is a tuple.  The first element indicates whether or
            # not we have already populated the item with its children.  The
            # second element is the actual item data.
            populated, node = data

            # Remove the node.
            self._remove_node(wxid, node)

        return
Exemple #34
0
class HistoryControl(HasPrivateTraits):

    # The UI control:
    control = Instance(wx.Window)

    # The current value of the control:
    value = Str

    # Should 'value' be updated on every keystroke?
    auto_set = Bool(False)

    # The current history of the control:
    history = List(Str)

    # The maximum number of history entries allowed:
    entries = Int(10)

    # Is the current value valid?
    error = Bool(False)

    #-- Public Methods ---------------------------------------------------------

    def create_control(self, parent):
        """ Creates the control.
        """
        self.control = control = wx.ComboBox(parent,
                                             -1,
                                             self.value,
                                             wx.Point(0, 0),
                                             wx.Size(-1, -1),
                                             self.history,
                                             style=wx.CB_DROPDOWN)
        wx.EVT_COMBOBOX(parent, control.GetId(), self._update_value)
        wx.EVT_KILL_FOCUS(control, self._kill_focus)
        wx.EVT_TEXT_ENTER(parent, control.GetId(), self._update_text_value)
        if self.auto_set:
            wx.EVT_TEXT(parent, control.GetId(), self._update_value_only)

        return control

    def dispose(self):
        """ Disposes of the control at the end of its life cycle.
        """
        control, self.control = self.control, None
        parent = control.GetParent()
        wx.EVT_COMBOBOX(parent, control.GetId(), None)
        wx.EVT_TEXT_ENTER(parent, control.GetId(), None)
        wx.EVT_KILL_FOCUS(control, None)

    def set_value(self, value):
        """ Sets the specified value and adds it to the history.
        """
        self._update(value)

    #-- Traits Event Handlers --------------------------------------------------

    def _value_changed(self, value):
        """ Handles the 'value' trait being changed.
        """
        if not self._no_update:
            control = self.control
            if control is not None:
                control.SetValue(value)
                self._restore = False

    def _history_changed(self):
        """ Handles the 'history' being changed.
        """
        if not self._no_update:
            if self._first_time is None:
                self._first_time = False
                if (self.value == '') and (len(self.history) > 0):
                    self.value = self.history[0]

            self._load_history(select=False)

    def _error_changed(self, error):
        """ Handles the 'error' trait being changed.
        """
        if error:
            self.control.SetBackgroundColour(ErrorColor)
        else:
            self.control.SetBackgroundColour(OKColor)

        self.control.Refresh()

    #-- Wx Event Handlers ------------------------------------------------------

    def _update_value(self, event):
        """ Handles the user selecting something from the drop-down list of the
            combobox.
        """
        self._update(event.GetString())

    def _update_value_only(self, event):
        """ Handles the user typing into the text field in 'auto_set' mode.
        """
        self._no_update = True
        self.value = event.GetString()
        self._no_update = False

    def _update_text_value(self, event, select=True):
        """ Handles the user typing something into the text field of the
            combobox.
        """
        if not self._no_update:
            self._update(self.control.GetValue(), select)

    def _kill_focus(self, event):
        """ Handles the combobox losing focus.
        """
        self._update_text_value(event, False)
        event.Skip()

    #-- Private Methods --------------------------------------------------------

    def _update(self, value, select=True):
        """ Updates the value and history list based on a specified value.
        """
        self._no_update = True

        if value.strip() != '':
            history = self.history
            if (len(history) == 0) or (value != history[0]):
                if value in history:
                    history.remove(value)
                history.insert(0, value)
                del history[self.entries:]
                self._load_history(value, select)

        self.value = value

        self._no_update = False

    def _load_history(self, restore=None, select=True):
        """ Loads the current history list into the control.
        """
        control = self.control
        control.Freeze()

        if restore is None:
            restore = control.GetValue()

        control.Clear()
        for value in self.history:
            control.Append(value)

        self._restore = True
        do_later(self._thaw_value, restore, select)

    def _thaw_value(self, restore, select):
        """ Restores the value of the combobox control.
        """
        control = self.control
        if control is not None:
            if self._restore:
                control.SetValue(restore)

                if select:
                    control.SetMark(0, len(restore))

            control.Thaw()
class TasksApplicationAction(GUIApplicationAction):

    #: The Tasks application the action applies to.
    application = Instance(TasksApplication)
Exemple #36
0
class Slot(Variable):
    """A trait for an object of a particular type or implementing a particular
    interface. Both Traits Interfaces and zope.interface.Interfaces are
    supported.
    """

    def __init__(self, klass=object, allow_none=True, factory=None,
                 args=None, kw=None, **metadata):

        default_value = None
        try:
            iszopeiface = issubclass(klass, zope.interface.Interface)
        except TypeError:
            iszopeiface = False
            if not isclass(klass):
                default_value = klass
                klass = klass.__class__

        metadata.setdefault('copy', 'deep')

        self._allow_none = allow_none
        self.klass = klass

        if has_interface(klass, IContainer) or \
           (isclass(klass) and IContainer.implementedBy(klass)):
            self._is_container = True
        else:
            self._is_container = False

        if iszopeiface:
            self._instance = None
            self.factory = factory
            self.args = args
            self.kw = kw
        else:
            self._instance = Instance(klass=klass, allow_none=allow_none,
                                      factory=factory, args=args, kw=kw,
                                      **metadata)
            if default_value:
                self._instance.default_value = default_value
            else:
                default_value = self._instance.default_value

            if klass.__name__ == 'VariableTree':
                raise TypeError('Slotting of VariableTrees is not supported,'
                                ' please use VarTree instead')

        super(Slot, self).__init__(default_value, **metadata)

    def validate(self, obj, name, value):
        ''' wrapper around Enthought validate method'''

        if value is None:
            if self._allow_none:
                return value
            self.validate_failed(obj, name, value)

        if self._instance is None:  # our iface is a zope.interface
            if not self.klass.providedBy(value):
                self._iface_error(obj, name, self.klass.__name__)
        else:
            try:
                value = self._instance.validate(obj, name, value)
            except Exception:
                if issubclass(self._instance.klass, Interface):
                    self._iface_error(obj, name, self._instance.klass.__name__)
                else:
                    obj.raise_exception("%s must be an instance of class '%s'" %
                                        (name, self._instance.klass.__name__),
                                        TypeError)

        return value

    def post_setattr(self, obj, name, value):
        '''Containers must know their place within the hierarchy, so set their
        parent here.  This keeps side effects out of validate()'''

        if self._is_container and value is not None:
            if value.parent is not obj:
                value.parent = obj

    def _iface_error(self, obj, name, iface_name):
        obj.raise_exception("%s must provide interface '%s'" %
                            (name, iface_name), TypeError)

    def get_attribute(self, name, value, trait, meta):
        """Return the attribute dictionary for this variable. This dict is
        used by the GUI to populate the edit UI. Slots also return an
        attribute dictionary for the slot pane.

        name: str
          Name of variable

        value: object
          The value of the variable

        trait: CTrait
          The variable's trait

        meta: dict
          Dictionary of metadata for this variable
        """

        io_attr = {}
        io_attr['name'] = name
        io_attr['type'] = trait.trait_type.klass.__name__
        io_attr['ttype'] = 'slot'

        slot_attr = {}
        slot_attr['name'] = name

        if value is None:
            slot_attr['filled'] = None
        elif value is []:
            slot_attr['filled'] = []
        else:
            slot_attr['filled'] = type(value).__name__

        slot_attr['klass'] = io_attr['type']
        slot_attr['containertype'] = 'singleton'

        for field in meta:
            if field not in gui_excludes:
                slot_attr[field] = meta[field]
                io_attr[field] = meta[field]

        return io_attr, slot_attr