Exemple #1
0
class SpectrumLabelOverlay(AbstractOverlay):
    display_extract_value = Bool(True)
    display_step = Bool(True)
    nsigma = Int
    font = KivaFont
    _cached_labels = List
    use_user_color = Bool
    user_color = Color

    def overlay(self, other_component, gc, view_bounds=None, mode="normal"):
        labels = self._get_labels()
        for label in labels:
            label.overlay(other_component, gc)

    def _get_labels(self):
        if self._layout_needed or not self._cached_labels:
            self._layout_needed = False
            labels = []
            nsigma = self.nsigma
            # spec = self.spectrum
            comp = self.component
            xs = comp.index.get_data()
            ys = comp.value.get_data()
            es = comp.errors
            n = len(xs)
            xs = xs.reshape(n // 2, 2)
            ys = ys.reshape(n // 2, 2)
            es = es.reshape(n // 2, 2)

            if self.use_user_color:
                color = self.user_color
            else:
                color = comp.color

            sorted_analyses = self.sorted_analyses
            for i, ((xa, xb), (ya, yb), (ea, eb)) in enumerate(zip(xs, ys, es)):
                # ui = spec.sorted_analyses[i]
                analysis = sorted_analyses[i]
                x = (xb - xa) / 2.0 + xa
                yi, ei = ya, ea
                yl = yi - ei * nsigma
                yu = yi + ei * nsigma

                (x, yl), (_, yu) = comp.map_screen([(x, yl), (x, yu)])
                y = yl - 15
                if y < 0:
                    y = yu + 10
                    if y > comp.height:
                        y = 50

                txt = self._assemble_text(analysis)
                labels.append(PlotLabel(text=txt,
                                        font=self.font,
                                        # font='modern {}'.format(self.font_size),
                                        color=color,
                                        x=x,
                                        y=y))

            self._cached_labels = labels

        return self._cached_labels

    def _assemble_text(self, ai):
        ts = []
        if self.display_step:
            ts.append(ai.step)

        if self.display_extract_value:
            ts.append('{:n}'.format(ai.extract_value))

        return '\n'.join(ts)

    @on_trait_change('component.+')
    def _handle_component_change(self, name, new):
        self._layout_needed = True
        self.request_redraw()

    @on_trait_change('display_extract_value, display_step')
    def _update_visible(self):
        self.visible = self.display_extract_value or self.display_step
Exemple #2
0
class SimpleInspectorTool(BaseTool):
    """ Simple inspector tool for plots

    This is a simple tool that reports the data-space coordinates of the
    current mouse cursor position in a plot.

    Interested overlays and other objects can listen for new_value events,
    which is a dictionary of data about the current location in data space,
    and can look at the last_mouse_position trait which holds the mouse
    position in screen space.

    The tool also provides a visible trait which listeners can use to hide
    themselves.  By default the 'p' key toggles this.

    Instances can provide a value_generator function that performs computations
    to generate additional values in the dictionary that is passed to the
    new_value event.  Subclasses can override gather_values() to similar
    effect.
    """

    #: This event fires whenever the mouse moves over a new image point.
    #: Its value is a dict with default keys "x", "y", "index" and "value".
    new_value = Event

    #: Indicates whether overlays listening to this tool should be visible.
    visible = Bool(True)

    #: Stores the last mouse position.  This can be used by overlays to
    #: position themselves around the mouse.
    last_mouse_position = Tuple

    #: This key will show and hide any overlays listening to this tool.
    inspector_key = KeySpec('p')

    #: A callable that computes other values for the new_value event
    #: this takes a dictionary as an argument, and returns a dictionary
    value_generator = Callable

    # Private Trails ########################################################

    # Stores the value of self.visible when the mouse leaves the tool,
    # so that it can be restored when the mouse enters again.
    _old_visible = Enum(None, True, False)  #Trait(None, Bool(True))

    #########################################################################
    # SimpleInspectorTool API
    #########################################################################

    def gather_values(self, event):
        """ Generate the values for the new_value dictionary.

        By default this returns a dictionary with keys "x", "y", "index" and
        "value".  If there is a value_generator callable, this will be called
        to modify the dictionary.

        Parameters
        ----------

        event
            The mouse_move event.

        Returns
        -------

        A dictionary.
        """
        x, y, index, value = self.map_to_data(event.x, event.y)
        d = {'index': index, 'value': value, 'x': x, 'y': y}

        if isinstance(self.component, ImagePlot):
            x_ndx, y_ndx = self.component.map_index((event.x, event.y),
                                                    outside_returns_none=False)

            # FIXME: off-by-one error. The size of the index is +1 to the size of
            # the image array
            if y_ndx == self.component.value.data.shape[0]:
                y_ndx -= 1
            if x_ndx == self.component.value.data.shape[1]:
                x_ndx += 1

            z = self.component.value.data[y_ndx, x_ndx]
            d['z'] = z
            d['color'] = z

        if self.value_generator is not None:
            d = self.value_generator(d)
        return d

    def map_to_data(self, x, y):
        """ Returns the data space coordinates of the given x and y.

        Takes into account orientation of the plot and the axis setting.
        """

        plot = self.component
        if plot.orientation == "h":
            index = x = plot.x_mapper.map_data(x)
            value = y = plot.y_mapper.map_data(y)
        else:
            index = y = plot.y_mapper.map_data(y)
            value = x = plot.x_mapper.map_data(x)
        return x, y, index, value

    #########################################################################
    # Component API
    #########################################################################

    def normal_key_pressed(self, event):
        if self.inspector_key.match(event):
            self.visible = not self.visible

    def normal_mouse_leave(self, event):
        if self._old_visible is None:
            self._old_visible = self.visible
            self.visible = False

    def normal_mouse_enter(self, event):
        if self._old_visible is not None:
            self.visible = self._old_visible
            self._old_visible = None

    def normal_mouse_move(self, event):
        plot = self.component
        if plot is not None:
            self.new_value = self.gather_values(event)
            self.last_mouse_position = (event.x, event.y)
Exemple #3
0
class ColorTranslationOp(HasStrictTraits):
    """
    Translate measurements from one color's scale to another, using a two-color
    or three-color control.
    
    To use, set up the :attr:`controls` dictionary with the channels to convert
    and the FCS files to compute the mapping.  Call :meth:`estimate` to
    paramterize the module; check that the plots look good by calling the 
    :meth:`~ColorTranslationDiagnostic.plot` method of the 
    :class:`ColorTranslationDiagnostic` instance returned by :meth:`default_view`;
    then call :meth:`apply` to apply the translation to an :class:`.Experiment`.
    
    Attributes
    ----------
    controls : Dict((Str, Str), File)
        Two-color controls used to determine the mapping.  They keys are 
        tuples of **from-channel** and **to-channel**.  The values are FCS files 
        containing two-color constitutive fluorescent expression data 
        for the mapping.
        
    mixture_model : Bool (default = False)
        If ``True``, try to model the **from** channel as a mixture of expressing
        cells and non-expressing cells (as you would get with a transient
        transfection), then weight the regression by the probability that the
        the cell is from the top (transfected) distribution.  Make sure you 
        check the diagnostic plots to see that this worked!
        
    linear_model : Bool (default = False)
        Set this to ``True`` to get a scaling that is strictly multiplicative,
        mirroring the TASBE approach.  Do check the diagnostic plot, though,
        to see how well (or poorly) your model fits the data.
        
    control_conditions : Dict((Str, Str), Dict(Str, Any))
        Occasionally, you'll need to specify the experimental conditions that
        the bleedthrough tubes were collected under (to apply the operations in the 
        history.)  Specify them here.  The key is a tuple of channel names; the 
        value is a dictionary of the conditions (same as you would specify for a
        :class:`~.Tube` )

        
    Notes
    -----
    In the TASBE workflow, this operation happens *after* the application of
    :class:`.AutofluorescenceOp` and :class:`.BleedthroughLinearOp`.  The entire
    operation history of the :class:`.Experiment` that is passed to 
    :meth:`estimate` is replayed on the control files in :attr:`controls`, so
    they are also corrected for autofluorescence and bleedthrough, and have
    metadata for subsetting.
    

    Examples
    --------
    Create a small experiment:
    
    .. plot::
        :context: close-figs
    
        >>> import cytoflow as flow
        >>> import_op = flow.ImportOp()
        >>> import_op.tubes = [flow.Tube(file = "tasbe/mkate.fcs")]
        >>> ex = import_op.apply()
    
    Create and parameterize the operation
    
    .. plot::
        :context: close-figs

        >>> color_op = flow.ColorTranslationOp()
        >>> color_op.controls = {("Pacific Blue-A", "FITC-A") : "tasbe/rby.fcs",
        ...                      ("PE-Tx-Red-YG-A", "FITC-A") : "tasbe/rby.fcs"}
        >>> color_op.mixture_model = True
    
    Estimate the model parameters
    
    .. plot::
        :context: close-figs 
    
        >>> color_op.estimate(ex)
    
    Plot the diagnostic plot
    
    .. plot::
        :context: close-figs

        >>> color_op.default_view().plot(ex)  

    Apply the operation to the experiment
    
    .. plot::
        :context: close-figs
    
        >>> ex = color_op.apply(ex)  
    """
    
    # traits
    id = Constant('edu.mit.synbio.cytoflow.operations.color_translation')
    friendly_id = Constant("Color translation")
    
    name = Constant("Color Translation")

    translation = util.Removed(err_string = "'translation' is removed; the same info is found in 'controls'", warning = True)
    controls = Dict(Tuple(Str, Str), File)
    mixture_model = Bool(False)
    linear_model = Bool(False)
    
    control_conditions = Dict(Tuple(Str, Str), Dict(Str, Any), {})

    # The regression coefficients determined by `estimate()`, used to map 
    # colors between channels.  The keys are tuples of (*from-channel*,
    # *to-channel) (corresponding to key-value pairs in `translation`).  The
    # values are lists of Float, the log-log coefficients for the color 
    # translation (determined by `estimate()`). 
    # TODO - why can't i make the value List(Float)?
    _coefficients = Dict(Tuple(Str, Str), Any, transient = True)
    _trans_fn = Dict(Tuple(Str, Str), Callable, transient = True)
    _sample = Dict(Tuple(Str, Str), Any, transient = True)
    _means = Dict(Tuple(Str, Str), Tuple(Float, Float), transient = True)

    def estimate(self, experiment, subset = None): 
        """
        Estimate the mapping from the two-channel controls
        
        Parameters
        ----------
        experiment : Experiment
            The :class:`.Experiment` used to check the voltages, etc. of the
            control tubes.  Also the source of the operation history that
            is replayed on the control tubes.
            
        subset : Str
            A Python expression used to subset the controls before estimating
            the color translation parameters.
        """

        if experiment is None:
            raise util.CytoflowOpError('experiment',
                                       "No experiment specified")
        
        if not self.controls:
            raise util.CytoflowOpError('controls',
                                       "No controls specified")
            
        self._coefficients.clear()
        self._trans_fn.clear()
        self._sample.clear()
        self._means.clear()

        tubes = {}
        
        translation = {x[0] : x[1] for x in list(self.controls.keys())}
        
        for from_channel, to_channel in translation.items():
            
            if from_channel not in experiment.channels:
                raise util.CytoflowOpError('translatin',
                                           "Channel {0} not in the experiment"
                                           .format(from_channel))
                
            if to_channel not in experiment.channels:
                raise util.CytoflowOpError('translation',
                                           "Channel {0} not in the experiment"
                                           .format(to_channel))
            
            if (from_channel, to_channel) not in self.controls:
                raise util.CytoflowOpError('translation',
                                           "Control file for {0} --> {1} "
                                           "not specified"
                                           .format(from_channel, to_channel))
                
            tube_file = self.controls[(from_channel, to_channel)]
            tube_conditions = self.control_conditions[(from_channel, to_channel)] \
                                    if (from_channel, to_channel) in self.control_conditions \
                                    else {}
            conditions = {k: experiment.data[k].dtype.name for k in tube_conditions.keys()}
            
            if tube_file not in tubes: 
                # make a little Experiment
                check_tube(tube_file, experiment)

                tube_exp = ImportOp(tubes = [Tube(file = tube_file,
                                                  conditions = tube_conditions)],
                                    conditions = conditions,
                                    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:
                    if hasattr(op, 'by'):
                        for by in op.by:
                            if 'experiment' in experiment.metadata[by]:
                                raise util.CytoflowOpError('experiment',
                                                           "Prior to applying this operation, "
                                                           "you must not apply any operation with 'by' "
                                                           "set to an experimental condition.")
                    tube_exp = op.apply(tube_exp) 

                # subset the events
                if subset:
                    try:
                        tube_exp = tube_exp.query(subset)
                    except Exception as e:
                        raise util.CytoflowOpError('subset',
                                                   "Subset string '{0}' isn't valid"
                                              .format(subset)) from e
                                    
                    if len(tube_exp.data) == 0:
                        raise util.CytoflowOpError('subset',
                                                   "Subset string '{0}' returned no events"
                                              .format(subset))
                
                tube_data = tube_exp.data    

                tubes[tube_file] = tube_data

                
            data = tubes[tube_file][[from_channel, to_channel]].copy()
            data = data[data[from_channel] > 0]
            data = data[data[to_channel] > 0]
            
            _ = data.reset_index(drop = True, inplace = True)
            
            self._sample[(from_channel, to_channel)] = data.sample(n = min(len(data), 5000))
            
            data[from_channel] = np.log10(data[from_channel])
            data[to_channel] = np.log10(data[to_channel])
            
            if self.mixture_model:    
                gmm = sklearn.mixture.BayesianGaussianMixture(n_components=2,
                                                              random_state = 1)
                fit = gmm.fit(data)
                
                self._means[(from_channel), (to_channel)] = \
                    (10 ** fit.means_[0][0], 10 ** fit.means_[1][0])

                # pick the component with the maximum mean
                idx = 0 if fit.means_[0][0] > fit.means_[1][0] else 1
                weights = [x[idx] for x in fit.predict_proba(data)]
            else:
                weights = [1] * len(data.index)
                
            if self.linear_model:
                # this mimics the TASBE approach, which constrains the fit to
                # a multiplicative scaling (eg, a linear fit with an intercept
                # of 0.)  I disagree that this is the right approach, which is
                # why it's not the default.
                 
                f = lambda x: weights * (data[to_channel] - x[0] * data[from_channel])
                x0 = [1]
                
                trans_fn = lambda data, x: np.power(data, x[0])
                 
            else:

                # this code uses a different approach from TASBE. instead of
                # computing a multiplicative scaling constant, it computes a
                # full linear regression on the log-scaled data (ie, allowing
                # the intercept to vary as well as the slope).  this is a 
                # more general model of the underlying physical behavior, and
                # fits the data better -- but it may not be more "correct."
                 
                f = lambda x: weights * (data[to_channel] - x[0] * data[from_channel] - x[1])
                x0 = [1, 0]
                
                trans_fn = lambda data, x: (10 ** x[1]) * np.power(data, x[0])
                 
                 
            opt = scipy.optimize.least_squares(f, x0)
            self._coefficients[(from_channel, to_channel)] = opt.x
            self._trans_fn[(from_channel, to_channel)] = lambda data, x = opt.x: trans_fn(data, x)


    def apply(self, experiment):
        """Applies the color translation to an experiment
        
        Parameters
        ----------
        experiment : Experiment
            the old_experiment to which this op is applied
            
        Returns
        -------
        Experiment 
            a new experiment with the color translation applied.  The corrected
            channels also have the following new metadata:
    
            **channel_translation** : Str
            Which channel was this one translated to?
        
            **channel_translation_fn** : Callable (pandas.Series --> pandas.Series)
            The function that translated this channel
        """

        if experiment is None:
            raise util.CytoflowOpError('experiment', "No experiment specified")
        
        if not self.controls:
            raise util.CytoflowOpError('controls', "No controls specified")
        
        if not self._trans_fn:
            raise util.CytoflowOpError(None, "Transfer functions aren't set. "
                                       "Did you forget to call estimate()?")
            
        translation = {x[0] : x[1] for x in list(self.controls.keys())}
        from_channels = [x[0] for x in list(self.controls.keys())]

        for key, val in translation.items():
            if (key, val) not in self._coefficients:
                raise util.CytoflowOpError(None,
                                           "Coefficients aren't set for translation "
                                           "{} --> {}.  Did you call estimate()?"
                                           .format(key, val))
                       
        new_experiment = experiment.clone()
        
        for channel in from_channels:
            new_experiment.data = \
                new_experiment.data[new_experiment.data[channel] > 0]
        
        for from_channel, to_channel in translation.items():
            trans_fn = self._trans_fn[(from_channel, to_channel)]
                        
            new_experiment[from_channel] = trans_fn(experiment[from_channel])
            new_experiment.metadata[from_channel]['channel_translation_fn'] = trans_fn
            new_experiment.metadata[from_channel]['channel_translation'] = to_channel
            
        new_experiment.history.append(self.clone_traits(transient = lambda _: True))
            
        return new_experiment
    
    def default_view(self, **kwargs):
        """
        Returns a diagnostic plot to see if the bleedthrough spline estimation
        is working.
        
        Returns
        -------
        IView
            A diagnostic view, call :meth:`ColorTranslationDiagnostic.plot` to 
            see the diagnostic plots
        """

        v = ColorTranslationDiagnostic(op = self)
        v.trait_set(**kwargs)
        return v
Exemple #4
0
class Item(ViewSubElement):
    """ An element in a Traits-based user interface.

    Magic:

    - Items are rendered as layout elements if :attr:`name` is set to
      special values:

      * ``name=''``, the item is rendered as a static label

      * ``name='_'``, the item is rendered as a separator

      * ``name=' '``, the item is rendered as a 5 pixel spacer

      * ``name='23'`` (any number), the item is rendered as a spacer of
        the size specified (number of pixels)
    """

    # FIXME: all the logic for the name = '', '_', ' ', '23' magic is in
    # _GroupPanel._add_items in qt/ui_panel.py, which is a very unlikely place
    # to look for it. Ideally, that logic should be in this class.

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

    # A unique identifier for the item. If not set, it defaults to the value
    # of **name**.
    id = Str

    # User interface label for the item in the GUI. If this attribute is not
    # set, the label is the value of **name** with slight modifications:
    # underscores are replaced by spaces, and the first letter is capitalized.
    # If an item's **name** is not specified, its label is displayed as
    # static text, without any editor widget.
    label = Str

    # Name of the trait the item is editing:
    name = Str

    # Style-sheet to apply to item / group (Qt only)
    style_sheet = Str

    # Help text describing the purpose of the item. The built-in help handler
    # displays this text in a pop-up window if the user clicks the widget's
    # label. View-level help displays the help text for all items in a view.
    # If this attribute is not set, the built-in help handler generates a
    # description based on the trait definition.
    help = Str

    # The HasTraits object whose trait attribute the item is editing:
    object = ContainerDelegate

    # Presentation style for the item:
    style = ContainerDelegate

    # Docking style for the item:
    dock = ContainerDelegate

    # Image to display on notebook tabs:
    image = ContainerDelegate

    # Category of elements dragged from view:
    export = ContainerDelegate

    # Should a label be displayed for the item?
    show_label = Delegate('container', 'show_labels')

    # Editor to use for the item:
    editor = ItemEditor

    # Additional editor traits to be set if default traits editor to be used:
    editor_args = Dict

    # Should the item use extra space along its Group's non-layout axis? If set to
    # True, the widget expands to fill any extra space that is available in the
    # display. If set to True for more than one item in the same View, any extra
    # space is divided between them. If set to False, the widget uses only
    # whatever space it is explicitly (or implicitly) assigned. The default
    # value of Undefined means that the use (or non-use) of extra space will be
    # determined by the editor associated with the item.
    resizable = Bool(Undefined)

    # Should the item use extra space along its Group's layout axis? For
    # example, it a vertical group, should an item expand vertically to use
    # any extra space available in the group?
    springy = Bool(False)

    # Should the item use any extra space along its Group's non-layout
    # orientation? For example, in a vertical group, should an item expand
    # horizontally to the full width of the group? If left to the default value
    # of Undefined, the decision will be left up to the associated item editor.
    full_size = Bool(Undefined)

    # Should the item's label use emphasized text? If the label is not shown,
    # this attribute is ignored.
    emphasized = Bool(False)

    # Should the item receive focus initially?
    has_focus = Bool(False)

    # Pre-condition for including the item in the display. If the expression
    # evaluates to False, the item is not defined in the display. Conditions
    # for **defined_when** are evaluated only once, when the display is first
    # constructed. Use this attribute for conditions based on attributes that
    # vary from object to object, but that do not change over time. For example,
    # displaying a 'maiden_name' item only for female employees in a company
    # database.
    defined_when = Str

    # Pre-condition for showing the item. If the expression evaluates to False,
    # the widget is not visible (and disappears if it was previously visible).
    # If the value evaluates to True, the widget becomes visible. All
    # **visible_when** conditions are checked each time that any trait value
    # is edited in the display. Therefore, you can use **visible_when**
    # conditions to hide or show widgets in response to user input.
    visible_when = Str

    # Pre-condition for enabling the item. If the expression evaluates to False,
    # the widget is disabled, that is, it does not accept input. All
    # **enabled_when** conditions are checked each time that any trait value
    # is edited in the display. Therefore, you can use **enabled_when**
    # conditions to enable or disable widgets in response to user input.
    enabled_when = Str

    # Amount of extra space, in pixels, to add around the item. Values must be
    # integers between -15 and 15. Use negative values to subtract from the
    # default spacing.
    padding = Padding

    # Tooltip to display over the item, when the mouse pointer is left idle
    # over the widget. Make this text as concise as possible; use the **help**
    # attribute to provide more detailed information.
    tooltip = Str

    # A Callable to use for formatting the contents of the item. This function
    # or method is called to create the string representation of the trait value
    # to be edited. If the widget does not use a string representation, this
    # attribute is ignored.
    format_func = Callable

    # Python format string to use for formatting the contents of the item.
    # The format string is applied to the string representation of the trait
    # value before it is displayed in the widget. This attribute is ignored if
    # the widget does not use a string representation, or if the
    # **format_func** is set.
    format_str = Str

    # Requested width of the editor (in pixels or fraction of available width).
    # For pixel values (i.e. values not in the range from 0.0 to 1.0), the
    # actual displayed width is at least the maximum of **width** and the
    # optimal width of the widget as calculated by the GUI toolkit. Specify a
    # negative value to ignore the toolkit's optimal width. For example, use
    # -50 to force a width of 50 pixels. The default value of -1 ensures that
    # the toolkit's optimal width is used.
    #
    # A value in the range from 0.0 to 1.0 specifies the fraction of the
    # available width to assign to the editor. Note that the value is not an
    # absolute value, but is relative to other item's whose **width** is also
    # in the 0.0 to 1.0 range. For example, if you have two item's with a width
    # of 0.1, and one item with a width of 0.2, the first two items will each
    # receive 25% of the available width, while the third item will receive
    # 50% of the available width. The available width is the total width of the
    # view minus the width of any item's with fixed pixel sizes (i.e. width
    # values not in the 0.0 to 1.0 range).
    width = Float(-1.0)

    # Requested height of the editor (in pixels or fraction of available
    # height). For pixel values (i.e. values not in the range from 0.0 to 1.0),
    # the actual displayed height is at least the maximum of **height** and the
    # optimal height of the widget as calculated by the GUI toolkit. Specify a
    # negative value to ignore the toolkit's optimal height. For example, use
    # -50 to force a height of 50 pixels. The default value of -1 ensures that
    # the toolkit's optimal height is used.
    #
    # A value in the range from 0.0 to 1.0 specifies the fraction of the
    # available height to assign to the editor. Note that the value is not an
    # absolute value, but is relative to other item's whose **height** is also
    # in the 0.0 to 1.0 range. For example, if you have two item's with a height
    # of 0.1, and one item with a height of 0.2, the first two items will each
    # receive 25% of the available height, while the third item will receive
    # 50% of the available height. The available height is the total height of
    # the view minus the height of any item's with fixed pixel sizes (i.e.
    # height values not in the 0.0 to 1.0 range).
    height = Float(-1.0)

    # The extended trait name of the trait containing the item's invalid state
    # status (passed through to the item's editor):
    invalid = Str

    #-------------------------------------------------------------------------
    #  Initialize the object:
    #-------------------------------------------------------------------------

    def __init__(self, value=None, **traits):
        """ Initializes the item object.
        """
        super(Item, self).__init__(**traits)

        if value is None:
            return

        if not isinstance(value, six.string_types):
            raise TypeError(
                "The argument to Item must be a string of the "
                "form: [id:][object.[object.]*][name]['['label']']`tooltip`"
                "[<width[,height]>][#^][$|@|*|~|;style]")

        value, empty = self._parse_label(value)
        if empty:
            self.show_label = False

        value = self._parse_style(value)
        value = self._parse_size(value)
        value = self._parse_tooltip(value)
        value = self._option(value, '#', 'resizable', True)
        value = self._option(value, '^', 'emphasized', True)
        value = self._split('id', value, ':', str_find, 0, 1)
        value = self._split('object', value, '.', str_rfind, 0, 1)

        if value != '':
            self.name = value

    #-------------------------------------------------------------------------
    #  Returns whether or not the object is replacable by an Include object:
    #-------------------------------------------------------------------------

    def is_includable(self):
        """ Returns a Boolean indicating whether the object is replaceable by an
            Include object.
        """
        return (self.id != '')

    #-------------------------------------------------------------------------
    #  Returns whether or not the Item represents a spacer or separator:
    #-------------------------------------------------------------------------

    def is_spacer(self):
        """ Returns True if the item represents a spacer or separator.
        """
        name = self.name.strip()

        return ((name == '') or (name == '_')
                or (all_digits.match(name) is not None))

    #-------------------------------------------------------------------------
    #  Gets the help text associated with the Item in a specified UI:
    #-------------------------------------------------------------------------

    def get_help(self, ui):
        """ Gets the help text associated with the Item in a specified UI.
        """
        # Return 'None' if the Item is a separator or spacer:
        if self.is_spacer():
            return None

        # Otherwise, it must be a trait Item:
        if self.help != '':
            return self.help

        object = eval(self.object_, globals(), ui.context)

        return object.base_trait(self.name).get_help()

    #-------------------------------------------------------------------------
    #  Gets the label to use for a specified Item in a specified UI:
    #-------------------------------------------------------------------------

    def get_label(self, ui):
        """ Gets the label to use for a specified Item.

        If not specified, the label is set as the name of the
        corresponding trait, replacing '_' with ' ', and capitalizing
        the first letter (see :func:`user_name_for`). This is called
        the *user name*.

        Magic:

        - if attr:`item.label` is specified, and it begins with '...',
          the final label is the user name followed by the item label
        - if attr:`item.label` is specified, and it ends with '...',
          the final label is the item label followed by the user name
        """
        # Return 'None' if the Item is a separator or spacer:
        if self.is_spacer():
            return None

        label = self.label
        if label != '':
            return label

        name = self.name
        object = eval(self.object_, globals(), ui.context)
        trait = object.base_trait(name)
        label = user_name_for(name)
        tlabel = trait.label
        if tlabel is None:
            return label

        if isinstance(tlabel, six.string_types):
            if tlabel[0:3] == '...':
                return label + tlabel[3:]
            if tlabel[-3:] == '...':
                return tlabel[:-3] + label
            if self.label != '':
                return self.label
            return tlabel

        return tlabel(object, name, label)

    #-------------------------------------------------------------------------
    #  Returns an id used to identify the item:
    #-------------------------------------------------------------------------

    def get_id(self):
        """ Returns an ID used to identify the item.
        """
        if self.id != '':
            return self.id

        return self.name

    #-------------------------------------------------------------------------
    #  Parses a '<width,height>' value from the string definition:
    #-------------------------------------------------------------------------

    def _parse_size(self, value):
        """ Parses a '<width,height>' value from the string definition.
        """
        match = size_pat.match(value)
        if match is not None:
            data = match.group(2)
            value = match.group(1) + match.group(3)
            col = data.find(',')
            if col < 0:
                self._set_float('width', data)
            else:
                self._set_float('width', data[:col])
                self._set_float('height', data[col + 1:])

        return value

    #-------------------------------------------------------------------------
    #  Parses a '`tooltip`' value from the string definition:
    #-------------------------------------------------------------------------

    def _parse_tooltip(self, value):
        """ Parses a *tooltip* value from the string definition.
        """
        match = tooltip_pat.match(value)
        if match is not None:
            self.tooltip = match.group(2)
            value = match.group(1) + match.group(3)

        return value

    #-------------------------------------------------------------------------
    #  Sets a specified trait to a specified string converted to a float:
    #-------------------------------------------------------------------------

    def _set_float(self, name, value):
        """ Sets a specified trait to a specified string converted to a float.
        """
        value = value.strip()
        if value != '':
            setattr(self, name, float(value))

    #-------------------------------------------------------------------------
    #  Returns a 'pretty print' version of the Item:
    #-------------------------------------------------------------------------

    def __repr__(self):
        """ Returns a "pretty print" version of the Item.
        """

        options = self._repr_options('id', 'object', 'label', 'style',
                                     'show_label', 'width', 'height')
        if options is None:
            return "Item( '%s' )" % self.name

        return "Item( '%s'\n%s\n)" % (self.name, self._indent(
            options, '      '))
Exemple #5
0
class Base1DMapper(AbstractMapper):
    """ Defines an abstract mapping from a 1-D region in input space to a 1-D
    region in output space.
    """

    # The data-space bounds of the mapper.
    range = Instance(DataRange1D)

    # The screen space position of the lower bound of the data space.
    low_pos = Float(0.0)

    # The screen space position of the upper bound of the data space.
    high_pos = Float(1.0)

    # Convenience property to get low and high positions in one structure.
    # Must be a tuple (low_pos, high_pos).
    screen_bounds = Property

    # Should the mapper stretch the dataspace when its screen space bounds are
    # modified (default), or should it preserve the screen-to-data ratio and
    # resize the data bounds?  If the latter, it will only try to preserve
    # the ratio if both screen and data space extents are non-zero.
    stretch_data = Bool(True)

    # If the subclass uses a cache, _cache_valid is maintained to
    # monitor its status
    _cache_valid = Bool(False)

    # Indicates whether or not the bounds have been set at all, or if they
    # are at their initial default values.
    _bounds_initialized = Bool(False)

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

    def _low_pos_changed(self):
        self._cache_valid = False
        self.updated = True
        return

    def _high_pos_changed(self):
        self._cache_valid = False
        self.updated = True
        return

    def _range_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._range_change_handler,
                                "updated",
                                remove=True)
        if new is not None:
            new.on_trait_change(self._range_change_handler, "updated")

        self._cache_valid = False
        self.updated = new
        return

    def _range_change_handler(self, obj, name, new):
        "Handles the range changing; dynamically attached to our ranges"
        self._cache_valid = False
        self.updated = obj
        return

    def _get_screen_bounds(self):
        return (self.low_pos, self.high_pos)

    def _set_screen_bounds(self, new_bounds):
        if new_bounds[0] == self.low_pos and new_bounds[1] == self.high_pos:
            return
        if not self.stretch_data and self.range is not None and self._bounds_initialized:
            rangelow = self.range.low
            rangehigh = self.range.high
            d_data = rangehigh - rangelow
            d_screen = self.high_pos - self.low_pos
            if d_data != 0 and d_screen != 0:
                new_data_extent = d_data / d_screen * abs(new_bounds[1] -
                                                          new_bounds[0])
                self.range.set_bounds(rangelow, rangelow + new_data_extent)
        self.set(low_pos=new_bounds[0], trait_change_notify=False)
        self.set(high_pos=new_bounds[1], trait_change_notify=False)
        self._cache_valid = False
        self._bounds_initialized = True
        self.updated = True
        return
Exemple #6
0
class InstanceEditor(EditorFactory):
    """Editor factory for instance editors."""

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

    #: List of items describing the types of selectable or editable instances
    values = List(InstanceChoiceItem)

    #: Extended name of the context object trait containing the list of types
    #: of selectable or editable instances
    name = Str()

    #: Is the current value of the object trait editable (vs. merely
    #: selectable)?
    editable = Bool(True)

    #: Should the object trait value be selectable from a list of objects (a
    #: value of True forces a selection list to be displayed, while a value of
    #: False displays a selection list only if at least one object in the list
    #: of possible object values is selectable):
    selectable = Bool(False)

    #: Should the editor support drag and drop of objects to set the trait
    #: value (a value of True forces the editor to allow drag and drop, while
    #: a value of False only supports drag and drop if at least one item in the
    #: list of possible objects supports drag and drop):
    droppable = Bool(False)

    #: Should factory-created objects be cached?
    cachable = Bool(True)

    #: Optional label for button
    label = Str()

    #: Optional instance view to use
    view = AView

    #: Extended name of the context object trait containing the view, or name
    #: of the view, to use
    view_name = Str()

    #: The ID to use with the view
    id = Str()

    #: Kind of pop-up editor (live, modal, nonmodal, wizard)
    kind = AKind

    #: The orientation of the instance editor relative to the instance selector
    orientation = Enum("default", "horizontal", "vertical")

    #: The default adapter class used to create InstanceChoice compatible
    #: adapters for instance objects:
    adapter = Type(InstanceChoice, allow_none=False)

    # -------------------------------------------------------------------------
    #  Traits view definitions:
    # -------------------------------------------------------------------------

    traits_view = View(
        [
            ["label{Button label}", "view{View name}", "|[]"],
            ["kind@", "|[Pop-up editor style]<>"],
        ]
    )
Exemple #7
0
class UCustom(Custom):
    """ An Item using a 'custom' style with no label.
    """

    show_label = Bool(False)
Exemple #8
0
class ExperimentFactory(DVCAble): #, ConsumerMixin):
    run_factory = Instance(AutomatedRunFactory)
    queue_factory = Instance(ExperimentQueueFactory)

    undoer = Instance(ExperimentUndoer)

    generate_queue_button = Button
    edit_queue_config_button = Button
    auto_gen_config = Instance(AutoGenConfig)

    add_button = Button('Add')
    clear_button = Button('Clear')
    save_button = Button('Save')
    edit_mode_button = Button('Edit')
    edit_enabled = DelegatesTo('run_factory')

    auto_increment_id = Bool(False)
    auto_increment_position = Bool(False)

    queue = Instance(ExperimentQueue, ())

    ok_add = Property(depends_on='mass_spectrometer, extract_device, labnumber, username, load_name')

    labnumber = DelegatesTo('run_factory')
    load_name = DelegatesTo('queue_factory')
    username = DelegatesTo('queue_factory')
    mass_spectrometer = DelegatesTo('queue_factory')
    extract_device = DelegatesTo('queue_factory')

    selected_positions = List
    default_mass_spectrometer = Str

    _load_persistence_flag = False
    # ===========================================================================
    # permisions
    # ===========================================================================
    # max_allowable_runs = Int(10000)
    # can_edit_scripts = Bool(True)

    def __init__(self, *args, **kw):
        super(ExperimentFactory, self).__init__(auto_setup=False, *args, **kw)
        # self.setup_consumer(self._add_run, main=True)
        pass

    def undo(self):
        self.info('undo')
        self.undoer.undo()

    def sync_queue_meta(self):
        self.debug('syncing queue meta')
        eq = self.queue
        qf = self.queue_factory
        for a in ('username', 'mass_spectrometer', 'extract_device',
                  'email', 'use_email',
                  'use_group_email',
                  'load_name', 'tray',
                  'delay_after_blank',
                  'delay_between_analyses',
                  'delay_after_air',
                  'default_lighting',
                  'queue_conditionals_name', 'note'):

            if not self._sync_queue_to_factory(eq, qf, a):
                self._sync_factory_to_queue(eq, qf, a)

        self.debug('run factory set mass spec {}'.format(self.mass_spectrometer))
        self.run_factory.set_mass_spectrometer(self.mass_spectrometer)

    def _sync_queue_to_factory(self, eq, qf, a):
        v = getattr(eq, a)
        if isinstance(v, str):
            v = v.strip()

        if v:
            self.debug('sync queue to factory {}>>{}'.format(a, v))
            setattr(qf, a, v)
            return True

    def _sync_factory_to_queue(self, eq, qf, a):
        v = getattr(qf, a)
        if isinstance(v, str):
            v = v.strip()
            if v:
                self.debug('sync factory to queue {}>>{}'.format(a, v))
                setattr(eq, a, v)

    def activate(self, load_persistence=True):
        # self.start_consuming()
        self._load_persistence_flag = load_persistence
        self.queue_factory.activate(load_persistence)
        self.run_factory.activate(load_persistence)

    def destroy(self):
        # self.stop_consuming()
        self.run_factory.deactivate()
        self.queue_factory.deactivate()

    def set_selected_runs(self, runs):
        self.run_factory.set_selected_runs(runs)

    '''
    uflag = bool(self.username)
        msflag = self.mass_spectrometer not in ('', 'Spectrometer', LINE_STR)
        lflag = True
        if self.extract_device not in ('', 'Extract Device', LINE_STR):
            lflag = bool(self.queue_factory.load_name)

        ret = uflag and msflag and lflag
        if self.run_factory.run_block in ('RunBlock', LINE_STR):
            ret = ret and self.labnumber
        return ret
    '''

    def _add_run(self, *args, **kw):

        if not self.ok_add:
            missing = []
            if not bool(self.username):
                missing.append('"Username"')
            if self.mass_spectrometer in ('', 'Spectrometer', LINE_STR):
                missing.append('"Spectrometer"')
            if self.extract_device not in ('', 'Extact Device', LINE_STR):
                if not bool(self.queue_factory.load_name):
                    missing.append('"Load"')
            if self.run_factory.run_block in ('RunBlock', LINE_STR):
                if not self.labnumber:
                    missing.append('"Labnumber"')

            f = 'a value'
            if len(missing) > 1:
                f = 'values'
            self.warning_dialog('Please set {} for {}'.format(f, ','.join(missing)))
            return

        positions = [str(pi.positions[0]) for pi in self.selected_positions]
        self.debug('add run positions= {}'.format(positions))

        # load_name = self.queue_factory.load_name

        q = self.queue
        rf = self.run_factory
        new_runs, freq = rf.new_runs(q, positions=positions,
                                     auto_increment_position=self.auto_increment_position,
                                     auto_increment_id=self.auto_increment_id)

        if new_runs:
            aruns = q.automated_runs
            if q.selected and q.selected[-1] in aruns:
                idx = aruns.index(q.selected[-1])
            else:
                idx = len(aruns) - 1

            runs = q.add_runs(new_runs, freq,
                              # freq_before=rf.freq_before,
                              # freq_after=rf.freq_after,
                              is_run_block=rf.run_block_enabled)

            self.undoer.push('add runs', runs)

            idx += len(runs)

            with rf.update_selected_ctx():
                q.select_run_idx(idx)

            q.changed = True

    # ===============================================================================
    # handlers
    # ===============================================================================
    def _clear_button_fired(self):
        self.queue.clear_frequency_runs()

    def _add_button_fired(self):
        """
            only allow add button to be fired every 0.5s

            use consumermixin.add_consumable instead of frequency limiting
        """
        self.debug('add run fired')
        # self.add_consumable(5)
        do_later(self._add_run)

    def _edit_mode_button_fired(self):
        self.run_factory.edit_mode = not self.run_factory.edit_mode

        # @on_trait_change('run_factory:clear_end_after')
        # def _clear_end_after(self, new):
        # print 'enadfas', new

    def _update_end_after(self, new):
        if new:
            for ai in self.queue.automated_runs:
                ai.end_after = False

        self.run_factory.set_end_after(new)

    def _queue_changed(self, new):
        self.undoer.queue = new

#     @on_trait_change('''queue_factory:[mass_spectrometer,
# extract_device, delay_+, tray, username, load_name,
# email, use_email, use_group_email,
# queue_conditionals_name, repository_identifier]''')
    def _update_queue(self, name, new):
        self.debug('update queue {}={}'.format(name, new))
        if self.queue:
            self.queue.trait_set(**{name: new})
            self.queue.changed = True
            if name == 'repository_identifier':
                for a in self.queue.automated_runs:
                    a.repository_identifier = new

        if name == 'mass_spectrometer':
            self.debug('_update_queue "{}"'.format(new))
            self.mass_spectrometer = new
            self.run_factory.set_mass_spectrometer(new)

        elif name == 'extract_device':
            self._set_extract_device(new)
            # do_later(self._set_extract_device, new)

            # elif name == 'username':
            # self._username = new
            # elif name=='email':
            # self.email=new
            # self.queue.username = new

        self._auto_save()

    def _auto_save(self):
        self.queue.auto_save()

    def get_patterns(self):
        return self._get_patterns(self.extract_device)

    # ===============================================================================
    # private
    # ===============================================================================
    def _set_extract_device(self, ed):
        self.debug('setting extract dev="{}" mass spec="{}"'.format(ed, self.mass_spectrometer))
        self.run_factory = self._run_factory_factory()

        self.run_factory.remote_patterns = patterns = self._get_patterns(ed)
        self.run_factory.setup_files()

        # self.run_factory.set_mass_spectrometer(self.mass_spectrometer)

        if self._load_persistence_flag:
            self.run_factory.load()

        if self.queue:
            self.queue.set_extract_device(ed)
            self.queue.username = self.username
            self.queue.mass_spectrometer = self.mass_spectrometer
            self.queue.patterns = patterns

    def _get_patterns(self, ed):
        ps = []
        service_name = convert_extract_device(ed)
        # service_name = ed.replace(' ', '_').lower()
        man = self.application.get_service(ILaserManager, 'name=="{}"'.format(service_name))
        if man:
            ps = man.get_pattern_names()
        else:
            self.debug('No remote patterns. {} ({}) not available'.format(ed, service_name))

        return ps

    # ===============================================================================
    # property get/set
    # ===============================================================================
    def _get_ok_add(self):
        """
        """
        uflag = bool(self.username)
        msflag = self.mass_spectrometer not in ('', 'Spectrometer', LINE_STR)
        lflag = True
        if self.extract_device not in ('', 'Extract Device', LINE_STR):
            lflag = bool(self.queue_factory.load_name)

        ret = uflag and msflag and lflag
        if self.run_factory.run_block in ('RunBlock', LINE_STR):
            ret = ret and self.labnumber
        return ret

    # ===============================================================================
    #
    # ===============================================================================
    def _run_factory_factory(self):
        if self.extract_device == 'Fusions UV':
            klass = UVAutomatedRunFactory
        else:
            klass = AutomatedRunFactory

        rf = klass(dvc=self.dvc,
                   application=self.application,
                   extract_device=self.extract_device,
                   mass_spectrometer=self.mass_spectrometer)

        # rf.activate()
        # rf.on_trait_change(lambda x: self.trait_set(_labnumber=x), 'labnumber')
        rf.on_trait_change(self._update_end_after, 'end_after')
        rf.on_trait_change(self._auto_save, 'auto_save_needed')
        print('making new factory', id(rf))
        return rf

    # handlers
    # def _generate_runs_from_load(self, ):
    #     def gen():
    #         dvc = self.dvc
    #         load_name = self.load_name
    #         with dvc.session_ctx():
    #             dbload = dvc.get_loadtable(load_name)
    #             for poss in dbload.loaded_positions:
    #                 # print poss
    #                 ln_id = poss.lab_identifier
    #                 dbln = dvc.get_labnumber(ln_id, key='id')
    #
    #                 yield dbln.identifier, dbln.sample.name, str(poss.position)
    #
    #     return gen

    def _edit_queue_config_button_fired(self):
        self.auto_gen_config.run_blocks = self.run_factory.run_blocks
        self.auto_gen_config.load()
        info = self.auto_gen_config.edit_traits()
        if info.result:
            self.auto_gen_config.dump()

    # def _generate_queue_button_fired(self):
    #     pd = myProgressDialog()
    #     pd.open()
    #
    #     ans = list(self._generate_runs_from_load()())
    #     self._gen_func(pd, ans)
    #     # t=Thread(target=self._gen_func, args=(pd, ans))
    #     # t.start()

    def _gen_func(self, pd, ans):
        import time

        pd.max = 100
        self.debug('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ generate queue')
        auto_gen_config = self.auto_gen_config

        # gen = self._generate_runs_from_load()
        q = self.queue
        rf = self.run_factory
        rf.suppress_meta = True

        def add_special(ln):
            # tt = time.time()
            pd.change_message('Add special: {}'.format(ln))
            rf.special_labnumber = ln
            new_runs, _ = rf.new_runs(q)
            q.add_runs(new_runs, 0)
            # rf.special_labnumber = ''
            # print 'add special {}, {}'.format(ln, time.time()-tt)

        st = time.time()
        rb = auto_gen_config.end_run_block
        if rb and rb in rf.run_blocks:
            rf.run_block = auto_gen_config.end_run_block
            new_runs, _ = rf.new_runs(q)
            q.add_runs(new_runs, 0, is_run_block=False)

        for ln, tag in (('Air', 'air'),
                        ('Cocktail', 'cocktail'),
                        ('Blank Unknown', 'blank')):

            if getattr(auto_gen_config, 'start_{}'.format(tag)):
                add_special(ln)

        # for i, (labnumber, sample, position) in enumerate(gen()):
        for i, (labnumber, sample, position) in enumerate(ans):
            if i:
                for ln, tag in (('Blank Unknown', 'blank'),
                                ('Air', 'air'),
                                ('Cocktail', 'cocktail')):
                    f = getattr(auto_gen_config, '{}_freq'.format(tag))
                    if f and i % f == 0:
                        add_special(ln)

            pd.change_message('Adding {}. Position: {}'.format(labnumber, position))

            # tt = time.time()
            rf.labnumber = labnumber
            rf.sample = sample
            # print 'set ln/sample {} {}'.format(labnumber, time.time()-tt)

            new_runs, _ = rf.new_runs(q, positions=position)
            # print 'new runs {} {}'.format(labnumber, time.time()-tt)

            q.add_runs(new_runs, 0, is_run_block=False)
            # print 'add runs {} {}'.format(labnumber, time.time()-tt)

        for ln, tag in (('Blank Unknown', 'blank'),
                        ('Air', 'air'),
                        ('Cocktail', 'cocktail')):

            if getattr(auto_gen_config, 'end_{}'.format(tag)):
                add_special(ln)

        rb = auto_gen_config.end_run_block
        if rb and rb in rf.run_blocks:
            rf.run_block = auto_gen_config.end_run_block
            new_runs, _ = rf.new_runs(q)
            q.add_runs(new_runs, 0, is_run_block=False)

        # print 'finished adding', time.time()-st
        q.changed = True
        rf.update_info_needed = True
        rf.suppress_meta = False
        print('totaltime', time.time() - st)
        pd.close()
        rf.labnumber = ''
        rf.sample = ''

    def _dvc_changed(self):
        self.queue_factory.dvc = self.dvc
        self.run_factory.dvc = self.dvc

    def _application_changed(self):
        self.run_factory.application = self.application
        self.queue_factory.application = self.application

    def _default_mass_spectrometer_changed(self):
        self.debug('default mass spec changed "{}"'.format(self.default_mass_spectrometer))
        self.run_factory.set_mass_spectrometer(self.default_mass_spectrometer)
        self.queue_factory.mass_spectrometer = self.default_mass_spectrometer
        self.mass_spectrometer = self.default_mass_spectrometer

    # ===============================================================================
    # defaults
    # ===============================================================================
    def _auto_gen_config_default(self):
        ag = AutoGenConfig()
        ag.load()
        return ag

    def _undoer_default(self):
        return ExperimentUndoer(run_factory=self.run_factory,
                                queue=self.queue)

    def _run_factory_default(self):
        return self._run_factory_factory()

    def _queue_factory_default(self):
        eq = ExperimentQueueFactory(dvc=self.dvc,
                                    application=self.application)

        eq.on_trait_change(self._update_queue, '''mass_spectrometer,
extract_device, delay_+, tray, username, load_name, note,
email, use_email, use_group_email,
queue_conditionals_name, repository_identifier''')
        # eq.activate()
        return eq
Exemple #9
0
class Legend(AbstractOverlay):
    """ A legend for a plot.
    """
    # The font to use for the legend text.
    font = KivaFont("modern 12")

    # The amount of space between the content of the legend and the border.
    border_padding = Int(10)

    # The border is visible (overrides Enable Component).
    border_visible = True

    # The color of the text labels
    color = black_color_trait

    # The background color of the legend (overrides AbstractOverlay).
    bgcolor = white_color_trait

    # The position of the legend with respect to its overlaid component.  (This
    # attribute applies only if the legend is used as an overlay.)
    #
    # * ur = Upper Right
    # * ul = Upper Left
    # * ll = Lower Left
    # * lr = Lower Right
    align = Enum("ur", "ul", "ll", "lr")

    # The amount of space between legend items.
    line_spacing = Int(3)

    # The size of the icon or marker area drawn next to the label.
    icon_bounds = List([24, 24])

    # Amount of spacing between each label and its icon.
    icon_spacing = Int(5)

    # Map of labels (strings) to plot instances or lists of plot instances.  The
    # Legend determines the appropriate rendering of each plot's marker/line.
    plots = Dict

    # The list of labels to show and the order to show them in.  If this
    # list is blank, then the keys of self.plots is used and displayed in
    # alphabetical order.  Otherwise, only the items in the **labels**
    # list are drawn in the legend.  Labels are ordered from top to bottom.
    labels = List

    # Whether or not to hide plots that are not visible.  (This is checked during
    # layout.)  This option *will* filter out the items in **labels** above, so
    # if you absolutely, positively want to set the items that will always
    # display in the legend, regardless of anything else, then you should turn
    # this option off.  Otherwise, it usually makes sense that a plot renderer
    # that is not visible will also not be in the legend.
    hide_invisible_plots = Bool(True)

    # If hide_invisible_plots is False, we can still choose to render the names
    # of invisible plots with an alpha.
    invisible_plot_alpha = Float(0.33)

    # The renderer that draws the icons for the legend.
    composite_icon_renderer = Instance(AbstractCompositeIconRenderer)

    # Action that the legend takes when it encounters a plot whose icon it
    # cannot render:
    #
    # * 'skip': skip it altogether and don't render its name
    # * 'blank': render the name but leave the icon blank (color=self.bgcolor)
    # * 'questionmark': render a "question mark" icon
    error_icon = Enum("skip", "blank", "questionmark")

    # Should the legend clip to the bounds it needs, or to its parent?
    clip_to_component = Bool(False)

    # The legend is not resizable (overrides PlotComponent).
    resizable = "hv"

    # An optional title string to show on the legend.
    title = Str('')

    # If True, title is at top, if False then at bottom.
    title_at_top = Bool(True)

    # The legend draws itself as in one pass when its parent is drawing
    # the **draw_layer** (overrides PlotComponent).
    unified_draw = True
    # The legend is drawn on the overlay layer of its parent (overrides
    # PlotComponent).
    draw_layer = "overlay"

    #------------------------------------------------------------------------
    # Private Traits
    #------------------------------------------------------------------------

    # A cached list of Label instances
    _cached_labels = List

    # A cached array of label sizes.
    _cached_label_sizes = Any

    # A cached list of label names.
    _cached_label_names = CList

    # A list of the visible plots.  Each plot corresponds to the label at
    # the same index in _cached_label_names.  This list does not necessarily
    # correspond to self.plots.value() because it is sorted according to
    # the plot name and it potentially excludes invisible plots.
    _cached_visible_plots = CList

    # A cached array of label positions relative to the legend's origin
    _cached_label_positions = Any

    def is_in(self, x, y):
        """ overloads from parent class because legend alignment
            and padding does not cooperatate with the basic implementation

            This may just be caused byt a questionable implementation of the
            legend tool, but it works by adjusting the padding. The Component
            class implementation of is_in uses the outer positions which
            includes the padding
        """
        in_x = (x >= self.x) and (x <= self.x + self.width)
        in_y = (y >= self.y) and (y <= self.y + self.height)

        return in_x and in_y



    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        """ Draws this component overlaid on another component.

        Implements AbstractOverlay.
        """
        self.do_layout()
        valign, halign = self.align
        if valign == "u":
            y = component.y2 - self.outer_height
        else:
            y = component.y
        if halign == "r":
            x = component.x2 - self.outer_width
        else:
            x = component.x
        self.outer_position = [x, y]

        if self.clip_to_component:
            c = self.component
            with gc:
                gc.clip_to_rect(c.x, c.y, c.width, c.height)
                PlotComponent._draw(self, gc, view_bounds, mode)
        else:
            PlotComponent._draw(self, gc, view_bounds, mode)


        return

    # The following two methods implement the functionality of the Legend
    # to act as a first-class component instead of merely as an overlay.
    # The make the Legend use the normal PlotComponent render methods when
    # it does not have a .component attribute, so that it can have its own
    # overlays (e.g. a PlotLabel).
    #
    # The core legend rendering method is named _draw_as_overlay() so that
    # it can be called from _draw_plot() when the Legend is not an overlay,
    # and from _draw_overlay() when the Legend is an overlay.

    def _draw_plot(self, gc, view_bounds=None, mode="normal"):
        if self.component is None:
            self._draw_as_overlay(gc, view_bounds, mode)
        return

    def _draw_overlay(self, gc, view_bounds=None, mode="normal"):
        if self.component is not None:
            self._draw_as_overlay(gc, view_bounds, mode)
        else:
            PlotComponent._draw_overlay(self, gc, view_bounds, mode)
        return

    def _draw_as_overlay(self, gc, view_bounds=None, mode="normal"):
        """ Draws the overlay layer of a component.

        Overrides PlotComponent.
        """
        # Determine the position we are going to draw at from our alignment
        # corner and the corresponding outer_padding parameters.  (Position
        # refers to the lower-left corner of our border.)

        # First draw the border, if necesssary.  This sort of duplicates
        # the code in PlotComponent._draw_overlay, which is unfortunate;
        # on the other hand, overlays of overlays seem like a rather obscure
        # feature.

        with gc:
            gc.clip_to_rect(int(self.x), int(self.y),
                            int(self.width), int(self.height))
            edge_space = self.border_width + self.border_padding
            icon_width, icon_height = self.icon_bounds

            icon_x = self.x + edge_space
            text_x = icon_x + icon_width + self.icon_spacing
            y = self.y2 - edge_space

            if self._cached_label_positions is not None:
                if len(self._cached_label_positions) > 0:
                    self._cached_label_positions[:,0] = icon_x

            for i, label_name in enumerate(self._cached_label_names):
                # Compute the current label's position
                label_height = self._cached_label_sizes[i][1]
                y -= label_height
                self._cached_label_positions[i][1] = y

                # Try to render the icon
                icon_y = y + (label_height - icon_height) / 2
                #plots = self.plots[label_name]
                plots = self._cached_visible_plots[i]
                render_args = (gc, icon_x, icon_y, icon_width, icon_height)

                try:
                    if isinstance(plots, list) or isinstance(plots, tuple):
                        # TODO: How do we determine if a *group* of plots is
                        # visible or not?  For now, just look at the first one
                        # and assume that applies to all of them
                        if not plots[0].visible:
                            # TODO: the get_alpha() method isn't supported on the Mac kiva backend
                            #old_alpha = gc.get_alpha()
                            old_alpha = 1.0
                            gc.set_alpha(self.invisible_plot_alpha)
                        else:
                            old_alpha = None
                        if len(plots) == 1:
                            plots[0]._render_icon(*render_args)
                        else:
                            self.composite_icon_renderer.render_icon(plots, *render_args)
                    elif plots is not None:
                        # Single plot
                        if not plots.visible:
                            #old_alpha = gc.get_alpha()
                            old_alpha = 1.0
                            gc.set_alpha(self.invisible_plot_alpha)
                        else:
                            old_alpha = None
                        plots._render_icon(*render_args)
                    else:
                        old_alpha = None  # Or maybe 1.0?

                    icon_drawn = True
                except:
                    icon_drawn = self._render_error(*render_args)

                if icon_drawn:
                    # Render the text
                    gc.translate_ctm(text_x, y)
                    gc.set_antialias(0)
                    self._cached_labels[i].draw(gc)
                    gc.set_antialias(1)
                    gc.translate_ctm(-text_x, -y)

                    # Advance y to the next label's baseline
                    y -= self.line_spacing
                if old_alpha is not None:
                    gc.set_alpha(old_alpha)

        return

    def _render_error(self, gc, icon_x, icon_y, icon_width, icon_height):
        """ Renders an error icon or performs some other action when a
        plot is unable to render its icon.

        Returns True if something was actually drawn (and hence the legend
        needs to advance the line) or False if nothing was drawn.
        """
        if self.error_icon == "skip":
            return False
        elif self.error_icon == "blank" or self.error_icon == "questionmark":
            with gc:
                gc.set_fill_color(self.bgcolor_)
                gc.rect(icon_x, icon_y, icon_width, icon_height)
                gc.fill_path()
            return True
        else:
            return False

    def get_preferred_size(self):
        """
        Computes the size and position of the legend based on the maximum size of
        the labels, the alignment, and position of the component to overlay.
        """
        # Gather the names of all the labels we will create
        if len(self.plots) == 0:
            return [0, 0]

        plot_names, visible_plots = map(list, zip(*sorted(self.plots.items())))
        label_names = self.labels
        if len(label_names) == 0:
            if len(self.plots) > 0:
                label_names = plot_names
            else:
                self._cached_labels = []
                self._cached_label_sizes = []
                self._cached_label_names = []
                self._cached_visible_plots = []
                self.outer_bounds = [0, 0]
                return [0, 0]

        if self.hide_invisible_plots:
            visible_labels = []
            visible_plots = []
            for name in label_names:
                # If the user set self.labels, there might be a bad value,
                # so ensure that each name is actually in the plots dict.
                if name in self.plots:
                    val = self.plots[name]
                    # Rather than checking for a list/TraitListObject/etc., we just check
                    # for the attribute first
                    if hasattr(val, 'visible'):
                        if val.visible:
                            visible_labels.append(name)
                            visible_plots.append(val)
                    else:
                        # If we have a list of renderers, add the name if any of them are
                        # visible
                        for renderer in val:
                            if renderer.visible:
                                visible_labels.append(name)
                                visible_plots.append(val)
                                break
            label_names = visible_labels

        # Create the labels
        labels = [self._create_label(text) for text in label_names]

        # For the legend title
        if self.title_at_top:
            labels.insert(0, self._create_label(self.title))
            label_names.insert(0, 'Legend Label')
            visible_plots.insert(0, None)
        else:
            labels.append(self._create_label(self.title))
            label_names.append(self.title)
            visible_plots.append(None)

        # We need a dummy GC in order to get font metrics
        dummy_gc = font_metrics_provider()
        label_sizes = array([label.get_width_height(dummy_gc) for label in labels])

        if len(label_sizes) > 0:
            max_label_width = max(label_sizes[:, 0])
            total_label_height = sum(label_sizes[:, 1]) + (len(label_sizes)-1)*self.line_spacing
        else:
            max_label_width = 0
            total_label_height = 0

        legend_width = max_label_width + self.icon_spacing + self.icon_bounds[0] \
                        + self.hpadding + 2*self.border_padding
        legend_height = total_label_height + self.vpadding + 2*self.border_padding

        self._cached_labels = labels
        self._cached_label_sizes = label_sizes
        self._cached_label_positions = zeros_like(label_sizes)
        self._cached_label_names = label_names
        self._cached_visible_plots = visible_plots

        if "h" not in self.resizable:
            legend_width = self.outer_width
        if "v" not in self.resizable:
            legend_height = self.outer_height
        return [legend_width, legend_height]

    def get_label_at(self, x, y):
        """ Returns the label object at (x,y) """
        for i, pos in enumerate(self._cached_label_positions):
            size = self._cached_label_sizes[i]
            corner = pos + size
            if (pos[0] <= x <= corner[0]) and (pos[1] <= y <= corner[1]):
                return self._cached_labels[i]
        else:
            return None

    def _do_layout(self):
        if self.component is not None or len(self._cached_labels) == 0 or \
                self._cached_label_sizes is None or len(self._cached_label_names) == 0:
            width, height = self.get_preferred_size()
            self.outer_bounds = [width, height]
        return

    def _create_label(self, text):
        """ Returns a new Label instance for the given text.  Subclasses can
        override this method to customize the creation of labels.
        """
        return Label(text=text, font=self.font, margin=0, color=self.color_,
                     bgcolor="transparent", border_width=0)

    def _composite_icon_renderer_default(self):
        return CompositeIconRenderer()

    #-- trait handlers --------------------------------------------------------
    def _anytrait_changed(self, name, old, new):
        if name in ("font", "border_padding", "padding", "line_spacing",
                    "icon_bounds", "icon_spacing", "labels", "plots",
                    "plots_items", "labels_items", "border_width", "align",
                    "position", "position_items", "bounds", "bounds_items",
                    "label_at_top"):
            self._layout_needed = True
        if name == "color":
            self.get_preferred_size()
        return

    def _plots_changed(self):
        """ Invalidate the caches.
        """
        self._cached_labels = []
        self._cached_label_sizes = None
        self._cached_label_names = []
        self._cached_visible_plots = []
        self._cached_label_positions = None

    def _title_at_top_changed(self, old, new):
        """ Trait handler for when self.title_at_top changes. """
        if old == True:
            indx = 0
        else:
            indx = -1
        if old != None:
            self._cached_labels.pop(indx)
            self._cached_label_names.pop(indx)
            self._cached_visible_plots.pop(indx)

        # For the legend title
        if self.title_at_top:
            self._cached_labels.insert(0, self._create_label(self.title))
            self._cached_label_names.insert(0, '__legend_label__')
            self._cached_visible_plots.insert(0, None)
        else:
            self._cached_labels.append(self._create_label(self.title))
            self._cached_label_names.append(self.title)
            self._cached_visible_plots.append(None)
Exemple #10
0
class PopupControl(HasPrivateTraits):

    # -- Constructor Traits ---------------------------------------------------

    #: The control the popup should be positioned relative to:
    control = Instance(wx.Window)

    #: The minimum width of the popup:
    width = Int()

    #: The minimum height of the popup:
    height = Int()

    #: Should the popup be resizable?
    resizable = Bool(False)

    # -- Public Traits --------------------------------------------------------

    #: The value (if any) set by the popup control:
    value = Any()

    #: Event fired when the popup control is closed:
    closed = Event()

    # -- Private Traits -------------------------------------------------------

    #: The popup control:
    popup = Instance(wx.Window)

    # -- Public Methods -------------------------------------------------------

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

        style = wx.SIMPLE_BORDER
        if self.resizable:
            style = wx.RESIZE_BORDER

        self.popup = popup = wx.Frame(None, -1, "", style=style)
        popup.Bind(wx.EVT_ACTIVATE, self._on_close_popup)
        self.create_control(popup)
        self._position_control()
        popup.Show()

    def create_control(self):
        """Creates the control.

        Must be overridden by a subclass.
        """
        raise NotImplementedError

    def dispose(self):
        """Called when the popup is being closed to allow any custom clean-up.

        Can be overridden by a subclass.
        """
        pass

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

    def _value_changed(self, value):
        """Handles the 'value' being changed."""
        do_later(self._close_popup)

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

    def _position_control(self):
        """Initializes the popup control's initial position and size."""
        # Calculate the desired size of the popup control:
        px, cy = self.control.ClientToScreen(0, 0)
        cdx, cdy = self.control.GetSize()
        pdx, pdy = self.popup.GetSize()
        pdx, pdy = max(pdx, cdx, self.width), max(pdy, self.height)

        # Calculate the best position and size for the pop-up:
        py = cy + cdy
        if (py + pdy) > SystemMetrics().screen_height:
            if (cy - pdy) < 0:
                bottom = SystemMetrics().screen_height - py
                if cy > bottom:
                    py, pdy = 0, cy
                else:
                    pdy = bottom
            else:
                py = cy - pdy

        # Finally, position the popup control:
        self.popup.SetSize(px, py, pdx, pdy)

    def _on_close_popup(self, event):
        """Closes the popup control when it is deactivated."""
        if not event.GetActive():
            self._close_popup()

    def _close_popup(self):
        """Closes the dialog."""
        self.popup.Unbind(wx.EVT_ACTIVATE)
        self.dispose()
        self.closed = True
        self.popup.Destroy()
        self.popup = self.control = None
Exemple #11
0
class ExportFCS(HasStrictTraits):
    """
    Exports events as FCS files.  
    
    This isn't a traditional view, in that it doesn't implement :meth:`plot`.
    Instead, use :meth:`enum_files` to figure out which files will be created
    from a particular experiment, and :meth:`export` to export the FCS files.
    
    The Cytoflow attributes will be encoded in keywords in the FCS TEXT
    segment, starting with the characters ``CF_``.  Any FCS keywords that 
    are the same across all the input files will also be included.
    
    Attributes
    ----------
    base : Str
        The prefix of the FCS filenames
        
    path : Directory
        The directory to export to.
        
    by : List(Str)
        A list of conditions from :attr:`~.Experiment.conditions`; each unique
        combination of conditions will be exported to an FCS file.
        
    keywords : Dict(Str, Str)
        If you want to add more keywords to the FCS files' TEXT segment, 
        specify them here.
        
    subset : str
        A Python expression used to select a subset of the data
    
    Examples
    --------
    
    Make a little data set.
            
    >>> import cytoflow as flow
    >>> import_op = flow.ImportOp()
    >>> import_op.tubes = [flow.Tube(file = "Plate01/RFP_Well_A3.fcs",
    ...                              conditions = {'Dox' : 10.0}),
    ...                    flow.Tube(file = "Plate01/CFP_Well_A4.fcs",
    ...                              conditions = {'Dox' : 1.0})]
    >>> import_op.conditions = {'Dox' : 'float'}
    >>> ex = import_op.apply()
        
    Export the data
        
    >>> import tempfile
    >>> flow.ExportFCS(path = 'export/',
    ...                by = ["Dox"],
    ...                subset = "Dox == 10.0").export(ex)
        
    """
    
    # traits   
    id = Constant("edu.mit.synbio.cytoflow.view.table")
    friendly_id = Constant("Table View") 
    
    base = Str
    path = Directory(exists = True)
    by = List(Str)
    keywords = Dict(Str, Str)
    
    subset = Str
    
    _include_by = Bool(True)
    
    def enum_files(self, experiment):
        """
        Return an iterator over the file names that this export module will
        produce from a given experiment.
        
        Parameters
        ----------
        experiment : Experiment
            The :class:`.Experiment` to export
        """
        if experiment is None:
            raise util.CytoflowViewError('experiment', "No experiment specified")   
        
        if len(self.by) == 0:
            raise util.CytoflowViewError('by',
                                         "You must specify some variables in `by`")

        for b in self.by:
            if b not in experiment.conditions:
                raise util.CytoflowOpError('by',
                                           "Aggregation metadata {} not found, "
                                           "must be one of {}"
                                           .format(b, experiment.conditions))
                
        if self.subset:
            try:
                experiment = experiment.query(self.subset)
            except util.CytoflowError as e:
                raise util.CytoflowViewError('subset', str(e)) from e
            except Exception as e:
                raise util.CytoflowViewError('subset',
                                             "Subset string '{0}' isn't valid"
                                             .format(self.subset)) from e
                 
            if len(experiment) == 0:
                raise util.CytoflowViewError('subset',
                                             "Subset string '{0}' returned no events"
                                             .format(self.subset))
                        
        class file_enum(object):
            
            def __init__(self, by, base, _include_by, experiment):
                self._iter = None
                self._returned = False
                self.by = by
                self.base = base
                self._include_by = _include_by
                
                if by:
                    self._iter = experiment.data.groupby(by).__iter__()
                
            def __iter__(self):
                return self
            
            def __next__(self):
                if self._iter:
                    values = next(self._iter)[0]
                    
                    if len(self.by) == 1:
                        values = [values]
                    
                    parts = []
                    for i, name in enumerate(self.by):
                        if self._include_by:
                            parts.append(name + '_' + str(values[i]))
                        else:
                            parts.append(str(values[i]))
                        
                    if self.base:
                        return self.base + '_' + '_'.join(parts) + '.fcs'
                    else:
                        return '_'.join(parts) + '.fcs'
                        
                else:
                    if self._returned:
                        raise StopIteration
                    else:
                        self._returned = True
                        return None
            
        return file_enum(self.by, self.base, self._include_by, experiment)
    
    def export(self, experiment):
        """
        Export FCS files from an experiment.
        
        Parameters
        ----------
        experiment : Experiment
            The :class:`.Experiment` to export
        """
        
        if experiment is None:
            raise util.CytoflowViewError('experiment', "No experiment specified")
        
        if len(experiment) == 0:
            raise util.CytoflowViewError('experiment', "No events in experiment")
        
        if not self.path:
            raise util.CytoflowOpError('path',
                                       'Must specify an output directory')
        
        d = Path(self.path)
        
        if not d.is_dir():
            raise util.CytoflowOpError('path',
                                       'Output directory {} must exist')
        
        # also tests for good experiment, self.by
        for filename in self.enum_files(experiment):
            p = d / filename
            if p.is_file():
                raise util.CytoflowOpError('path',
                                           'File {} already exists'
                                           .format(p)) 
                
        if self.subset:
            try:
                experiment = experiment.query(self.subset)
            except util.CytoflowError as e:
                raise util.CytoflowViewError('subset', str(e)) from e
            except Exception as e:
                raise util.CytoflowViewError('subset',
                                             "Subset string '{0}' isn't valid"
                                             .format(self.subset)) from e
                 
            if len(experiment) == 0:
                raise util.CytoflowViewError('subset',
                                             "Subset string '{0}' returned no events"
                                             .format(self.subset))
            
        tube0, common_metadata = list(experiment.metadata['fcs_metadata'].items())[0]
        common_metadata = copy(common_metadata)
        
        exclude_keywords = ['$BEGINSTEXT', '$ENDSTEXT', '$BEGINANALYSIS', 
                            '$ENDANALYSIS', '$BEGINDATA', '$ENDDATA',
                            '$BYTEORD', '$DATATYPE', '$MODE', '$NEXTDATA', 
                            '$TOT', '$PAR']
        common_metadata = {str(k) : str(v) for k, v in common_metadata.items()
                           if re.search('^\$P\d+[BENRDSG]$', k) is None
                           and k not in exclude_keywords}
        
        for filename, metadata in experiment.metadata['fcs_metadata'].items():
            if filename == tube0:
                continue
            for name, value in metadata.items():
                if name not in common_metadata:
                    continue
                
                if name not in common_metadata or value != common_metadata[name]:
                    del common_metadata[name]
                    
                    
        for i, channel in enumerate(experiment.channels):
            if 'voltage' in experiment.metadata[channel]:
                common_metadata['$P{}V'.format(i + 1)] = experiment.metadata[channel]['voltage']
            
        
        for group, data_subset in experiment.data.groupby(self.by):
            data_subset = data_subset[experiment.channels]
            
            if len(self.by) == 1:
                group = [group]
            
            parts = []
            kws = copy(self.keywords)
            kws.update(common_metadata)
            kws = {k : str(v) for k, v in kws.items()}
            
            for i, name in enumerate(self.by):
                if self._include_by:
                    parts.append(name + '_' + str(group[i]))
                else:
                    parts.append(str(group[i]))
                    kws["CF_" + name] = str(group[i])
                
            if self.base:
                filename = self.base + '_' + '_'.join(parts) + '.fcs'
            else:
                filename = '_'.join(parts) + '.fcs'
                
        
            full_path = d / filename
            util.write_fcs(str(full_path), 
                           experiment.channels, 
                           {c: experiment.metadata[c]['range'] for c in experiment.channels},
                           data_subset.values,
                           compat_chn_names = False,
                           compat_negative = False,
                           **kws)
Exemple #12
0
class StubEditor(Editor):
    """ A minimal editor implementaton for a StubEditorFactory.

    The editor creates a FakeControl instance as its control object
    and keeps values synchronized either to `control_value` or
    `control_event` (if `is_event` is True).
    """

    #: Whether or not the traits are events.
    is_event = Bool()

    #: An auxiliary value we want to synchronize.
    auxiliary_value = Any()

    #: An auxiliary list we want to synchronize.
    auxiliary_list = List()

    #: An auxiliary event we want to synchronize.
    auxiliary_event = Event()

    #: An auxiliary int we want to synchronize with a context value.
    auxiliary_cv_int = Int(sync_value="from")

    #: An auxiliary float we want to synchronize with a context value.
    auxiliary_cv_float = Float()

    def init(self, parent):
        self.control = FakeControl()
        self.is_event = self.factory.is_event
        if self.is_event:
            self.control.on_trait_change(self.update_object, "control_event")
        else:
            self.control.on_trait_change(self.update_object, "control_value")
        self.set_tooltip()

    def dispose(self):
        if self.is_event:
            self.control.on_trait_change(self.update_object,
                                         "control_event",
                                         remove=True)
        else:
            self.control.on_trait_change(self.update_object,
                                         "control_value",
                                         remove=True)
        super().dispose()

    def update_editor(self):
        if self.is_event:
            self.control.control_event = True
        else:
            self.control.control_value = self.value

    def update_object(self, new):
        if self.control is not None:
            if not self._no_update:
                self._no_update = True
                try:
                    self.value = new
                finally:
                    self._no_update = False

    def set_tooltip_text(self, control, text):
        control.tooltip = text

    def set_focus(self, parent):
        pass
Exemple #13
0
class ImageWidget(Widget):
    """ A clickable/draggable widget containing an image. """

    #### 'ImageWidget' interface ##############################################

    # The bitmap.
    bitmap = Any

    # Is the widget selected?
    selected = Bool(False)

    #### Events ####

    # A key was pressed while the tree is in focus.
    key_pressed = Event

    # A node has been activated (ie. double-clicked).
    node_activated = Event

    # A drag operation was started on a node.
    node_begin_drag = Event

    # A (non-leaf) node has been collapsed.
    node_collapsed = Event

    # A (non-leaf) node has been expanded.
    node_expanded = Event

    # A left-click occurred on a node.
    node_left_clicked = Event

    # A right-click occurred on a node.
    node_right_clicked = Event

    #### Private interface ####################################################

    _selected = Any

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

    def __init__ (self, parent, **traits):
        """ Creates a new widget. """

        # Base class constructors.
        super(ImageWidget, self).__init__(**traits)

        # Add some padding around the image.
        size = (self.bitmap.GetWidth() + 10, self.bitmap.GetHeight() + 10)

        # Create the toolkit-specific control.
        self.control = wx.Window(parent, -1, size=size)
        self.control.__tag__ = 'hack'

        self._mouse_over  = False
        self._button_down = False

        # Set up mouse event handlers:
        wx.EVT_ENTER_WINDOW(self.control, self._on_enter_window)
        wx.EVT_LEAVE_WINDOW(self.control, self._on_leave_window)
        wx.EVT_LEFT_DCLICK(self.control, self._on_left_dclick)
        wx.EVT_LEFT_DOWN(self.control, self._on_left_down)
        wx.EVT_LEFT_UP(self.control, self._on_left_up)
        wx.EVT_PAINT(self.control, self._on_paint)

        # Pens used to draw the 'selection' marker:
        # ZZZ: Make these class instances when moved to the wx toolkit code.
        self._selectedPenDark = wx.Pen(
            wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DSHADOW), 1, wx.SOLID
        )

        self._selectedPenLight = wx.Pen(
            wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DHIGHLIGHT), 1, wx.SOLID
        )

        return

    ###########################################################################
    # Private interface.
    ###########################################################################

    #### Trait event handlers #################################################

    def _bitmap_changed(self, bitmap):
        """ Called when the widget's bitmap is changed. """

        if self.control is not None:
            self.control.Refresh()

        return

    def _selected_changed(self, selected):
        """ Called when the selected state of the widget is changed. """

        if selected:
            for control in self.GetParent().GetChildren():
                if hasattr(control, '__tag__'):
                    if control.Selected():
                      control.Selected(False)
                      break

                self.Refresh()

        return

    #### wx event handlers ####################################################

    def _on_enter_window(self, event):
        """ Called when the mouse enters the widget. """

        if self._selected is not None:
            self._mouse_over = True
            self.Refresh()

        return

    def _on_leave_window(self, event):
        """ Called when the mouse leaves the widget. """

        if self._mouse_over:
            self._mouse_over = False
            self.Refresh()

        return

    def _on_left_dclick(self, event):
        """ Called when the left mouse button is double-clicked. """

        #print 'left dclick'

        event.Skip()

        return

    def _on_left_down ( self, event = None ):
        """ Called when the left mouse button goes down on the widget. """

        #print 'left down'

        if self._selected is not None:
            self.CaptureMouse()
            self._button_down = True
            self.Refresh()

        event.Skip()

        return

    def _on_left_up ( self, event = None ):
        """ Called when the left mouse button goes up on the widget. """

        #print 'left up'

        need_refresh = self._button_down
        if need_refresh:
            self.ReleaseMouse()
            self._button_down = False

        if self._selected is not None:
            wdx, wdy = self.GetClientSizeTuple()
            x        = event.GetX()
            y        = event.GetY()
            if (0 <= x < wdx) and (0 <= y < wdy):
                if self._selected != -1:
                    self.Selected( True )
                elif need_refresh:
                    self.Refresh()

                return

        if need_refresh:
            self.Refresh()

        event.Skip()

        return

    def _on_paint ( self, event = None ):
        """ Called when the widget needs repainting. """

        wdc      = wx.PaintDC( self.control )
        wdx, wdy = self.control.GetClientSizeTuple()
        bitmap   = self.bitmap
        bdx      = bitmap.GetWidth()
        bdy      = bitmap.GetHeight()
        wdc.DrawBitmap( bitmap, (wdx - bdx) / 2, (wdy - bdy) / 2, True )

        pens = [ self._selectedPenLight, self._selectedPenDark ]
        bd   = self._button_down
        if self._mouse_over:
            wdc.SetBrush( wx.TRANSPARENT_BRUSH )
            wdc.SetPen( pens[ bd ] )
            wdc.DrawLine( 0, 0, wdx, 0 )
            wdc.DrawLine( 0, 1, 0, wdy )
            wdc.SetPen( pens[ 1 - bd ] )
            wdc.DrawLine( wdx - 1, 1, wdx - 1, wdy )
            wdc.DrawLine( 1, wdy - 1, wdx - 1, wdy - 1 )

        if self._selected == True:
            wdc.SetBrush( wx.TRANSPARENT_BRUSH )
            wdc.SetPen( pens[ bd ] )
            wdc.DrawLine( 1, 1, wdx - 1, 1 )
            wdc.DrawLine( 1, 1, 1, wdy - 1 )
            wdc.DrawLine( 2, 2, wdx - 2, 2 )
            wdc.DrawLine( 2, 2, 2, wdy - 2 )
            wdc.SetPen( pens[ 1 - bd ] )
            wdc.DrawLine( wdx - 2, 2, wdx - 2, wdy - 1 )
            wdc.DrawLine( 2, wdy - 2, wdx - 2, wdy - 2 )
            wdc.DrawLine( wdx - 3, 3, wdx - 3, wdy - 2 )
            wdc.DrawLine( 3, wdy - 3, wdx - 3, wdy - 3 )

        return
Exemple #14
0
class BaselineView(HasTraits):
    python_console_cmds = Dict()

    ns = List()
    es = List()
    ds = List()

    table = List()

    plot = Instance(Plot)
    plot_data = Instance(ArrayPlotData)

    running = Bool(True)
    position_centered = Bool(False)

    clear_button = SVGButton(label='',
                             tooltip='Clear',
                             filename=os.path.join(os.path.dirname(__file__),
                                                   'images', 'iconic',
                                                   'x.svg'),
                             width=16,
                             height=16)
    zoomall_button = SVGButton(label='',
                               tooltip='Zoom All',
                               filename=os.path.join(os.path.dirname(__file__),
                                                     'images', 'iconic',
                                                     'fullscreen.svg'),
                               width=16,
                               height=16)
    center_button = SVGButton(label='',
                              tooltip='Center on Baseline',
                              toggle=True,
                              filename=os.path.join(os.path.dirname(__file__),
                                                    'images', 'iconic',
                                                    'target.svg'),
                              width=16,
                              height=16)
    paused_button = SVGButton(
        label='',
        tooltip='Pause',
        toggle_tooltip='Run',
        toggle=True,
        filename=os.path.join(os.path.dirname(__file__), 'images', 'iconic',
                              'pause.svg'),
        toggle_filename=os.path.join(os.path.dirname(__file__), 'images',
                                     'iconic', 'play.svg'),
        width=16,
        height=16)

    reset_button = Button(label='Reset Filters')
    reset_iar_button = Button(label='Reset IAR')
    init_base_button = Button(label='Init. with known baseline')

    traits_view = View(
        HSplit(
            Item('table',
                 style='readonly',
                 editor=TabularEditor(adapter=SimpleAdapter()),
                 show_label=False,
                 width=0.3),
            VGroup(
                HGroup(
                    Item('paused_button', show_label=False),
                    Item('clear_button', show_label=False),
                    Item('zoomall_button', show_label=False),
                    Item('center_button', show_label=False),
                    Item('reset_button', show_label=False),
                    Item('reset_iar_button', show_label=False),
                    Item('init_base_button', show_label=False),
                ),
                Item(
                    'plot',
                    show_label=False,
                    editor=ComponentEditor(bgcolor=(0.8, 0.8, 0.8)),
                ))))

    def _zoomall_button_fired(self):
        self.plot.index_range.low_setting = 'auto'
        self.plot.index_range.high_setting = 'auto'
        self.plot.value_range.low_setting = 'auto'
        self.plot.value_range.high_setting = 'auto'

    def _center_button_fired(self):
        self.position_centered = not self.position_centered

    def _paused_button_fired(self):
        self.running = not self.running

    def _reset_button_fired(self):
        self.link.send(SBP_MSG_RESET_FILTERS, '\x00')

    def _reset_iar_button_fired(self):
        self.link.send(SBP_MSG_RESET_FILTERS, '\x01')

    def _init_base_button_fired(self):
        self.link.send(SBP_MSG_INIT_BASE, '')

    def _clear_button_fired(self):
        self.ns = []
        self.es = []
        self.ds = []
        self.plot_data.set_data('n', [])
        self.plot_data.set_data('e', [])
        self.plot_data.set_data('d', [])
        self.plot_data.set_data('t', [])
        self.plot_data.set_data('curr_n', [])
        self.plot_data.set_data('curr_e', [])
        self.plot_data.set_data('curr_d', [])

    def _baseline_callback_ecef(self, data):
        #Don't do anything for ECEF currently
        return

    def iar_state_callback(self, sbp_msg):
        self.num_hyps = struct.unpack('<I', sbp_msg.payload)[0]

    def _baseline_callback_ned(self, sbp_msg):
        # Updating an ArrayPlotData isn't thread safe (see chaco issue #9), so
        # actually perform the update in the UI thread.
        if self.running:
            GUI.invoke_later(self.baseline_callback, sbp_msg)

    def update_table(self):
        self._table_list = self.table.items()

    def gps_time_callback(self, sbp_msg):
        self.week = MsgGPSTime(sbp_msg).wn
        self.nsec = MsgGPSTime(sbp_msg).ns

    def baseline_callback(self, sbp_msg):
        soln = MsgBaselineNED(sbp_msg)
        table = []

        soln.n = soln.n * 1e-3
        soln.e = soln.e * 1e-3
        soln.d = soln.d * 1e-3

        dist = np.sqrt(soln.n**2 + soln.e**2 + soln.d**2)

        tow = soln.tow * 1e-3
        if self.nsec is not None:
            tow += self.nsec * 1e-9

        if self.week is not None:
            t = datetime.datetime(1980, 1, 6) + \
                datetime.timedelta(weeks=self.week) + \
                datetime.timedelta(seconds=tow)

            table.append(('GPS Time', t))
            table.append(('GPS Week', str(self.week)))

            if self.log_file is None:
                self.log_file = open(
                    time.strftime("baseline_log_%Y%m%d-%H%M%S.csv"), 'w')

            self.log_file.write('%s,%.4f,%.4f,%.4f,%.4f,%d,0x%02x,%d\n' %
                                (str(t), soln.n, soln.e, soln.d, dist,
                                 soln.n_sats, soln.flags, self.num_hyps))
            self.log_file.flush()

        table.append(('GPS ToW', tow))

        table.append(('N', soln.n))
        table.append(('E', soln.e))
        table.append(('D', soln.d))
        table.append(('Dist.', dist))
        table.append(('Num. Sats.', soln.n_sats))
        table.append(('Flags', '0x%02x' % soln.flags))
        if soln.flags & 1:
            table.append(('Mode', 'Fixed RTK'))
        else:
            table.append(('Mode', 'Float'))
        table.append(('IAR Num. Hyps.', self.num_hyps))

        self.ns.append(soln.n)
        self.es.append(soln.e)
        self.ds.append(soln.d)

        self.ns = self.ns[-1000:]
        self.es = self.es[-1000:]
        self.ds = self.ds[-1000:]

        self.plot_data.set_data('n', self.ns)
        self.plot_data.set_data('e', self.es)
        self.plot_data.set_data('d', self.ds)
        self.plot_data.set_data('curr_n', [soln.n])
        self.plot_data.set_data('curr_e', [soln.e])
        self.plot_data.set_data('curr_d', [soln.d])
        self.plot_data.set_data('ref_n', [0.0])
        self.plot_data.set_data('ref_e', [0.0])
        self.plot_data.set_data('ref_d', [0.0])

        t = range(len(self.ns))
        self.plot_data.set_data('t', t)

        if self.position_centered:
            d = (self.plot.index_range.high - self.plot.index_range.low) / 2.
            self.plot.index_range.set_bounds(soln.e - d, soln.e + d)
            d = (self.plot.value_range.high - self.plot.value_range.low) / 2.
            self.plot.value_range.set_bounds(soln.n - d, soln.n + d)

        self.table = table

    def __init__(self, link):
        super(BaselineView, self).__init__()

        self.log_file = None

        self.num_hyps = 0

        self.plot_data = ArrayPlotData(n=[0.0],
                                       e=[0.0],
                                       d=[0.0],
                                       t=[0.0],
                                       ref_n=[0.0],
                                       ref_e=[0.0],
                                       ref_d=[0.0],
                                       curr_e=[0.0],
                                       curr_n=[0.0],
                                       curr_d=[0.0])
        self.plot = Plot(self.plot_data)
        lin = self.plot.plot(('e', 'n'), type='line', color=(0, 0, 1, 0.1))
        pts = self.plot.plot(('e', 'n'),
                             type='scatter',
                             color='blue',
                             marker='dot',
                             line_width=0.0,
                             marker_size=1.0)
        ref = self.plot.plot(('ref_e', 'ref_n'),
                             type='scatter',
                             color='red',
                             marker='plus',
                             marker_size=5,
                             line_width=1.5)
        cur = self.plot.plot(('curr_e', 'curr_n'),
                             type='scatter',
                             color='orange',
                             marker='plus',
                             marker_size=5,
                             line_width=1.5)
        plot_labels = [
            'Base Position', 'Rover Relative Position', 'Rover Path'
        ]
        plots_legend = dict(zip(plot_labels, [ref, cur, pts]))
        self.plot.legend.plots = plots_legend
        self.plot.legend.visible = True

        self.plot.index_axis.tick_label_position = 'inside'
        self.plot.index_axis.tick_label_color = 'gray'
        self.plot.index_axis.tick_color = 'gray'
        self.plot.index_axis.title = 'E (meters)'
        self.plot.index_axis.title_spacing = 5
        self.plot.value_axis.tick_label_position = 'inside'
        self.plot.value_axis.tick_label_color = 'gray'
        self.plot.value_axis.tick_color = 'gray'
        self.plot.value_axis.title = 'N (meters)'
        self.plot.value_axis.title_spacing = 5
        self.plot.padding = (25, 25, 25, 25)

        self.plot.tools.append(PanTool(self.plot))
        zt = ZoomTool(self.plot,
                      zoom_factor=1.1,
                      tool_mode="box",
                      always_on=False)
        self.plot.overlays.append(zt)

        self.week = None
        self.nsec = 0

        self.link = link
        self.link.add_callback(self._baseline_callback_ned,
                               SBP_MSG_BASELINE_NED)
        self.link.add_callback(self._baseline_callback_ecef,
                               SBP_MSG_BASELINE_ECEF)
        self.link.add_callback(self.iar_state_callback, SBP_MSG_IAR_STATE)
        self.link.add_callback(self.gps_time_callback, SBP_MSG_GPS_TIME)

        self.python_console_cmds = {'baseline': self}
Exemple #15
0
class Pulse(HasTraits):
    duration = Float(1)
    wait_control = Instance(WaitControl, transient=True)
    manager = Any(transient=True)
    power = Float(1.1, auto_set=False, enter_set=True)
    units = Property

    pulse_button = Event
    pulse_label = Property(depends_on='pulsing')
    pulsing = Bool(False)
    enabled = Bool(False)

    disable_at_end = Bool(False)

    # def dump(self):
    #         p = os.path.join(paths.hidden_dir, 'pulse')
    #         with open(p, 'wb') as f:
    #             pickle.dump(self, f)

    @on_trait_change('manager:enabled')
    def upad(self, obj, name, old, new):
        self.enabled = new

    def _power_changed(self):
        if self.pulsing and self.manager:
            self.manager.set_laser_power(self.power)

    def _duration_changed(self):
        self.wait_control.duration = self.duration
        self.wait_control.reset()

    def _wait_control_default(self):
        return WaitControl(low_name=0,
                           auto_start=False,
                           duration=self.duration,
                           title='',
                           dispose_at_end=False)

    def start(self):
        self._duration_changed()

        #         evt = TEvent()
        man = self.manager
        if man is not None:
            # man.enable_laser()
            resp = man.set_laser_power(self.power)
            if resp is False:
                self.pulsing = False
                self.trait_set(duration=0, trait_change_notify=False)
                return

        self.wait_control.start()

        self.pulsing = False
        if man is not None:
            if self.disable_at_end:
                man.disable_laser()
            else:
                man.set_laser_power(0)

    def _get_pulse_label(self):
        return 'Fire' if not self.pulsing else 'Stop'

    def _get_units(self):
        return self.manager.units

    def _pulse_button_fired(self):
        if self.pulsing:
            self.pulsing = False
            self.wait_control.current_time = -1
        else:
            self.pulsing = True
            t = Thread(target=self.start)
            t.start()

    def traits_view(self):
        v = View(
            VGroup(
                VGroup(
                    HGroup(
                        Item('power',
                             tooltip='Hit Enter for change to take effect'),
                        Item('units', style='readonly', show_label=False),
                        spring,
                        Item('pulse_button',
                             editor=ButtonEditor(label_value='pulse_label'),
                             show_label=False,
                             enabled_when='object.enabled')),
                    Item('duration',
                         label='Duration (s)',
                         tooltip='Set the laser pulse duration in seconds')),
                VGroup(
                    CustomLabel(
                        'object.wait_control.message',
                        size=14,
                        weight='bold',
                        color_name='object.wait_control.message_color'),
                    HGroup(
                        Spring(width=-5, springy=False),
                        Item('object.wait_control.high',
                             label='Set Max. Seconds'), spring,
                        UItem('object.wait_control.continue_button')),
                    HGroup(
                        Spring(width=-5, springy=False),
                        Item(
                            'object.wait_control.current_time',
                            show_label=False,
                            editor=RangeEditor(
                                mode='slider',
                                low=1,
                                # low_name='low_name',
                                high_name='object.wait_control.duration')),
                        CustomLabel('object.wait_control.current_time',
                                    size=14,
                                    weight='bold')))),
            # Item('wait_control', show_label=False, style='custom'),
            id='pulse',
            handler=PulseHandler())
        return v
Exemple #16
0
class AramisPlot2D(HasTraits):
    '''This class manages 2D views for AramisCDT variables
    '''

    aramis_info = Instance(AramisInfo)

    aramis_data = Instance(AramisFieldData)

    aramis_cdt = Instance(AramisCDT)

    figure = Instance(Figure)

    save_plot = Bool(False)

    show_plot = Bool(True)

    save_dir = Directory

    def _save_dir_default(self):
        return os.getcwd()

    temp_dir = Directory

    def _temp_dir_default(self):
        return tempfile.mkdtemp()

    plot2d = Button

    def _plot2d_fired(self):
        aramis_data = self.aramis_data
        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()

        ax = fig.add_subplot(2, 2, 1)
        ax.plot(aramis_data.i_cut.T, aramis_data.d_ux.T, color='black')
        ax.plot(aramis_data.i_cut[0, :],
                aramis_data.d_ux_avg,
                color='red',
                linewidth=2)
        # for x in aramis_data.d_ux:
        #    ax.plot(aramis_data.i_cut.T, np.convolve(x, np.ones(10) / 10., 'same'), color='green', linewidth=0.3)
        y_max_lim = ax.get_ylim()[-1]
        ax.vlines(aramis_data.i_cut[0, :-1], [0],
                  aramis_cdt.crack_filter_avg * y_max_lim,
                  color='magenta',
                  linewidth=1,
                  zorder=10)
        ax.set_title('d_ux')

        ax2 = fig.add_subplot(2, 2, 2)
        ax2.plot(aramis_data.i_cut.T, aramis_data.ux_arr.T, color='green')
        ax2.plot(aramis_data.i_cut[0, :],
                 aramis_data.ux_arr_avg,
                 color='red',
                 linewidth=2)
        y_min_lim = np.min(ax2.get_ylim())
        y_max_lim = np.max(ax2.get_ylim())
        ax2.vlines(aramis_data.i_cut[0, :-1],
                   y_min_lim,
                   aramis_cdt.crack_filter_avg * y_max_lim +
                   ~aramis_cdt.crack_filter_avg * y_min_lim,
                   color='magenta',
                   linewidth=1,
                   zorder=10)
        ax2.set_title('ux')

        ax3 = fig.add_subplot(2, 2, 3)
        ax3.plot(aramis_data.i_cut[0, :], aramis_data.dd_ux_avg, color='black')
        ax3.plot(aramis_data.i_cut[0, :], aramis_data.ddd_ux_avg, color='blue')
        y_max_lim = ax3.get_ylim()[-1]
        ax3.vlines(aramis_data.i_cut[0, :-1], [0],
                   aramis_cdt.crack_filter_avg * y_max_lim,
                   color='magenta',
                   linewidth=1,
                   zorder=10)
        ax3.set_title('dd_ux, ddd_ux')

        ax = fig.add_subplot(2, 2, 4)
        ax.plot(aramis_data.i_cut.T, aramis_data.delta_ux_arr.T, color='black')
        ax.plot(aramis_data.i_cut[0, :],
                aramis_data.delta_ux_arr_avg,
                color='red',
                linewidth=2)
        y_max_lim = ax.get_ylim()[-1]
        ax.vlines(aramis_data.i_cut[0, :-1], [0],
                  aramis_cdt.crack_filter_avg * y_max_lim,
                  color='magenta',
                  linewidth=1,
                  zorder=10)
        ax.set_title('delta_ux')

        #         ax = fig.add_subplot(2, 2, 4)
        #         from aramis_data import get_d
        #         ir = aramis_data.integ_radius
        #         ax.plot(aramis_data.x_arr_0.T[ir:-ir, :],
        #                 (get_d(aramis_data.x_arr_0 + aramis_data.ux_arr, ir).T - get_d(aramis_data.x_arr_0, ir).T)[ir:-ir, :], color='black')
        #        ax.plot(aramis_data.x_arr_0.T[ir:-ir, :],
        #                 get_d(aramis_data.x_arr_0 + aramis_data.ux_arr, ir).T[ir:-ir, :], color='black')
        #         xx = get_d(aramis_data.x_arr_0 + aramis_data.ux_arr, ir)
        #         print xx[:, ir]
        #         print xx[:, ir][:, None] * np.ones(xx.shape[1])[None, :]
        #         ax.plot(aramis_data.x_arr_0.T[ir:-ir, :],
        #                 (xx - xx[:, ir][:, None] * np.ones(xx.shape[1])[None, :]).T[ir:-ir, :] * 1000, color='black')

        plt.suptitle(self.aramis_info.specimen_name +
                     ' - %d' % aramis_data.current_step)

        aramis_cdt.crack_spacing_avg

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'plot2d.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_crack_hist = Button

    def _plot_crack_hist_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.hist(aramis_cdt.crack_arr.flatten(), bins=40, normed=True)

        ax.set_xlabel('crack_width [mm]')
        ax.set_ylabel('frequency [-]')

        ax2 = ax.twinx()
        ax2.hist(aramis_cdt.crack_arr.flatten(),
                 normed=True,
                 histtype='step',
                 color='black',
                 cumulative=True,
                 bins=40,
                 linewidth=2)
        ax2.set_ylabel('probability [-]')

        ax.set_title(aramis_cdt.aramis_info.specimen_name +
                     ' - %d' % self.aramis_data.current_step)

        mu = float(aramis_cdt.crack_arr.flatten().mean())
        sigma = float(aramis_cdt.crack_arr.flatten().std())
        skew = float(stats.skew(aramis_cdt.crack_arr.flatten()))
        textstr = '$\mu=%.3g$\n$\sigma=%.3g$\n$\gamma_1=%.3g$' % (mu, sigma,
                                                                  skew)

        # these are matplotlib.patch.Patch properties
        props = dict(boxstyle='round', facecolor='wheat', alpha=1)

        # place a text box in upper left in axes coords,
        ax.text(0.95,
                0.95,
                textstr,
                transform=ax.transAxes,
                fontsize=14,
                verticalalignment='top',
                horizontalalignment='right',
                bbox=props)

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'crack_hist.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_number_of_cracks_t = Button

    def _plot_number_of_cracks_t_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.plot(aramis_cdt.control_strain_t, aramis_cdt.number_of_cracks_t)

        ax.set_xlabel('control strain')
        ax.set_ylabel('number of cracks')

        if self.show_plot:
            fig.canvas.draw()

    plot_force_step = Button

    def _plot_force_step_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.set_title('')
        ax.plot(self.aramis_info.step_list, self.aramis_data.force)

        ax.set_xlabel('step')
        ax.set_ylabel('force_t [kN]')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'force_time.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_stress_strain = Button

    def _plot_stress_strain_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.set_title('')
        ax.plot(aramis_cdt.control_strain_t, self.aramis_data.stress)

        ax.set_xlabel('control strain [-]')
        ax.set_ylabel('nominal stress [MPa]')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'stress_strain.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_w_strain = Button

    def _plot_w_strain_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.set_title('')
        plt.plot(aramis_cdt.control_strain_t, aramis_cdt.crack_width_avg_t.T)

        ax.set_xlabel('control strain [-]')
        ax.set_ylabel('crack width [mm]')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'w_strain.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_subw_strain = Button

    def _plot_subw_strain_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.set_title('')
        ax.plot(aramis_cdt.control_strain_t, aramis_cdt.crack_width_t.T)

        ax.set_xlabel('control strain [-]')
        ax.set_ylabel('subcrack width [mm]')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'subw_strain.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_stress_strain_init = Button

    def _plot_stress_strain_init_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.set_title('')
        ax.plot(aramis_cdt.control_strain_t, self.aramis_data.stress)
        ax.plot(aramis_cdt.control_strain_t[aramis_cdt.init_step_avg_lst],
                self.aramis_data.stress[aramis_cdt.init_step_avg_lst],
                'ro',
                linewidth=4)
        ax.plot(aramis_cdt.control_strain_t[aramis_cdt.init_step_lst],
                self.aramis_data.stress[aramis_cdt.init_step_lst],
                'kx',
                linewidth=4)

        ax.set_xlabel('control strain [-]')
        ax.set_ylabel('nominal stress [MPa]')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'stress_strain_init.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_crack_init = Button

    def _plot_crack_init_fired(self):

        aramis_cdt = self.aramis_cdt

        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)
        ax.set_title('')
        ax.vlines(
            np.arange(len(aramis_cdt.init_step_avg_lst)) + 0.5, [0],
            aramis_cdt.init_step_avg_lst)
        ax.set_xlabel('crack number')
        ax.set_ylabel('initiation step')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'crack_init.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_force_time = Button

    def _plot_force_time_fired(self):
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        ax.set_title('')
        ax.plot(self.aramis_data.step_times, self.aramis_data.force)
        ax.set_xlabel('Time')
        ax.set_ylabel('Force')

        if self.show_plot:
            fig.canvas.draw()

    plot_number_of_missing_facets = Button

    def _plot_number_of_missing_facets_fired(self):

        aramis_cdt = self.aramis_cdt
        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111)

        y = aramis_cdt.number_of_missing_facets_t
        ax.plot(self.aramis_info.step_list, y)

        ax.set_xlabel('step')
        ax.set_ylabel('number of missing facets')
        ax.set_title('missing facets in time')

        if self.save_plot:
            fig.savefig(
                os.path.join(self.save_dir, 'number_of_missing_facets.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_strain_crack_avg = Button

    def _plot_strain_crack_avg_fired(self):

        aramis_cdt = self.aramis_cdt
        aramis_data = self.aramis_data

        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111, aspect='equal')
        ax.set_title(aramis_cdt.aramis_info.specimen_name +
                     ' - %d' % self.aramis_data.current_step)

        plot3d_var = getattr(aramis_data, 'd_ux')

        mask = np.logical_or(np.isnan(self.aramis_data.x_arr_0),
                             self.aramis_data.x_0_mask[0, :, :])
        mask = None
        #         plt.scatter(aramis_cdt.x_arr_0[mask],
        #                    aramis_cdt.y_arr_0[mask], c=plot3d_var[mask], cmap=my_cmap_lin,
        #                    marker='s')

        print plot3d_var[mask].shape
        # contour the data, plotting dots at the nonuniform data points.
        #         CS = plt.contour(aramis_cdt.x_arr_0[mask][0, :, :],
        #                          aramis_cdt.y_arr_0[mask][0, :, :],
        #                          plot3d_var[mask][0, :, :], 25, linewidths=.5, colors='k')
        # plotting filled contour
        CS = ax.contourf(self.aramis_data.x_arr_0,
                         self.aramis_data.y_arr_0,
                         plot3d_var,
                         256,
                         cmap=plt.get_cmap('jet'))

        ax.vlines(
            self.aramis_data.x_arr_0[0, :][self.aramis_cdt.crack_filter_avg],
            [0],
            np.nanmax(self.aramis_data.y_arr_0),
            color='white',
            zorder=10,
            linewidth=2)

        ax.set_xlabel('x [mm]')
        ax.set_ylabel('y [mm]')

        fig.colorbar(CS, orientation='horizontal')

        if self.save_plot:
            fig.savefig(os.path.join(self.save_dir, 'strain_crack_avg.png'))

        if self.show_plot:
            fig.canvas.draw()

    plot_crack_filter_crack_avg = Button

    def _plot_crack_filter_crack_avg_fired(self):
        aramis_cdt = self.aramis_cdt
        aramis_data = self.aramis_data

        fig = self.figure
        fig.clf()
        ax = fig.add_subplot(111, aspect='equal')
        ax.set_title(aramis_cdt.aramis_info.specimen_name +
                     ' - %d' % self.aramis_data.current_step)

        plot3d_var = getattr(aramis_data, 'd_ux')

        mask = np.logical_or(np.isnan(self.aramis_data.x_arr_0),
                             self.aramis_data.x_0_mask[0, :, :])
        mask = None
        #         plt.scatter(aramis_cdt.x_arr_0[mask],
        #                    aramis_cdt.y_arr_0[mask], c=plot3d_var[mask], cmap=my_cmap_lin,
        #                    marker='s')

        print plot3d_var[mask].shape
        # contour the gridded data, plotting dots at the nonuniform data points.
        #         CS = plt.contour(aramis_cdt.x_arr_0[mask][0, :, :],
        #                          aramis_cdt.y_arr_0[mask][0, :, :],
        #                          plot3d_var[mask][0, :, :], 25, linewidths=.5, colors='k')
        # plotting filled contour
        CS = ax.contourf(self.aramis_data.x_arr_0,
                         self.aramis_data.y_arr_0,
                         plot3d_var,
                         2,
                         cmap=plt.get_cmap('binary'))
        ax.plot(self.aramis_data.x_arr_0, self.aramis_data.y_arr_0, 'ko')

        ax.plot(self.aramis_data.x_arr_0[aramis_cdt.crack_filter],
                self.aramis_data.y_arr_0[aramis_cdt.crack_filter],
                linestyle='None',
                marker='.',
                color='white')
        #         CS = ax.contourf(aramis_cdt.x_arr_0,
        #                          aramis_cdt.y_arr_0,
        #                          plot3d_var, 256, cmap=plt.get_cmap('jet'))
        #
        #         ax.plot(aramis_cdt.x_arr_0[aramis_cdt.crack_filter],
        #                  aramis_cdt.y_arr_0[aramis_cdt.crack_filter],
        #                  'k.')

        ax.vlines(self.aramis_data.x_arr_0[0, :][aramis_cdt.crack_filter_avg],
                  [0],
                  np.nanmax(self.aramis_data.y_arr_0[mask]),
                  color='magenta',
                  zorder=100,
                  linewidth=2)

        ax.set_xlabel('x [mm]')
        ax.set_ylabel('y [mm]')

        # plt.colorbar()

        # plt.axis('equal')

        if self.save_plot:
            fig.savefig(
                os.path.join(self.save_dir, 'crack_filter_crack_avg.png'))

        if self.show_plot:
            fig.canvas.draw()

    test_figure = Instance(plt.Figure)

    plot_test = Button

    def _plot_test_fired(self):
        aramis_cdt = self.aramis_cdt
        if self.test_figure == None:
            self.test_figure = plt.figure(figsize=(12, 6), dpi=100)
            self.test_figure.canvas.mpl_connect('close_event',
                                                self.handle_close)
        fig = self.test_figure
        fig.clf()

        fig.suptitle(aramis_cdt.aramis_info.specimen_name +
                     ' - %d' % self.aramis_data.current_step,
                     y=1)

        ax_diag = plt.subplot2grid((3, 3), (0, 0))  # no colorbar (2, 3)
        ax_diag.locator_params(nbins=4)
        ax_hist = plt.subplot2grid((3, 3), (1, 0))  # no colorbar (2, 3)
        ax_hist.locator_params(nbins=4)
        ax_area = plt.subplot2grid((3, 3), (0, 1),
                                   rowspan=2,
                                   colspan=2,
                                   adjustable='box',
                                   aspect='equal')  # no colorbar (2, 3)

        x = self.aramis_data.step_times  # self.aramis_cdt.control_strain_t
        stress = self.aramis_data.ad_channels_arr[:, 1]
        ax_diag.plot(x, stress)
        ax_diag.plot(x[self.aramis_data.current_step],
                     stress[self.aramis_data.current_step], 'ro')
        ax_diag.plot(x[aramis_cdt.init_step_avg_lst],
                     stress[aramis_cdt.init_step_avg_lst],
                     'go',
                     ms=4)

        ax_diag.set_xlabel('step number [-]')
        ax_diag.set_ylabel('nominal stress [MPa]')
        ax_diag.set_xlim(0, ax_diag.get_xlim()[1])

        if aramis_cdt.crack_arr.size != 0:
            ax_hist.hist(aramis_cdt.crack_arr.flatten(), bins=40, normed=True)

        ax_hist.set_xlabel('crack_width [mm]')
        ax_hist.set_ylabel('frequency [-]')

        ax_hist_2 = ax_hist.twinx()

        if aramis_cdt.crack_arr.size != 0:
            ax_hist_2.hist(aramis_cdt.crack_arr.flatten(),
                           normed=True,
                           histtype='step',
                           color='black',
                           cumulative=True,
                           bins=40,
                           linewidth=2)
        ax_hist_2.set_ylabel('probability [-]')
        ax_hist_2.set_ylim(0, 1)
        # ax_hist.set_xlim(0, 0.15)
        # ax_hist.set_ylim(0, 50)

        plot3d_var = getattr(self.aramis_data, 'd_ux')

        CS = ax_area.contourf(self.aramis_data.x_arr_0,
                              self.aramis_data.y_arr_0,
                              plot3d_var,
                              2,
                              cmap=plt.get_cmap('binary'))
        ax_area.plot(self.aramis_data.x_arr_0, self.aramis_data.y_arr_0, 'ko')

        # ax_area.plot(self.aramis_data.x_arr_0[aramis_cdt.crack_filter],
        #         self.aramis_data.y_arr_0[aramis_cdt.crack_filter], linestyle='None',
        #         marker='.', color='white')

        # ax_area.scatter(self.aramis_data.x_arr_0[aramis_cdt.crack_filter],
        # self.aramis_data.y_arr_0[aramis_cdt.crack_filter], color='white',
        # zorder=10, s=aramis_cdt.crack_arr * 50)
        sc = ax_area.scatter(
            self.aramis_data.x_arr_0[aramis_cdt.crack_filter],
            self.aramis_data.y_arr_0[aramis_cdt.crack_filter],
            cmap=plt.cm.get_cmap('jet'),  # color='white',
            zorder=100,
            s=aramis_cdt.crack_arr * 300,
            c=aramis_cdt.crack_arr,
            edgecolors='none')

        ax_col = plt.subplot2grid((3, 3), (2, 1),
                                  rowspan=1,
                                  colspan=2,
                                  adjustable='box')
        fig.colorbar(sc, cax=ax_col, orientation='horizontal')

        #         ax_area.vlines(self.aramis_data.x_arr_0[0, :][aramis_cdt.crack_filter_avg],
        #                        [0], np.nanmax(self.aramis_data.y_arr_0[None]),
        #                        color='magenta', zorder=100, linewidth=2)
        #         ax_area.vlines(aramis_cdt.x_arr_0[10, :-1][aramis_cdt.crack_filter_avg],
        #                    [0], np.nanmax(aramis_cdt.y_arr_0),
        #                    color='red', zorder=100, linewidth=1)

        ax_area.set_xlabel('x [mm]')
        ax_area.set_ylabel('y [mm]')

        # ax_area.set_colorbar(CS, orientation='horizontal')
        fig.tight_layout()

        #         cbar_ax = fig.add_axes([0.45, 0.15, 0.5, 0.03])
        #         fig.colorbar(CS, cax=cbar_ax, orientation='horizontal')

        fig.canvas.draw()

        if self.save_plot:
            fig.savefig(
                os.path.join(
                    self.save_dir,
                    '%s%04d.png' % (aramis_cdt.aramis_info.specimen_name,
                                    self.aramis_data.current_step)))

        if self.show_plot:
            fig.show()

    def handle_close(self, evt):
        print 'Closed Figure!'
        import gc
        print gc.collect()
        self.test_figure = None

    create_animation = Button

    def _create_animation_fired(self):
        aramis_cdt = self.aramis_cdt
        save_plot, show_plot = self.save_plot, self.show_plot
        self.save_plot = False
        self.show_plot = False
        start_step_idx = self.aramis_data.current_step
        fname_pattern = '%s%04d'
        for step_idx in self.aramis_info.step_list:
            self.aramis_data.current_step = step_idx
            self.plot_test = True
            self.test_figure.savefig(
                os.path.join(
                    self.temp_dir,
                    fname_pattern % (aramis_cdt.aramis_info.specimen_name,
                                     self.aramis_data.current_step)))

        try:
            os.system(
                'ffmpeg -framerate 3 -i %s.png -vcodec ffv1 -sameq %s.avi' %
                (os.path.join(
                    self.temp_dir,
                    fname_pattern.replace(
                        '%s', aramis_cdt.aramis_info.specimen_name)),
                 os.path.join(self.save_dir,
                              aramis_cdt.aramis_info.specimen_name)))
        except:
            print 'Cannot create video in avi format. Check if you have "ffmpeg" in in your system path.'

        try:
            os.system('convert -verbose -delay 25 %s* %s.gif' %
                      (os.path.join(self.temp_dir,
                                    aramis_cdt.aramis_info.specimen_name),
                       os.path.join(self.save_dir,
                                    aramis_cdt.aramis_info.specimen_name)))
        except:
            print 'Cannot create animated gif. Check if you have "convert" in in your system path.'

        self.save_plot = save_plot
        self.show_plot = show_plot
        self.aramis_data.current_step = start_step_idx

    view = View(
        Group(
            UItem('plot2d'),
            UItem('plot_crack_hist'),
            UItem('plot_number_of_cracks_t'),
            UItem('plot_stress_strain_init'),
            UItem('plot_stress_strain'),
            UItem('plot_w_strain'),
            UItem('plot_subw_strain'),
            UItem('plot_strain_crack_avg'),
            UItem('plot_crack_filter_crack_avg'),
            UItem('plot_crack_init'),
            UItem('plot_test'),
            '_',
            '_',
            UItem('create_animation'),
            label='Plot results',
        ),
        Group(
            UItem('plot_force_time'),
            UItem('plot_force_step'),
            UItem('plot_number_of_missing_facets'),
            label='Check data',
        ),
        id='aramisCDT.view2d',
    )
Exemple #17
0
class Dialog(MDialog, Window):
    """ The toolkit specific implementation of a Dialog.  See the IDialog
    interface for the API documentation.
    """

    #### 'IDialog' interface ##################################################

    cancel_label = Unicode

    help_id = Str

    help_label = Unicode

    ok_label = Unicode

    resizeable = Bool(True)

    return_code = Int(OK)

    style = Enum('modal', 'nonmodal')

    #### 'IWindow' interface ##################################################

    title = Unicode("Dialog")

    ###########################################################################
    # Protected 'IDialog' interface.
    ###########################################################################

    def _create_buttons(self, parent):
        sizer = wx.StdDialogButtonSizer()

        # The 'OK' button.
        if self.ok_label:
            label = self.ok_label
        else:
            label = "OK"

        self._wx_ok = ok = wx.Button(parent, wx.ID_OK, label)
        ok.SetDefault()
        wx.EVT_BUTTON(parent, wx.ID_OK, self._wx_on_ok)
        sizer.AddButton(ok)

        # The 'Cancel' button.
        if self.cancel_label:
            label = self.cancel_label
        else:
            label = "Cancel"

        self._wx_cancel = cancel = wx.Button(parent, wx.ID_CANCEL, label)
        wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel)
        sizer.AddButton(cancel)

        # The 'Help' button.
        if len(self.help_id) > 0:
            if self.help_label:
                label = self.help_label
            else:
                label = "Help"

            help = wx.Button(parent, wx.ID_HELP, label)
            wx.EVT_BUTTON(parent, wx.ID_HELP, self._wx_on_help)
            sizer.AddButton(help)

        sizer.Realize()
        return sizer

    def _create_contents(self, parent):
        sizer = wx.BoxSizer(wx.VERTICAL)
        parent.SetSizer(sizer)
        parent.SetAutoLayout(True)

        # The 'guts' of the dialog.
        dialog_area = self._create_dialog_area(parent)
        sizer.Add(dialog_area, 1, wx.EXPAND | wx.ALL, 5)

        # The buttons.
        buttons = self._create_buttons(parent)
        sizer.Add(buttons, 0, wx.ALIGN_RIGHT | wx.ALL, 5)

        # Resize the dialog to match the sizer's minimal size.
        if self.size != (-1, -1):
            parent.SetSize(self.size)
        else:
            sizer.Fit(parent)

        parent.CentreOnParent()

    def _create_dialog_area(self, parent):
        panel = wx.Panel(parent, -1)
        panel.SetBackgroundColour("red")
        panel.SetSize((100, 200))

        return panel

    def _show_modal(self):
        if sys.platform == 'darwin':
            # Calling Show(False) is needed on the Mac for the modal dialog
            # to show up at all.
            self.control.Show(False)
        return _RESULT_MAP[self.control.ShowModal()]

    ###########################################################################
    # Protected 'IWidget' interface.
    ###########################################################################

    def _create_control(self, parent):
        style = wx.DEFAULT_DIALOG_STYLE | wx.CLIP_CHILDREN

        if self.resizeable:
            style |= wx.RESIZE_BORDER

        return wx.Dialog(parent, -1, self.title, style=style)

    #### wx event handlers ####################################################

    def _wx_on_ok(self, event):
        """ Called when the 'OK' button is pressed. """

        self.return_code = OK

        # Let the default handler close the dialog appropriately.
        event.Skip()

    def _wx_on_cancel(self, event):
        """ Called when the 'Cancel' button is pressed. """

        self.return_code = CANCEL

        # Let the default handler close the dialog appropriately.
        event.Skip()

    def _wx_on_help(self, event):
        """ Called when the 'Help' button is pressed. """

        print 'Heeeeelllllllllllllpppppppppppppppppppp'
Exemple #18
0
class TabularEditor(BasicEditorFactory):
    """ Editor factory for tabular editors.
    """

    #-- Trait Definitions ------------------------------------------------------

    # The editor class to be created:
    klass = Property

    # Should column headers (i.e. titles) be displayed?
    show_titles = Bool(True)

    # Should row headers be displated (Qt4 only)?
    show_row_titles = Bool(False)

    # The optional extended name of the trait used to indicate that a complete
    # table update is needed:
    update = Str

    # The optional extended name of the trait used to indicate that the table
    # just needs to be repainted.
    refresh = Str

    # Should the table update automatically when the table item's contents
    # change? Note that in order for this feature to work correctly, the editor
    # trait should be a list of objects derived from HasTraits. Also,
    # performance can be affected when very long lists are used, since enabling
    # this feature adds and removed Traits listeners to each item in the list.
    auto_update = Bool(False)

    # The optional extended name of the trait to synchronize the selection
    # values with:
    selected = Str

    # The optional extended name of the trait to synchronize the selection rows
    # with:
    selected_row = Str

    # Whether or not to allow selection.
    selectable = Bool(True)

    # The optional extended name of the trait to synchronize the activated value
    # with:
    activated = Str

    # The optional extended name of the trait to synchronize the activated
    # value's row with:
    activated_row = Str

    # The optional extended name of the trait to synchronize left click data
    # with. The data is a TabularEditorEvent:
    clicked = Str

    # The optional extended name of the trait to synchronize left double click
    # data with. The data is a TabularEditorEvent:
    dclicked = Str

    # The optional extended name of the trait to synchronize right click data
    # with. The data is a TabularEditorEvent:
    right_clicked = Str

    # The optional extended name of the trait to synchronize right double
    # clicked data with. The data is a TabularEditorEvent:
    right_dclicked = Str

    # The optional extended name of the trait to synchronize column
    # clicked data with. The data is a TabularEditorEvent:
    column_clicked = Str

    # The optional extended name of the trait to synchronize column
    # right clicked data with. The data is a TabularEditorEvent:
    column_right_clicked = Str

    # The optional extended name of the Event trait that should be used to
    # trigger a scroll-to command. The data is an integer giving the row.
    scroll_to_row = Str

    # Controls behavior of scroll to row
    scroll_to_row_hint = Enum("center", "top", "bottom", "visible")

    # Can the user edit the values?
    editable = Bool(True)

    # Can the user edit the labels (i.e. the first column)
    editable_labels = Bool(False)

    # Are multiple selected items allowed?
    multi_select = Bool(False)

    # Should horizontal lines be drawn between items?
    horizontal_lines = Bool(True)

    # Should vertical lines be drawn between items?
    vertical_lines = Bool(True)

    # Should the columns automatically resize? Don't allow this when the amount
    # of data is large.
    auto_resize = Bool(False)

    # Should the rows automatically resize (Qt4 only)? Don't allow
    # this when the amount of data is large.
    auto_resize_rows = Bool(False)

    # Whether to stretch the last column to fit the available space.
    stretch_last_section = Bool(True)

    # The adapter from trait values to editor values:
    adapter = Instance('traitsui.tabular_adapter.TabularAdapter', ())

    # What type of operations are allowed on the list:
    operations = List(Enum('delete', 'insert', 'append', 'edit', 'move'),
                      ['delete', 'insert', 'append', 'edit', 'move'])

    # Are 'drag_move' operations allowed (i.e. True), or should they always be
    # treated as 'drag_copy' operations (i.e. False):
    drag_move = Bool(False)

    # The set of images that can be used:
    images = List(Image)

    def _get_klass(self):
        """ Returns the toolkit-specific editor class to be instantiated.
        """
        return toolkit_object('tabular_editor:TabularEditor')
Exemple #19
0
class UItem(Item):
    """ An Item that has no label.
    """

    show_label = Bool(False)
Exemple #20
0
class SbpRelayView(HasTraits):
    """
  UDP Relay view- Class allows user to specify port, IP address, and message set
  to relay over UDP
  """
    running = Bool(False)
    configured = Bool(False)
    broadcasting = Bool(False)
    msg_enum = Enum('Observations', 'All')
    ip_ad = String(DEFAULT_UDP_ADDRESS)
    port = Int(DEFAULT_UDP_PORT)
    information = String(
        'UDP Streaming\n\nBroadcast SBP information received by'
        ' the console to other machines or processes over UDP. With the \'Observations\''
        ' radio button selected, the console will broadcast the necessary information'
        ' for a rover Piksi to acheive an RTK solution.'
        '\n\nThis can be used to stream observations to a remote Piksi through'
        ' aircraft telemetry via ground control software such as MAVProxy or'
        ' Mission Planner.')
    http_information = String(
        'Skylark - Experimental Piksi Networking\n\n'
        "Skylark is Swift Navigation's Internet service for connecting Piksi receivers without the use of a radio. To receive GPS observations from the closest nearby Piksi base station (within 5km), click Connect to Skylark.\n\n"
    )
    start = Button(label='Start', toggle=True, width=32)
    stop = Button(label='Stop', toggle=True, width=32)
    connected_rover = Bool(False)
    connect_rover = Button(label='Connect to Skylark', toggle=True, width=32)
    disconnect_rover = Button(label='Disconnect from Skylark',
                              toggle=True,
                              width=32)
    base_pragma = String()
    rover_pragma = String()
    base_device_uid = String()
    rover_device_uid = String()
    toggle = True
    view = View(
        VGroup(
            spring,
            HGroup(
                VGroup(
                    Item('running',
                         show_label=True,
                         style='readonly',
                         visible_when='running'),
                    Item('msg_enum',
                         label="Messages to broadcast",
                         style='custom',
                         enabled_when='not running'),
                    Item('ip_ad',
                         label='IP Address',
                         enabled_when='not running'),
                    Item('port', label="Port", enabled_when='not running'),
                    HGroup(
                        spring,
                        UItem('start',
                              enabled_when='not running',
                              show_label=False),
                        UItem('stop', enabled_when='running',
                              show_label=False), spring)),
                VGroup(
                    Item('information',
                         label="Notes",
                         height=10,
                         editor=MultilineTextEditor(
                             TextEditor(multi_line=True)),
                         style='readonly',
                         show_label=False,
                         resizable=True,
                         padding=15),
                    spring,
                ),
            ), spring,
            HGroup(
                VGroup(
                    HGroup(
                        spring,
                        UItem('connect_rover',
                              enabled_when='not connected_rover',
                              show_label=False),
                        UItem('disconnect_rover',
                              enabled_when='connected_rover',
                              show_label=False), spring),
                    HGroup(spring, Item('base_pragma', label='Base option '),
                           Item('base_device_uid', label='Base device '),
                           spring),
                    HGroup(spring, Item('rover_pragma', label='Rover option'),
                           Item('rover_device_uid', label='Rover device'),
                           spring),
                ),
                VGroup(
                    Item('http_information',
                         label="Notes",
                         height=10,
                         editor=MultilineTextEditor(
                             TextEditor(multi_line=True)),
                         style='readonly',
                         show_label=False,
                         resizable=True,
                         padding=15),
                    spring,
                ),
            ), spring))

    def __init__(self,
                 link,
                 device_uid=None,
                 base=DEFAULT_BASE,
                 whitelist=None):
        """
    Traits tab with UI for UDP broadcast of SBP.

    Parameters
    ----------
    link : sbp.client.handler.Handler
      Link for SBP transfer to/from Piksi.
    device_uid : str
      Piksi Device UUID (defaults to None)
    base : str
      HTTP endpoint
    whitelist : [int] | None
      Piksi Device UUID (defaults to None)

    """
        self.link = link
        self.http = HTTPDriver(None, base)
        self.net_link = None
        self.fwd = None
        self.func = None
        # Whitelist used for UDP broadcast view
        self.msgs = OBS_MSGS
        # register a callback when the msg_enum trait changes
        self.on_trait_change(self.update_msgs, 'msg_enum')
        # Whitelist used for Skylark broadcasting
        self.whitelist = whitelist
        self.device_uid = None
        self.python_console_cmds = {'update': self}

    def update_msgs(self):
        """Updates the instance variable msgs which store the msgs that we
    will send over UDP.

    """
        if self.msg_enum == 'Observations':
            self.msgs = OBS_MSGS
        elif self.msg_enum == 'All':
            self.msgs = [None]
        else:
            raise NotImplementedError

    def set_route(self, serial_id, channel=CHANNEL_UUID):
        """Sets serial_id hash for HTTP headers.

    Parameters
    ----------
    serial_id : int
      Piksi device ID
    channel : str
      UUID namespace for device UUID

    """
        device_uid = str(get_uuid(channel, serial_id))
        self.device_uid = device_uid
        if self.http:
            self.http.device_uid = device_uid

    def _prompt_networking_error(self, text):
        """Nonblocking prompt for a networking error.

    Parameters
    ----------
    text : str
      Helpful error message for the user

    """
        prompt = CallbackPrompt(title="Networking Error",
                                actions=[close_button])
        prompt.text = text
        prompt.run(block=False)

    def _prompt_setting_error(self, text):
        """Nonblocking prompt for a device setting error.

    Parameters
    ----------
    text : str
      Helpful error message for the user

    """
        prompt = CallbackPrompt(title="Setting Error", actions=[close_button])
        prompt.text = text
        prompt.run(block=False)

    def _retry_read(self):
        """Retry read connections. Intended to be called by
    _connect_rover_fired.

    """
        i = 0
        repeats = 5
        _rover_pragma = self.rover_pragma
        _rover_device_uid = self.rover_device_uid or self.device_uid
        while self.http and not self.http.connect_read(
                device_uid=_rover_device_uid, pragma=_rover_pragma):
            print "Attempting to read observation from Skylark..."
            time.sleep(0.1)
            i += 1
            if i >= repeats:
                msg = (
                    "\nUnable to receive observations from Skylark!\n\n"
                    "Please check that:\n"
                    " - you have a network connection\n"
                    " - your Piksi has a single-point position\n"
                    " - a Skylark-connected Piksi receiver \n   is nearby (within 5km)"
                )
                self._prompt_networking_error(msg)
                self.http.read_close()
                return
        print "Connected as a rover!"
        with Handler(Framer(self.http.read, self.http.write)) as net_link:
            self.net_link = net_link
            self.fwd = Forwarder(net_link, swriter(self.link))
            self.fwd.start()
            while True:
                time.sleep(1)
                if not net_link.is_alive():
                    sys.stderr.write(
                        "Network observation stream disconnected!")
                    break
        # Unless the user has initiated a reconnection, assume here that the rover
        # still wants to be connected, so if we break out of the handler loop,
        # cleanup rover connection and try reconnecting.
        if self.connected_rover:
            sys.stderr.write("Going for a networking reconnection!")
            self._disconnect_rover_fired()
            self._connect_rover_fired()

    def _connect_rover_fired(self):
        """Handle callback for HTTP rover connections.

    """
        if not self.device_uid:
            msg = "\nDevice ID not found!\n\nConnection requires a valid Piksi device ID."
            self._prompt_setting_error(msg)
            return
        if not self.http:
            self._prompt_networking_error("\nNetworking disabled!")
            return
        try:
            _base_pragma = self.base_pragma
            _base_device_uid = self.base_device_uid or self.device_uid
            if not self.http.connect_write(self.link,
                                           self.whitelist,
                                           device_uid=_base_device_uid,
                                           pragma=_base_pragma):
                msg = ("\nUnable to connect to Skylark!\n\n"
                       "Please check that you have a network connection.")
                self._prompt_networking_error(msg)
                self.http.close()
                self.connected_rover = False
                return
            self.connected_rover = True
            print "Connected as a base station!"
            executor = ThreadPoolExecutor(max_workers=2)
            executor.submit(self._retry_read)
        except:
            self.connected_rover = False
            import traceback
            print traceback.format_exc()

    def _disconnect_rover_fired(self):
        """Handle callback for HTTP rover disconnects.

    """
        if not self.device_uid:
            msg = "\nDevice ID not found!\n\nConnection requires a valid Piksi device ID."
            self._prompt_setting_error(msg)
            return
        if not self.http:
            self._prompt_networking_error("\nNetworking disabled!")
            return
        try:
            if self.connected_rover:
                self.http.close()
                self.connected_rover = False
                if self.fwd and self.net_link:
                    self.net_link.stop()
        except:
            self.connected_rover = False
            import traceback
            print traceback.format_exc()

    def _start_fired(self):
        """Handle start udp broadcast button. Registers callbacks on
    self.link for each of the self.msgs If self.msgs is None, it
    registers one generic callback for all messages.

    """
        self.running = True
        try:
            self.func = UdpLogger(self.ip_ad, self.port)
            self.link.add_callback(self.func, self.msgs)
        except:
            import traceback
            print traceback.format_exc()

    def _stop_fired(self):
        """Handle the stop udp broadcast button. It uses the self.funcs and
    self.msgs to remove the callbacks that were registered when the
    start button was pressed.

    """
        try:
            self.link.remove_callback(self.func, self.msgs)
            self.func.__exit__()
            self.func = None
            self.running = False
        except:
            import traceback
            print traceback.format_exc()
Exemple #21
0
class UReadonly(Readonly):
    """ An Item using a 'readonly' style with no label.
    """

    show_label = Bool(False)
class Node(Controller):
    """ Basic Node structure of the pipeline that need to be tuned.

    Attributes
    ----------
    name : str
        the node name
    full_name : str
        a unique name among all nodes and sub-nodes of the top level pipeline
    enabled : bool
        user parameter to control the node activation
    activated : bool
        parameter describing the node status

    Methods
    -------
    connect
    set_callback_on_plug
    get_plug_value
    set_plug_value
    get_trait
    """
    name = Str()
    enabled = Bool(default_value=True)
    activated = Bool(default_value=False)
    node_type = Enum(("processing_node", "view_node"))

    def __init__(self, pipeline, name, inputs, outputs):
        """ Generate a Node

        Parameters
        ----------
        pipeline: Pipeline (mandatory)
            the pipeline object where the node is added
        name: str (mandatory)
            the node name
        inputs: list of dict (mandatory)
            a list of input parameters containing a dictionary with default
            values (mandatory key: name)
        outputs: dict (mandatory)
            a list of output parameters containing a dictionary with default
            values (mandatory key: name)
        """
        super(Node, self).__init__()
        self.pipeline = weak_proxy(pipeline)
        self.name = name
        self.plugs = SortedDictionary()
        # _callbacks -> (src_plug_name, dest_node, dest_plug_name)
        self._callbacks = {}

        # generate a list with all the inputs and outputs
        # the second parameter (parameter_type) is False for an input,
        # True for an output
        parameters = list(zip(inputs, [
            False,
        ] * len(inputs)))
        parameters.extend(list(zip(outputs, [
            True,
        ] * len(outputs))))
        for parameter, parameter_type in parameters:
            # check if parameter is a dictionary as specified in the
            # docstring
            if isinstance(parameter, dict):
                # check if parameter contains a name item
                # as specified in the docstring
                if "name" not in parameter:
                    raise Exception(
                        "Can't create parameter with unknown"
                        "identifier and parameter {0}".format(parameter))
                parameter = parameter.copy()
                plug_name = parameter.pop("name")
                # force the parameter type
                parameter["output"] = parameter_type
                # generate plug with input parameter and identifier name
                plug = Plug(**parameter)
            else:
                raise Exception("Can't create Node. Expect a dict structure "
                                "to initialize the Node, "
                                "got {0}: {1}".format(type(parameter),
                                                      parameter))
            # update plugs list
            self.plugs[plug_name] = plug
            # add an event on plug to validate the pipeline
            plug.on_trait_change(pipeline.update_nodes_and_plugs_activation,
                                 "enabled")

        # add an event on the Node instance traits to validate the pipeline
        self.on_trait_change(pipeline.update_nodes_and_plugs_activation,
                             "enabled")

    @property
    def process(self):
        return get_ref(self._process)

    @process.setter
    def process(self, value):
        self._process = value

    @property
    def full_name(self):
        if self.pipeline.parent_pipeline:
            return self.pipeline.pipeline_node.full_name + '.' + self.name
        else:
            return self.name

    @staticmethod
    def _value_callback(self, source_plug_name, dest_node, dest_plug_name,
                        value):
        """ Spread the source plug value to the destination plug.
        """
        dest_node.set_plug_value(dest_plug_name, value)

    def _value_callback_with_logging(self, log_stream, prefix,
                                     source_plug_name, dest_node,
                                     dest_plug_name, value):
        """ Spread the source plug value to the destination plug, and log it in
        a stream for debugging.
        """
        #print '(debug) value changed:', self, self.name, source_plug_name, dest_node, dest_plug_name, repr(value), ', stream:', log_stream, prefix

        plug = self.plugs.get(source_plug_name, None)
        if plug is None:
            return

        def _link_name(dest_node, plug, prefix, dest_plug_name,
                       source_node_or_process):
            external = True
            sibling = False
            # check if it is an external link: if source is not a parent of dest
            if hasattr(source_node_or_process, 'process') \
                    and hasattr(source_node_or_process.process, 'nodes'):
                source_process = source_node_or_process
                source_node = source_node_or_process.process.pipeline_node
                children = [
                    x for k, x in source_node.process.nodes.items() if x != ''
                ]
                if dest_node in children:
                    external = False
            # check if it is a sibling node:
            # if external and source is not in dest
            if external:
                sibling = True
                #print >> open('/tmp/linklog.txt', 'a'), 'check sibling, prefix:', prefix, 'source:', source_node_or_process, ', dest_plug_name:', dest_plug_name, 'dest_node:', dest_node, dest_node.name
                if hasattr(dest_node, 'process') \
                        and hasattr(dest_node.process, 'nodes'):
                    children = [
                        x for k, x in dest_node.process.nodes.items()
                        if x != ''
                    ]
                    if source_node_or_process in children:
                        sibling = False
                    else:
                        children = [
                            x.process for x in children \
                            if hasattr(x, 'process')]
                    if source_node_or_process in children:
                        sibling = False
                #print 'sibling:', sibling
            if external:
                if sibling:
                    name = '.'.join(prefix.split('.')[:-2] \
                        + [dest_node.name, dest_plug_name])
                else:
                    name = '.'.join(prefix.split('.')[:-2] + [dest_plug_name])
            else:
                # internal connection in a (sub) pipeline
                name = prefix + dest_node.name
                if name != '' and not name.endswith('.'):
                    name += '.'
                name += dest_plug_name
            return name

        dest_plug = dest_node.plugs[dest_plug_name]
        #print >> open('/tmp/linklog.txt', 'a'), 'link_name:',  self, repr(self.name), ', prefix:', repr(prefix), ', source_plug_name:', source_plug_name, 'dest:', dest_plug, repr(dest_plug_name), 'dest node:', dest_node, repr(dest_node.name)
        print('value link:', \
            'from:', prefix + source_plug_name, \
            'to:', _link_name(dest_node, dest_plug, prefix, dest_plug_name,
                              self), \
            ', value:', repr(value), file=log_stream) #, 'self:', self, repr(self.name), ', prefix:',repr(prefix), ', source_plug_name:', source_plug_name, 'dest:', dest_plug, repr(dest_plug_name), 'dest node:', dest_node, repr(dest_node.name)
        log_stream.flush()

        # actually propagate
        dest_node.set_plug_value(dest_plug_name, value)

    def connect(self, source_plug_name, dest_node, dest_plug_name):
        """ Connect linked plugs of two nodes

        Parameters
        ----------
        source_plug_name: str (mandatory)
            the source plug name
        dest_node: Node (mandatory)
            the destination node
        dest_plug_name: str (mandatory)
            the destination plug name
        """
        # add a callback to spread the source plug value
        value_callback = SomaPartial(self.__class__._value_callback,
                                     weak_proxy(self), source_plug_name,
                                     weak_proxy(dest_node), dest_plug_name)
        self._callbacks[(source_plug_name, dest_node,
                         dest_plug_name)] = value_callback
        self.set_callback_on_plug(source_plug_name, value_callback)

    def disconnect(self, source_plug_name, dest_node, dest_plug_name):
        """ disconnect linked plugs of two nodes

        Parameters
        ----------
        source_plug_name: str (mandatory)
            the source plug name
        dest_node: Node (mandatory)
            the destination node
        dest_plug_name: str (mandatory)
            the destination plug name
        """
        # remove the callback to spread the source plug value
        callback = self._callbacks.pop(
            (source_plug_name, dest_node, dest_plug_name))
        self.remove_callback_from_plug(source_plug_name, callback)

    def __getstate__(self):
        """ Remove the callbacks from the default __getstate__ result because
        they prevent Node instance from being used with pickle.
        """
        state = super(Node, self).__getstate__()
        state['_callbacks'] = state['_callbacks'].keys()
        state['pipeline'] = get_ref(state['pipeline'])
        return state

    def __setstate__(self, state):
        """ Restore the callbacks that have been removed by __getstate__.
        """
        state['_callbacks'] = dict((i, SomaPartial(self._value_callback, *i))
                                   for i in state['_callbacks'])
        if state['pipeline'] is state['process']:
            state['pipeline'] = state['process'] = weak_proxy(
                state['pipeline'])
        else:
            state['pipeline'] = weak_proxy(state['pipeline'])
        super(Node, self).__setstate__(state)
        for callback_key, value_callback in six.iteritems(self._callbacks):
            self.set_callback_on_plug(callback_key[0], value_callback)

    def set_callback_on_plug(self, plug_name, callback):
        """ Add an event when a plug change

        Parameters
        ----------
        plug_name: str (mandatory)
            a plug name
        callback: @f (mandatory)
            a callback function
        """
        self.on_trait_change(callback, plug_name)

    def remove_callback_from_plug(self, plug_name, callback):
        """ Remove an event when a plug change

        Parameters
        ----------
        plug_name: str (mandatory)
            a plug name
        callback: @f (mandatory)
            a callback function
        """
        self.on_trait_change(callback, plug_name, remove=True)

    def get_plug_value(self, plug_name):
        """ Return the plug value

        Parameters
        ----------
        plug_name: str (mandatory)
            a plug name

        Returns
        -------
        output: object
            the plug value
        """
        return getattr(self, plug_name)

    def set_plug_value(self, plug_name, value):
        """ Set the plug value

        Parameters
        ----------
        plug_name: str (mandatory)
            a plug name
        value: object (mandatory)
            the plug value we want to set
        """
        setattr(self, plug_name, value)

    def get_trait(self, trait_name):
        """ Return the desired trait

        Parameters
        ----------
        trait_name: str (mandatory)
            a trait name

        Returns
        -------
        output: trait
            the trait named trait_name
        """
        return self.trait(trait_name)
Exemple #23
0
class Editor(HasPrivateTraits):
    """ Represents an editing control for an object trait in a Traits-based
        user interface.
    """

    #: The UI (user interface) this editor is part of:
    ui = Instance("traitsui.ui.UI", clean_up=True)

    #: 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, clean_up=True)

    #: 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(clean_up=True)

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

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

    #: The GUI widget defined by this editor:
    control = Any(clean_up=True)

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

    #: 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 = Instance(EditorFactory, clean_up=True)

    #: 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()

    #: Function to use for string formatting
    format_func = Callable()

    #: Format string to use for formatting (used if **format_func** is not set)
    format_str = Str()

    #: The extended trait name of the trait containing editor invalid state
    #: status:
    invalid_trait_name = Str()

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

    # -- private trait definitions ------------------------------------------

    #: A set to track values being updated to prevent infinite recursion.
    _no_trait_update = Set(Str)

    #: A list of all values synchronized to.
    _user_to = List(Tuple(Any, Str, Callable))

    #: A list of all values synchronized from.
    _user_from = List(Tuple(Str, Callable))

    # ------------------------------------------------------------------------
    # Editor interface
    # ------------------------------------------------------------------------

    # -- Abstract methods ---------------------------------------------------

    def init(self, parent):
        """ Create and initialize the underlying toolkit widget.

        This method must be overriden by subclasses.  Implementations must
        ensure that the :attr:`control` trait is set to an appropriate
        toolkit object.

        Parameters
        ----------
        parent : toolkit control
            The parent toolkit object of the editor's toolkit objects.
        """
        raise NotImplementedError("This method must be overriden.")

    def update_editor(self):
        """ Updates the editor when the value changes externally to the editor.

        This should normally be overridden in a subclass.
        """
        pass

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

        This should normally be overridden in a subclass.

        Parameters
        ----------
        excp : Exception
            The exception which occurred.
        """
        pass

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

        This method must be overriden by subclasses.
        """
        raise NotImplementedError("This method must be overriden.")

    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, then this method
        calls that function to do the formatting.  If the **format_str**
        attribute is set on the editor, then this method uses that string for
        formatting. If neither attribute is set, then this method just calls
        the appropriate text type to format.

        Sub-classes may choose to override the default implementation.

        Parameters
        ----------
        value : any
            The value being edited.
        format_func : callable or None
            A function that takes a value and returns a string.
        """
        if self.format_func is not None:
            return self.format_func(value)

        if self.format_str != "":
            return self.format_str % value

        if format_func is not None:
            return format_func(value)

        return str(value)

    def restore_prefs(self, prefs):
        """ Restores saved user preference information for the editor.

        Editors with state may choose to override this. It will only be used
        if the editor has an `id` value.

        Parameters
        ----------
        prefs : dict
            A dictionary of preference values.
        """
        pass

    def save_prefs(self):
        """ Returns any user preference information for the editor.

        Editors with state may choose to override this. It will only be used
        if the editor has an `id` value.

        Returns
        -------
        prefs : dict or None
            A dictionary of preference values, or None if no preferences to
            be saved.
        """
        return None

    # -- Editor life-cycle methods ------------------------------------------

    def prepare(self, parent):
        """ Finish setting up the editor.

        Parameters
        ----------
        parent : toolkit control
            The parent toolkit object of the editor's toolkit objects.
        """
        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()

    def dispose(self):
        """ Disposes of the contents of an editor.

        This disconnects any synchronised values and resets references
        to other objects.

        Subclasses may chose to override this method to perform additional
        clean-up.
        """
        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)

        for name, handler in self._user_from:
            self.on_trait_change(handler, name, remove=True)

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

        # Break linkages to references we no longer need:
        for name in self.trait_names(clean_up=True):
            setattr(self, name, None)

    # -- Undo/redo methods --------------------------------------------------

    def log_change(self, undo_factory, *undo_args):
        """ Logs a change made in the editor with undo/redo history.

        Parameters
        ----------
        undo_factory : callable
            Callable that creates an undo item.  Often self.get_undo_item.
        *undo_args
            Any arguments to pass to the undo factory.
        """
        ui = self.ui

        # 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)

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

        Can be overridden in a subclass for special value types.

        Parameters
        ----------
        object : HasTraits instance
            The object being modified.
        name : str
            The name of the trait that is to be changed.
        old_value : any
            The original value of the trait.
        new_value : any
            The new value of the trait.
        """
        return UndoItem(object=object,
                        name=name,
                        old_value=old_value,
                        new_value=new_value)

    # -- Trait synchronization code -----------------------------------------

    def sync_value(
        self,
        user_name,
        editor_name,
        mode="both",
        is_list=False,
        is_event=False,
    ):
        """ Synchronize an editor trait and a user object trait.

        Also sets the initial value of the editor trait from the
        user object trait (for modes 'from' and 'both'), and the initial
        value of the user object trait from the editor trait (for mode
        'to'), as long as the relevant traits are not events.

        Parameters
        ----------
        user_name : str
            The name of the trait to be used on the user object. If empty, no
            synchronization will be set up.
        editor_name : str
            The name of the relevant editor trait.
        mode : str, optional; one of 'to', 'from' or 'both'
            The direction of synchronization. 'from' means that trait changes
            in the user object should be propagated to the editor. 'to' means
            that trait changes in the editor should be propagated to the user
            object. 'both' means changes should be propagated in both
            directions. The default is 'both'.
        is_list : bool, optional
            If true, synchronization for item events will be set up in
            addition to the synchronization for the object itself.
            The default is False.
        is_event : bool, optional
            If true, this method won't attempt to initialize the user
            object or editor trait values. The default is False.
        """
        if user_name == "":
            return

        key = "%s:%s" % (user_name, editor_name)

        parts = user_name.split(".")
        if len(parts) == 1:
            user_object = self.context_object
            xuser_name = user_name
        else:
            user_object = self.ui.context[parts[0]]
            xuser_name = ".".join(parts[1:])
            user_name = parts[-1]

        if mode in {"from", "both"}:
            self._bind_from(key, user_object, xuser_name, editor_name, is_list)

            if not is_event:
                # initialize editor value from user value
                with self.raise_to_debug():
                    user_value = xgetattr(user_object, xuser_name)
                    setattr(self, editor_name, user_value)

        if mode in {"to", "both"}:
            self._bind_to(key, user_object, xuser_name, editor_name, is_list)

            if mode == "to" and not is_event:
                # initialize user value from editor value
                with self.raise_to_debug():
                    editor_value = xgetattr(self, editor_name)
                    xsetattr(user_object, xuser_name, editor_value)

    # -- Utility methods -----------------------------------------------------

    def parse_extended_name(self, name):
        """ Extract the object, name and a getter from an extended name

        Parameters
        ----------
        name : str
            The extended name to parse.

        Returns
        -------
        object, name, getter : any, str, callable
            The object from the context, the (extended) name of the
            attributes holding the value, and a callable which gets the
            current value from the context.
        """
        base_name, __, name = name.partition(".")
        if name:
            object = self.ui.context[base_name]
        else:
            name = base_name
            object = self.context_object

        return (object, name, partial(xgetattr, object, name))

    # -- Utility context managers --------------------------------------------

    @contextmanager
    def no_trait_update(self, name):
        """ Context manager that blocks updates from the named trait. """
        if name in self._no_trait_update:
            yield
            return

        self._no_trait_update.add(name)
        try:
            yield
        finally:
            self._no_trait_update.remove(name)

    @contextmanager
    def raise_to_debug(self):
        """ Context manager that uses raise to debug to raise exceptions. """
        try:
            yield
        except Exception:
            from traitsui.api import raise_to_debug

            raise_to_debug()

    @contextmanager
    def updating_value(self):
        """ Context manager to handle updating value. """
        if self.updating:
            yield
            return

        self.updating = True
        try:
            yield
        finally:
            self.updating = False

    # ------------------------------------------------------------------------
    # object interface
    # ------------------------------------------------------------------------

    def __init__(self, parent, **traits):
        """ Initializes the editor object.
        """
        super(HasPrivateTraits, self).__init__(**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.invalid_trait_name, "invalid", "from")

    # ------------------------------------------------------------------------
    # private methods
    # ------------------------------------------------------------------------

    def _update_editor(self, object, name, old_value, new_value):
        """ Performs updates when the object trait changes.

        This is designed to be used as a trait listener.
        """
        # 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 = self.ui.get_extended_value(self.object_name)

        # 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 the Item is not readonly
        # or it is not for an event):
        if (self.item.style != "readonly"
                and object.base_trait(name).type != "event"):
            # Indicate that the contents of the UI have been changed:
            self.ui.modified = True

            if self.updating:
                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.updating:
            # Update the editor control to reflect the current object state:
            self.update_editor()

    def _sync_values(self):
        """ Initialize and synchronize editor and factory traits

        Initializes and synchronizes (as needed) editor traits with the
        value of corresponding factory traits.  The name of the factory
        trait and the editor trait must match and the factory trait needs
        to have ``sync_value`` metadata set.  The strategy followed is:

        - for each factory trait with ``sync_value`` metadata:

          1.  if the value is a :class:`ContextValue` instance then
              call :meth:`sync_value` with the ``name`` from the
              context value.
          2.  if the trait has ``sync_name`` metadata, look at the
              referenced trait value and if it is a non-empty string
              then use this value as the name of the value in the
              context.
          3.  otherwise initialize the current value of the factory
              trait to the corresponding value of the editor.

        - synchronization mode in cases 1 and 2 is taken from the
          ``sync_value`` metadata of the editor trait first and then
          the ``sync_value`` metadata of the factory trait if that is
          empty.

        - if the value is a container type, then the `is_list` metadata
          is set to
        """
        factory = self.factory
        for name, trait in factory.traits(sync_value=not_none).items():
            value = getattr(factory, name)
            self_trait = self.trait(name)
            if self_trait.sync_value:
                mode = self_trait.sync_value
            else:
                mode = trait.sync_value
            if isinstance(value, ContextValue):
                self.sync_value(
                    value.name,
                    name,
                    mode,
                    bool(self_trait.is_list),
                    self_trait.type == "event",
                )
            elif (trait.sync_name is not None
                  and getattr(factory, trait.sync_name, "") != ""):
                # Note: this is implemented as a stepping stone from things
                # like ``low_name`` and ``high_name`` to using context values.
                sync_name = getattr(factory, trait.sync_name)
                self.sync_value(
                    sync_name,
                    name,
                    mode,
                    bool(self_trait.is_list),
                    self_trait.type == "event",
                )
            elif value is not Undefined:
                setattr(self, name, value)

    def _bind_from(self, key, user_object, xuser_name, editor_name, is_list):
        """ Bind trait change handlers from a user object to the editor.

        Parameters
        ----------
        key : str
            The key to use to guard against recursive updates.
        user_object : object
            The object in the TraitsUI context that is being bound.
        xuser_name: : str
            The extended name of the trait to be used on the user object.
        editor_name : str
            The name of the relevant editor trait.
        is_list : bool, optional
            If true, synchronization for item events will be set up in
            addition to the synchronization for the object itself.
            The default is False.
        """
        def user_trait_modified(new):
            if key not in self._no_trait_update:
                with self.no_trait_update(key), self.raise_to_debug():
                    xsetattr(self, editor_name, new)

        user_object.on_trait_change(user_trait_modified, xuser_name)
        self._user_to.append((user_object, xuser_name, user_trait_modified))

        if is_list:

            def user_list_modified(event):
                if (isinstance(event, TraitListEvent)
                        and key not in self._no_trait_update):
                    with self.no_trait_update(key), self.raise_to_debug():
                        n = event.index
                        getattr(self,
                                editor_name)[n:n +
                                             len(event.removed)] = event.added

            items = xuser_name + "_items"
            user_object.on_trait_change(user_list_modified, items)
            self._user_to.append((user_object, items, user_list_modified))

    def _bind_to(self, key, user_object, xuser_name, editor_name, is_list):
        """ Bind trait change handlers from a user object to the editor.

        Parameters
        ----------
        key : str
            The key to use to guard against recursive updates.
        user_object : object
            The object in the TraitsUI context that is being bound.
        xuser_name: : str
            The extended name of the trait to be used on the user object.
        editor_name : str
            The name of the relevant editor trait.
        is_list : bool, optional
            If true, synchronization for item events will be set up in
            addition to the synchronization for the object itself.
            The default is False.
        """
        def editor_trait_modified(new):
            if key not in self._no_trait_update:
                with self.no_trait_update(key), self.raise_to_debug():
                    xsetattr(user_object, xuser_name, new)

        self.on_trait_change(editor_trait_modified, editor_name)

        self._user_from.append((editor_name, editor_trait_modified))

        if is_list:

            def editor_list_modified(event):
                if key not in self._no_trait_update:
                    with self.no_trait_update(key), self.raise_to_debug():
                        n = event.index
                        value = xgetattr(user_object, xuser_name)
                        value[n:n + len(event.removed)] = event.added

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

    def __set_value(self, value):
        """ Set the value of the trait the editor is editing.

        This calls the appropriate setattr method on the handler to perform
        the actual change.
        """
        with self.updating_value():
            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

    def _str(self, value):
        """ Returns the text representation of a specified value.

        This is a convenience method to cover the differences between Python
        2 and Python 3 strings.

        Parameters
        ----------
        value : any
            The value to be represented as a string.

        Returns
        -------
        string : unicode
            The string of the value, as an appropriate text type for Python 2
            or 3.
        """
        # In Unicode!
        return str(value)

    # -- Traits property getters and setters --------------------------------

    @cached_property
    def _get_context_object(self):
        """ Returns the context object the editor is using

        In some cases a proxy object is edited rather than an object directly
        in the context, in which case we return ``self.object``.
        """
        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

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

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

    def _get_value(self):
        """ Returns the value of the trait the editor is editing.
        """
        return getattr(self.object, self.name, Undefined)

    def _set_value(self, value):
        """ Set the value of the trait the editor is editing.

        Dispatches via the TraitsUI Undo/Redo mechanisms to make change
        reversible, if desired.
        """
        if self.ui and self.name != "None":
            self.ui.do_undoable(self.__set_value, value)

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

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

    ipw = Instance(tvtk.ImagePlaneWidget, allow_none=False, record=True)

    use_lookup_table = Bool(
        True, help='Use a lookup table to map input scalars to colors')

    input_info = PipelineInfo(datasets=['image_data'],
                              attribute_types=['any'],
                              attributes=['scalars'])

    view = View(Group(Item(name='ipw', style='custom', resizable=True),
                      show_labels=False),
                width=600,
                height=600,
                resizable=True,
                scrollable=True)

    ######################################################################
    # `Module` interface
    ######################################################################
    def setup_pipeline(self):
        """Override this method so that it *creates* the tvtk
        pipeline.

        This method is invoked when the object is initialized via
        `__init__`.  Note that at the time this method is called, the
        tvtk data pipeline will *not* yet be setup.  So upstream data
        will not be available.  The idea is that you simply create the
        basic objects and setup those parts of the pipeline not
        dependent on upstream sources and filters.  You should also
        set the `actors` attribute up at this point.
        """
        # Create the various objects for this module.
        self.ipw = tvtk.ImagePlaneWidget(display_text=1,
                                         key_press_activation=0,
                                         left_button_action=1,
                                         middle_button_action=0,
                                         user_controlled_lookup_table=True)
        self.setup_lut()

    def update_pipeline(self):
        """Override this method so that it *updates* the tvtk pipeline
        when data upstream is known to have changed.

        This method is invoked (automatically) when any of the inputs
        sends a `pipeline_changed` event.
        """
        mod_mgr = self.module_manager
        if mod_mgr is None:
            return

        # Data is available, so set the input for the IPW.
        input = mod_mgr.source.outputs[0]
        dataset = mod_mgr.source.get_output_dataset()
        if not (dataset.is_a('vtkStructuredPoints') \
                or dataset.is_a('vtkImageData')):
            msg = 'ImagePlaneWidget only supports structured points or '\
                  'image data.'
            error(msg)
            raise TypeError(msg)

        self.configure_input(self.ipw, input)
        self.setup_lut()

    def update_data(self):
        """Override this method so that it flushes the vtk pipeline if
        that is necessary.

        This method is invoked (automatically) when any of the inputs
        sends a `data_changed` event.
        """
        # Just set data_changed, the component should do the rest.
        self.data_changed = True

    @on_trait_change('use_lookup_table')
    def setup_lut(self):
        # Set the LUT for the IPW.
        if self.use_lookup_table:
            if self.module_manager is not None:
                self.ipw.lookup_table = \
                                self.module_manager.scalar_lut_manager.lut
        else:
            self.ipw.color_map.lookup_table = None
        self.render()

    ######################################################################
    # Non-public methods.
    ######################################################################
    def _ipw_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self.render, remove=True)
            self.widgets.remove(old)
        new.on_trait_change(self.render)
        self.widgets.append(new)
        if old is not None:
            self.update_pipeline()
        self.pipeline_changed = True
Exemple #25
0
class Visualization(HasTraits):

    # UI definition    
    scene = Instance(MlabSceneModel, ())

    #################################################
    #
    # Widget for Log messages
    #
    #################################################

    lastlog_string = Str
    lastlog_widget = Item('lastlog_string', show_label = False, style = 'readonly')

    #################################################
    #
    # Widget for handling Resources
    #
    #################################################

    # resource table_editor
    selected_resource = Instance(TraitResource)
    resources_table_editor = TableEditor(
        columns = [ObjectColumn(name = 'resource_name', width = 1)],
        deletable = False,
        editable = False,
        sort_model  = True,
        auto_size   = False,
        orientation = 'vertical',
        selected = 'selected_resource',
        row_factory = TraitResource)

    # Resources list
    resources_list = List(TraitResource, selection_mode = "rows")
    resources_list_widget = Item('resources_list', show_label = False, editor = resources_table_editor, padding = 10),

    # Raise/Lower Button
    resources_button = Button(label="Raise/Lower resource to level:")      
    resources_button_widget = Item('resources_button', show_label=False)

    # Raise/Lower level selector
    resources_level_int = Int
    resources_level_int_widget = Item('resources_level_int', show_label=False)

    # Raise/Lower group
    resources_raiselower_hgroup = HGroup(resources_button_widget, resources_level_int_widget)

    # Group for all the data properties widget
    rpg = VGroup(resources_list_widget, resources_raiselower_hgroup, label="Resources:", show_border=True)

    #################################################
    #
    # Widget for handling Classes
    #
    #################################################

    # class table_editor
    selected_class = Instance(TraitClass)
    classes_table_editor = TableEditor(
        columns = [ObjectColumn(name='class_name', width = 1)],
        deletable = False,
        editable = False,
        sort_model = True,
        auto_size = False,
        orientation = 'vertical',
        selected = 'selected_class',
        row_factory = TraitClass)

    # new classes widget
    classes_list = List(TraitClass)
    classes_list_widget = Item('classes_list', show_label = False, editor = classes_table_editor, padding = 10),

    # classes button
    classes_button = Button(label="Raise/Lower class to level:")
    classes_button_widget = Item('classes_button', show_label=False)

    # Raise/Lower level selector
    classes_level_int = Int
    classes_level_int_widget = Item('classes_level_int', show_label=False)

    # Raise/Lower group
    classes_raiselower_hgroup = HGroup(classes_button_widget, classes_level_int_widget)

    # Group for all the data properties widget
    cpg = VGroup(classes_list_widget, classes_raiselower_hgroup, label="Classes:", show_border=True)

    #################################################
    #
    # Widget for handling DataProperties
    #
    #################################################

    # dataproperty table_editor
    selected_dp = Instance(TraitDataProperty)
    dataproperty_table_editor = TableEditor(
        columns = [ObjectColumn(name = 'dp_name', width = 1, label = "DataProperty"), 
                   ObjectColumn(name = 'dp_domain', width = 1, label = "Domain"), 
                   ObjectColumn(name = 'dp_range', width = 1, label = "Range"),
                   ObjectColumn(name = 'dp_label', width = 1, label = "Label"), 
                   ObjectColumn(name = 'dp_comment', width = 1, label = "Comment")],
        deletable = False,
        editable = False,
        sort_model  = True,
        auto_size   = False,
        orientation = 'vertical',
        selected = 'selected_dp',
        row_factory = TraitDataProperty)

    # new dataproperties widget
    dataproperties_list = List(TraitDataProperty)
    dataproperties_list_widget = Item('dataproperties_list', show_label = False, editor = dataproperty_table_editor, padding = 10),

    # Raise/Lower Button
    dataproperties_button = Button(label="Raise/Lower Datatype Property to level:")      
    dataproperties_button_widget = Item('dataproperties_button', show_label=False)

    # Raise/Lower level selector
    dataproperties_level_int = Int
    dataproperties_level_int_widget = Item('dataproperties_level_int', show_label=False)

    # Raise/Lower group
    dataproperties_raiselower_hgroup = HGroup(dataproperties_button_widget, dataproperties_level_int_widget)

    # Group for all the data properties widget
    dpg = VGroup(dataproperties_list_widget, dataproperties_raiselower_hgroup, label="Data Properties:", show_border=True)
    
    #################################################
    #
    # Widget for handling Objectproperties
    #
    #################################################

    # objectproperty table_editor
    selected_op = Instance(TraitObjectProperty)
    objectproperty_table_editor = TableEditor(
        columns = [ObjectColumn(name = 'op_name', width = 1, label = "Objectproperty"), 
                   ObjectColumn(name = 'op_domain', width = 1, label = "Domain"), 
                   ObjectColumn(name = 'op_range', width = 1, label = "Range"),
                   ObjectColumn(name = 'op_label', width = 1, label = "Label"), 
                   ObjectColumn(name = 'op_comment', width = 1, label = "Comment")],
        deletable = False,
        editable = False,
        sort_model  = True,
        auto_size   = False,
        orientation = 'vertical',
        selected = 'selected_op',
        row_factory = TraitObjectProperty)

    # new objectproperties widget
    objectproperties_list = List(TraitObjectProperty)
    objectproperties_list_widget = Item('objectproperties_list', show_label = False, editor = objectproperty_table_editor, padding = 10),

    # Raise/Lower Button
    objectproperties_button = Button(label="Raise/Lower Object Property to level:")      
    objectproperties_button_widget = Item('objectproperties_button', show_label=False)

    # Hide Button
    objectproperties_hide_button = Button(label="Hide/Show Object Property")      
    objectproperties_hide_button_widget = Item('objectproperties_hide_button', show_label=False)

    # Raise/Lower level selector
    objectproperties_level_int = Int
    objectproperties_level_int_widget = Item('objectproperties_level_int', show_label=False)

    # Raise/Lower group
    objectproperties_raiselower_hgroup = HGroup(objectproperties_hide_button_widget, objectproperties_button_widget, objectproperties_level_int_widget)

    # Group for all the object properties widgets
    opg = VGroup(objectproperties_list_widget, objectproperties_raiselower_hgroup, label="Object Properties:", show_border=True)

    #################################################
    #
    # Widget for handling custom queries
    #
    #################################################
    
    # query prefixes widgets
    query_rdf_prefix = Bool(True, label="RDF Prefix")
    query_owl_prefix = Bool(True, label="OWL Prefix")
    query_rdfs_prefix = Bool(True, label="RDFS Prefix")
    query_ns_prefix = Bool(True, label="NS Prefix")
    
    # multilevel
    query_multilevel = Bool(False, label="Multilevel Query?")
    query_reification = Bool(False, label="Reification Query?")

    # query widgets
    query_string = Str
    query_entry_widget = Item('query_string', show_label=False)

    # query button
    query_button = Button(label="SPARQL Query") 
    query_button_widget = Item('query_button', show_label=False)

    # Raise/Lower level selector
    query_level_int = Int
    query_level_int_widget = Item('query_level_int', show_label=False)

    # Prefixes Group
    query_prefixes_group = VGroup('query_rdf_prefix', 'query_rdfs_prefix', 'query_owl_prefix', 'query_ns_prefix', 'query_multilevel', 'query_reification')

    # Raise/Lower group
    query_raiselower_hgroup = HGroup(query_prefixes_group, query_button_widget, query_level_int_widget)

    # group for all the query widgets
    qpg = VGroup(query_entry_widget, query_raiselower_hgroup, label="SPARQL Query", show_border=True)

    #################################################
    #
    # Widget for handling planes
    #
    #################################################

    # query button
    plane_button = Button(label="Merge plane A on plane B") 
    plane_button_widget = Item('plane_button', show_label=False)

    # Raise/Lower level selector
    plane_level_int1 = Int
    plane_level_int1_widget = Item('plane_level_int1', show_label=False)
    plane_level_int2 = Int
    plane_level_int2_widget = Item('plane_level_int2', show_label=False)

    # Raise/Lower group
    plane_merge_hgroup = HGroup(plane_level_int1_widget, plane_button_widget, plane_level_int2_widget)

    # group for all the query widgets
    ppg = VGroup(plane_merge_hgroup, label="Planes", show_border=True)


    #################################################
    #
    # Widget for stats
    #
    #################################################

    # stats string
    stats_string = Str
    stats_string_widget = Item('stats_string', show_label=False, style="readonly")    

    # group for all the query widgets
    spg = VGroup(stats_string_widget, label="Info", show_border=True)


    #################################################
    #
    # Widget for exporting png
    #
    #################################################

    # export widgets
    export_button = Button(label="Export as PNG image") 
    export_button_widget = Item('export_button', show_label=False)


    #################################################
    #
    # Widget for resetting the planes
    #
    #################################################

    # refresh widgets
    reset = Button(label="Reset placement")
    reset_w = Item('reset', show_label=False)

    #################################################
    #
    # Widget for refreshing the view
    #
    #################################################

    # refresh widgets
    refresh = Button(label="Refresh")
    refresh_w = Item('refresh', show_label=False)
   
    # widgets
    view = View(VGroup(HGroup(VGroup(Tabbed(rpg, cpg, dpg, opg, qpg, ppg, spg),
                                     export_button_widget,
                                     reset_w,
                                     refresh_w), 
                              Item('scene', editor=SceneEditor(scene_class=MayaviScene), height=640, width=800, show_label=False))), 
                lastlog_widget,
                scrollable=True)
   
    # constructor
    def __init__(self, kp):

        """Initializer of the UI class"""

        ###################################################
        #
        # Initialize the scene
        #
        ###################################################

        # super class initializer
        HasTraits.__init__(self)

        # Drawer instance
        self.drawer = Drawer(self.scene)

        ###################################################
        #
        # Retrieve Data
        #
        ###################################################

        # store the kp
        self.kp = kp
        if self.kp.owl_files:
            self.kp.load_owl()
        elif self.kp.n3_files:
            self.kp.load_n3()
        elif self.kp.blazehost:
            self.kp.get_everything_blaze()
        else:
            self.kp.get_everything()
        
        ###################################################
        #
        # Fill the side lists
        #
        ###################################################
        
        # get data properties
        dps = self.kp.get_data_properties()
        for dp in dps:
            self.dataproperties_list.append(TraitDataProperty(dp_name = str(dp[0]), dp_domain = str(dp[1]), dp_range = str(dp[2])))

        # get object properties
        ops = self.kp.get_object_properties()    
        for op in ops:
            self.objectproperties_list.append(TraitObjectProperty(op_name = str(op[0]), op_domain = str(op[1]), op_range = str(op[2])))

        # get instances
        for res in self.kp.get_instances():
            self.resources_list.append(TraitResource(resource_name = str(res[0])))

        # get classes
        cs = self.kp.get_classes()
        for c in cs:
            self.classes_list.append(TraitClass(class_name = str(c)))

        ###################################################
        #
        # Draw
        #
        ###################################################

        # initialize data structures
        self.res_list = ResourceList()
        self.planes = []
        self.active_labels = []

        # get and analyze knowledge
        self.data_classifier()
        self.calculate_placement()
        self.draw()


    @on_trait_change('scene.activated')
    def scene_ready(self):

        # get the figure and bind the picker
        self.figure = self.scene.mayavi_scene
        self.figure.on_mouse_pick(self.picker_callback)


    def picker_callback(self, picker):
            
        # find the resource related to the clicked object
        # ..if any

        # NOTE: this code is absolutely raw. Needs to be
        # optimized and many tricks can be used to speed
        # up the execution even with large datasets.

        for resource in self.res_list.list.keys():

            # cycle over the resources
            r = self.res_list.list[resource]      
            if picker.actor in r.gitem.actor.actors:
                logging.debug("Received click on %s" % r.name)
                self.lastlog_string = r.name
                
                if r.isStatement:
                    print r.name
                    s,p,o = self.kp.get_statement_els(r.name)
                    self.lastlog_string = "Selected statement (%s,%s,%s)" % (s,p,o)
                    
                else:
                    
                    # remove active labels
                    for label in self.active_labels:                    
                        label.remove()
                    self.active_labels = []

                    # draw the new labels
                    for active_label in self.drawer.draw_text(r):
                        self.active_labels.append(active_label)

                    # fill the side panel
                    self.stats_string = r.get_info()

                break

            else:                

                # cycle over the data properties
                for dp in r.data_properties:
                    if picker.actor in dp.gitem_predicate.actor.actors:
                        logging.debug("Received click on %s" % dp.dproperty)
                        self.lastlog_string = dp.dproperty
                        break
                        
                # cycle over the object properties
                for op in r.object_properties:
                    if picker.actor in op.gitem.actor.actors:
                        logging.debug("Received click on %s" % op.oproperty)
                        self.lastlog_string = op.oproperty
                        break


    def _plane_button_fired(self):

        """This method is used to merge a plane with the plane 0"""

        # debug print
        logging.debug("Merge plane function called")
        self.lastlog_string = "Merge plane function called"

        # get the plane to merge
        l1 = self.plane_level_int1    
        l2 = self.plane_level_int2   

        # get items on that plane
        uri_list = self.res_list.find_by_layer(l1)
        
        # move items on plane 0
        self.redraw(uri_list, l2)


    def _export_button_fired(self):
        
        """This method is used to export the scene to a PNG image"""
        
        # debug print
        logging.debug("Exporting the current scene to a PNG image")
        self.lastlog_string = "Exporting the current scene to a PNG image"

        # # export
        # self.scene.save("/tmp/output.png")


    def _reset_fired(self):
        
        """This method is used to reset the view to plane 0"""
        
        # debug print
        logging.debug("Resetting the view to plane 0")
        self.lastlog_string = "Resetting the view to plane 0"

        # reset
        self.redraw(None, 0)


    def _query_button_fired(self):

        """This method is executed when the query button is pressed"""    

        # debug print
        logging.debug("QUERY button pressed")
        self.lastlog_string = "QUERY button pressed"

        # get required prefixes
        prefixes = ""
        if self.query_rdf_prefix:
            prefixes += RDF_PREFIX
        if self.query_rdfs_prefix:
            prefixes += RDFS_PREFIX
        if self.query_owl_prefix:
            prefixes += OWL_PREFIX
        if self.query_ns_prefix:
            prefixes += NS_PREFIX

        # multilevel
        multilevel = self.query_multilevel

        if not multilevel:

            # read the required value
            l = self.query_level_int    
     
            # retrieve URI related to the query
            # execute the sparql query
            uri_list = []
            if self.query_reification:
                uri_list = self.kp.custom_query(q_reification)
            else:
                if len(self.query_string) > 0:
                    uri_list = self.kp.custom_query(prefixes + self.query_string)

            # move objects!
            self.redraw(uri_list, l)

        else:

            # retrieve URI related to the query
            # execute the sparql query
            uri_list = []
            if self.query_reification:
                print "here"
                uri_list = self.kp.custom_multilevel_query(q_reification)
                print "here"
                print uri_list
            else:
                if len(self.query_string) > 0:
                    uri_list = self.kp.custom_multilevel_query(prefixes + self.query_string)
                
            # move objects!
            level_counter = 0
            for level in uri_list:

                level_counter += 1
                self.redraw(level, level_counter)                


    def calculate_res_coords(self, num_items, z):

        """This function calculate the best placement for
        num_items items"""

        # initialize coords
        coords = []
        
        # divide 360 by the number of points to get the base angle
        if num_items > 0:
            multiplier = 30
            angle = 360 / num_items
            iteration = 0 
            for item in xrange(num_items):        
                x = multiplier * math.cos(math.radians(iteration * angle))
                y = multiplier * math.sin(math.radians(iteration * angle))                
                coords.append([x,y,z])
                iteration += 1
                
        # return
        return coords

    
    def calculate_dp_coords(self, r):

        """This method is used to calculate the coordinates
        for all the data properties of a resource r"""

        # initialize coords
        coords = []

        # get the center
        x, y, z = r.get_coordinates()

        # calculate coordinates for datatype properties
        num_prop = len(r.data_properties)
        if num_prop > 0:
            dangle = 360 / num_prop
            diteration = 0
            for dp in xrange(num_prop):
                        
                dmultiplier = 7
                dpx = dmultiplier * math.cos(math.radians(diteration * dangle)) + x
                dpy = dmultiplier * math.sin(math.radians(diteration * dangle)) + y
                dpz = z
                coords.append([dpx, dpy, dpz])
                diteration += 1
            
        # return 
        return coords


    def redraw(self, uri_list, plane):

        """This function moves all the resources of uri_list
        to the plane indicated by the plane variable. If uri_list 
        is None and plane is 0, then everything is reset to the 
        initial position."""

        # temporarily disable rendering for faster visualization
        self.scene.disable_render = True

        # delete all the text labels
        for active_label in self.active_labels:
            active_label.remove()
        self.active_labels = []

        # since the plane may already host other objects,
        # we get the list of objects on that plane and
        # merge it with the uri_list
        if uri_list:
            old_uri_list = self.res_list.find_by_layer(plane)
            new_uri_list = old_uri_list + uri_list

        # calculate new coordinates for object on the given plane
        if uri_list:
            coords = self.calculate_res_coords(len(new_uri_list), plane*100)
        else:
            coords = self.calculate_res_coords(len(self.res_list.list), plane*100)

        # iterate over the uris
        for resource in self.res_list.list.keys():

            r = self.res_list.list[resource]      
            if (not(uri_list) and plane == 0) or (r.name in new_uri_list):
                
                # remove the old object
                r.gitem.remove() 

                # get the new coordinates
                x,y,z = coords.pop()
                r.set_coordinates(x,y,z)
                
                # design the new object on a different plane                
                gitem = self.drawer.draw_resource(r)
                r.gitem = gitem
                
                # also raise the dp
                dpcoords = self.calculate_dp_coords(r)
                for dp in r.data_properties:
                     
                    # delete the old property
                    dp.gitem_object.remove()
                    dp.gitem_predicate.remove()
                    
                    # update the coordinate
                    xx, yy, zz = dpcoords.pop()
                    dp.set_coordinates(xx, yy, zz)
                     
                    # draw the property                 
                    a1, a2 = self.drawer.draw_data_property(dp)
                    dp.gitem_predicate = a1
                    dp.gitem_object = a2
                 
        # also redraw the object properties
        for resource in self.res_list.list.keys():
            r = self.res_list.list[resource]      
            for op in r.object_properties:
                 
                # delete the old object property
                op.gitem.remove()
                     
                # draw the edge
                item = self.drawer.draw_object_property(op)       
                op.gitem = item

        # remove existing planes and draw a plane where needed
        needed_planes = self.res_list.get_layers_list().keys()
        for plane in self.planes:
            plane.remove()
        self.planes = []
        for needed_plane in needed_planes:
            self.planes.append(self.drawer.draw_plane(int(needed_plane)))
    
        # enable rendering
        self.scene.disable_render = False
        

    def _classes_button_fired(self):
        
        # getting selected class
        c = self.selected_class.class_name
    
        # read the required value
        l = self.classes_level_int    

        # debug print
        logging.debug("Raising instances of class %s" % c)
        self.lastlog_string = "Raising instances of class %s" % c    

        # getting instances of classs c
        uri_list = []
        qres = self.kp.get_instances_of(c)
        for res in qres:
            uri_list.append(str(res[0]))

        # raising instances
        self.redraw(uri_list, l)        


    def _resources_button_fired(self):
        
        # getting selected resource
        r = self.selected_resource.resource_name

        # read the required value
        l = self.resources_level_int    

        # debug print
        logging.debug("Raising resource %s" % r)
        self.lastlog_string = "Raising resource %s" % r
        
        # raise
        self.redraw([r], l)


    def _dataproperties_button_fired(self):
        
        # getting selected datatype property
        dp = self.selected_dp.dp_name

        # read the required value
        l = self.dataproperties_level_int    
    
        # debug print
        logging.debug("Raising instances of class %s" % dp)
        self.lastlog_string = "Raising instances with datatype property %s" % dp

        # getting instances with dataproperty dp
        uri_list = []
        qres = self.kp.get_instances_with_dp(dp)
        for res in qres:
            uri_list.append(str(res[0]))

        # raising instances
        self.redraw(uri_list, l)


    def _objectproperties_button_fired(self):
        
        # getting selected object property
        op = self.selected_op.op_name
    
        # read the required value
        l = self.objectproperties_level_int    

        # debug print
        logging.debug("Raising instances with object property %s" % op)
        self.lastlog_string = "Raising instances with object property %s" % op

        # getting instances with object property op
        uri_list = []
        qres = self.kp.get_instances_with_op(op)
        for res in qres:
            uri_list.append(str(res[0]))

        # raising instances
        self.redraw(uri_list, l)


    def _objectproperties_hide_button_fired(self):
        
        # getting selected object property
        opname = self.selected_op.op_name
    
        # debug print
        logging.debug("Hide/show object property %s" % opname)
        self.lastlog_string = "Hide/show object property %s" % opname               

        # draw object properties
        for resource in self.res_list.list.keys():                
            for op in self.res_list.list[resource].object_properties:
                if op.oproperty == opname:
                    
                    # if item is None -> draw, else remove
                    if op.gitem:
                        op.gitem.remove()
                        op.gitem = None
                    else:
                        # draw the edge
                        item = self.drawer.draw_object_property(op)       
                        op.gitem = item


    def _refresh_fired(self):
        
        """This method is executed when the refresh button is pressed"""
        
        # debug
        logging.debug("REFRESH button pressed")

        # clean
        self.scene.mlab.clf()

        # TODO redraw
        pass


    def data_classifier(self, sparql_query=None):

        # re-init res_list
        self.res_list = ResourceList()
        
        # retrieve classes
        cs = self.kp.get_classes()

        # retrieve statements
        sts = self.kp.get_statements()
        
        # execute the sparql query
        uri_list = []
        if sparql_query:
            uri_list = self.kp.custom_query(sparql_query)

        # data analyzer
        for triple in self.kp.local_storage:
    
            sub, pred, ob = triple
            print triple
            
            # analyze the subject
            sub_res = self.res_list.find_by_name(str(sub))
            if not sub_res:
                
                # Create a new Resource
                if str(sub) in cs:                                        
                    sub_res = Resource(sub, True)                        
                else:
                    sub_res = Resource(sub, False)
                    if str(sub) in sts:
                        sub_res.isStatement = True                    
                self.res_list.add_resource(sub_res)
                
            # analyze the object
            if isinstance(ob, rdflib.URIRef):
                ob_res = self.res_list.find_by_name(str(ob))
                if not ob_res:

                    if str(ob) in cs:                    
                        ob_res = Resource(ob, True)
                    else:
                        ob_res = Resource(ob, False)

                    self.res_list.add_resource(ob_res)

            # analyze the predicate (looking at the object)
            if isinstance(ob, rdflib.URIRef):
    
                # new object property found
                op = ObjectProperty(pred, sub_res, ob_res)
                sub_res.add_object_property(op)
    
            else:
    
                # new data property found
                dp = DataProperty(pred, sub_res, str(ob))
                sub_res.add_data_property(dp)        

        # look for comments and labels
        comments = self.kp.get_all_comments()
        for c in comments:
            res = self.res_list.find_by_name(str(c[0]))
            if res:
                res.comment = c[1]        

        labels = self.kp.get_all_labels()
        for l in labels:
            res = self.res_list.find_by_name(str(l[0]))
            if res:
                res.label = l[1]

                
    def calculate_placement_ng(self, uriplanes = None):

        """This method is used to calculate the best placement for
        nodes. uriplanes is a list of lists.  The first list specifies
        the items to be placed on plane 1 (the default is plane 0),
        the second on plane 2, and so on. If None everything is placed
        on plane 0"""

        # calculate the elements for each plane
        if uriplanes:

            # determine the number of planes
            num_planes = len(uriplanes)
            
            # create a list for every plane
            planes = []
            for p in range(num_planes):
                planes.append([])
            
            # fill the previously created lists
            for resource in self.res_list.list.keys():

                # find in which plane must be placed the resource
                plane_found = False
                r = self.res_list.list[resource]      
                plane_counter = 0
                for uriplane in uriplanes:
                    plane_counter += 1
                    if r.name in uriplane:                        
                        print "PLANE FOUND!"
                        planes[plane_counter].append(r)
                        plane_found = True
                        break
                if not plane_found:
                    planes[0].append(r)
                    
        print "PLANES ARE:"
        counter = 0
        for plane in planes:
            print "* plane %s:" % counter
            print plane
            print
            counter += 1


    def calculate_placement(self):

        """This method is used to calculate the best
        placement for nodes on the plane 0"""

        # resource coordinates generator
        num_points = len(self.res_list.list)
        
        # divide 360 by the number of points to get the base angle
        if num_points > 0:
            multiplier = 30
            angle = 360 / num_points
            iteration = 0 
            for resource in self.res_list.list.keys():
        
                r = self.res_list.list[resource]        
                x = multiplier * math.cos(math.radians(iteration * angle))
                y = multiplier * math.sin(math.radians(iteration * angle))
                self.res_list.list[resource].set_coordinates(x,y,0)
        
                # calculate coordinates for datatype properties
                num_prop = len(r.data_properties)
                try:
                    dangle = 360 / num_prop
                    diteration = 0
                    for dp in r.data_properties:
                        
                        dmultiplier = 7
                        dp.x = dmultiplier * math.cos(math.radians(diteration * dangle)) + r.get_coordinates()[0]
                        dp.y = dmultiplier * math.sin(math.radians(diteration * dangle)) + r.get_coordinates()[1]
                        dp.z = r.get_coordinates()[2]                                                
                        diteration += 1

                except:
                    pass                
                iteration += 1   
        

    def draw(self):

        """This method is called at init time or at refresh time.
        Everything is placed on plane 0. Then the selected items
        can be moved to other planes with proper functions"""

        # disable rendering
        self.scene.disable_render = True

        # create a dict for plane0
        plane0_dict = {}

        # draw plane
        plane0_dict["plane"] = self.drawer.draw_plane(0)
        plane0_dict["widgets"] = []

        # draw resources
        for resource in self.res_list.list.keys():
            r = self.res_list.list[resource]        
            gitem = self.drawer.draw_resource(r)
            r.gitem = gitem            

            # draw data properties
            for dp in r.data_properties:
                
                # draw the property                
                a1, a2 = self.drawer.draw_data_property(dp)
                dp.gitem_object = a2
                dp.gitem_predicate = a1

        # draw object properties
        for resource in self.res_list.list.keys():                
            for op in self.res_list.list[resource].object_properties:

                # draw the edge
                item = self.drawer.draw_object_property(op)       
                op.gitem = item

        # enable rendering
        self.scene.disable_render = False        

        # store the first plane
        self.planes.append(plane0_dict["plane"])
Exemple #26
0
class SimpleInspectorOverlay(TextGridOverlay):
    """ Simple inspector overlay for plots

    This is a simple overlay that listens for new_value events on a
    SimpleInspectorTool and displays formatted values in a grid.

    By default this displays the 'x' and 'y' values provided by the
    SimpleInspectorTool, but instances can provide a field_formatters
    trait which is a list of lists of callables which extract values
    from a dictionary and formats them.  Each callable corresponds to a
    cell in the underlying TextGrid component.

    Although by default this works with the SimpleInspectorTool, with
    appropriate field_formatters this class can be used with any inspector
    tool that follows the same API.
    """
    # XXX We should probably refactor this into a BaseInspectorOverlay
    # which handles the visibility and basic event handling, and smaller
    # version of this class which handles inserting values into a text grid

    # the inspector that I am listening to.  This should have a new_value
    # event and a visible trait for me to listen to.
    inspector = Any

    # fields to display
    field_formatters = List(List(Callable))

    # Anchor the text to the mouse?  (If False, then the text is in one of the
    # corners.)  Use the **align** trait to determine which corner.
    tooltip_mode = Bool(False)

    # The default state of the overlay is visible.
    visible = True

    # Whether the overlay should auto-hide and auto-show based on the
    # tool's location, or whether it should be forced to be hidden or visible.
    visibility = Enum("auto", True, False)

    #########################################################################
    # Traits Handlers
    #########################################################################

    def _field_formatters_default(self):
        return [[basic_formatter('x', 2)], [basic_formatter('y', 2)]]

    def _new_value_updated(self, event):
        if event is None:
            self.text_grid = array()
            if self.visibility == "auto":
                self.visibility = False
        elif self.visibility == "auto":
            self.visible = True

        if self.tooltip_mode:
            self.alternate_position = self.inspector.last_mouse_position

        d = event
        text = []
        self.text_grid.string_array = array([[formatter(**d) for formatter in row]
            for row in self.field_formatters])

        self.text_grid.request_redraw()

    def _visible_changed(self):
        if self.component:
            self.request_redraw()

    def _inspector_changed(self, old, new):
        if old:
            old.on_trait_event(self._new_value_updated, 'new_value', remove=True)
            old.on_trait_change(self._tool_visible_changed, "visible", remove=True)
        if new:
            new.on_trait_event(self._new_value_updated, 'new_value')
            new.on_trait_change(self._tool_visible_changed, "visible")
            self._tool_visible_changed()

    def _tool_visible_changed(self):
        self.visibility = self.inspector.visible
        if self.visibility != "auto":
            self.visible = self.visibility
Exemple #27
0
class SoundDeviceSamplesGenerator(SamplesGenerator):
    """
    Controller for sound card hardware using sounddevice library

    Uses the device with index :attr:`device` to read samples
    from input stream, generates output stream via the generator
    :meth:`result`.
    """

    #: input device index, refers to sounddevice list
    device = Int(0, desc="input device index")

    #: Number of input channels, maximum depends on device
    numchannels = Long(
        1, desc="number of analog input channels that collects data")

    #: Number of samples to collect; defaults to -1.
    # If is set to -1 device collects till user breaks streaming by setting Trait: collectsamples = False
    numsamples = Long(-1, desc="number of samples to collect")

    #: Indicates if samples are collected, helper trait to break result loop
    collectsamples = Bool(True, desc="Indicates if samples are collected")

    #: Sampling frequency of the signal, changes with sinusdevices
    sample_freq = Property(desc="sampling frequency")

    #: Indicates that the sounddevice buffer has overflown
    overflow = Bool(False, desc="Indicates if sounddevice buffer overflow")

    #: Indicates that the stream is collecting samples
    running = Bool(False,
                   desc="Indicates that the stream is collecting samples")

    #: The sounddevice InputStream object for inspection
    stream = Any

    # internal identifier
    digest = Property(depends_on=['device', 'numchannels', 'numsamples'])

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

    # checks that numchannels are not more than device can provide
    @observe('device,numchannels')
    def _get_numchannels(self, change):
        self.numchannels = min(
            self.numchannels,
            sd.query_devices(self.device)['max_input_channels'])

    def _get_sample_freq(self):
        return sd.query_devices(self.device)['default_samplerate']

    def device_properties(self):
        """
        Returns
        -------
        Dictionary of device properties according to sounddevice
        """
        return sd.query_devices(self.device)

    def result(self, num):
        """
        Python generator that yields the output block-wise. Use at least a 
        block-size of one ring cache block. 
        
        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, :attr:`numchannels`). 
            The last block may be shorter than num.
        """
        print(self.device_properties(), self.sample_freq)
        self.stream = stream_obj = sd.InputStream(device=self.device,
                                                  channels=self.numchannels,
                                                  clip_off=True,
                                                  samplerate=self.sample_freq)

        with stream_obj as stream:
            self.running = True
            if self.numsamples == -1:
                while self.collectsamples:  # yield data as long as collectsamples is True
                    data, self.overflow = stream.read(num)
                    yield data[:num]

            elif self.numsamples > 0:  # amount of samples to collect is specified by user
                samples_count = 0  # numsamples counter
                while samples_count < self.numsamples:
                    anz = min(num, self.numsamples - samples_count)
                    data, self.overflow = stream.read(num)
                    yield data[:anz]
                    samples_count += anz
        self.running = False
        return
Exemple #28
0
class Bullseye(HasTraits):
    plots = Instance(GridPlotContainer)
    abplots = Instance(VPlotContainer)
    screen = Instance(Plot)
    horiz = Instance(Plot)
    vert = Instance(Plot)
    asum = Instance(Plot)
    bsum = Instance(Plot)

    process = Instance(Process)

    colormap = Enum("gray", "jet", "hot", "prism")
    invert = Bool(True)

    label = None
    gridm = None
    grid = None

    traits_view = View(HGroup(VGroup(
        HGroup(
            VGroup(
                Item("object.process.x", label="Centroid x",
                    format_str=u"%.4g µm",
                    tooltip="horizontal beam position relative to chip "
                    "center"),
                Item("object.process.a", label="Major 4sig",
                    format_str=u"%.4g µm",
                    tooltip="major axis beam width 4 sigma ~ 1/e^2 width"),
                Item("object.process.t", label="Rotation",
                    format_str=u"%.4g°",
                    tooltip="angle between horizontal an major axis"),
                #Item("object.process.black", label="Black",
                #    format_str=u"%.4g",
                #    tooltip="background black level"),
            ), VGroup(
                Item("object.process.y", label="Centroid y",
                    format_str=u"%.4g µm",
                    tooltip="vertical beam position relative to chip "
                    "center"),
                Item("object.process.b", label="Minor 4sig",
                    format_str=u"%.4g µm",
                    tooltip="major axis beam width 4 sigma ~ 1/e^2 width"),
                #Item("object.process.d", label="Mean width",
                #    format_str=u"%.4g µm",
                #    tooltip="mean beam width 4 sigma ~ 1/e^2 width"),
                #Item("object.process.e", label="Ellipticity",
                #    format_str=u"%.4g",
                #    tooltip="ellipticity minor-to-major width ratio"),
                #Item("object.process.peak", label="Peak",
                #    format_str=u"%.4g",
                #    tooltip="peak pixel level"),
                Item("object.process.include_radius", label="Include radius",
                    format_str=u"%.4g µm",
                    tooltip="energy inclusion radius according to ignore "
                    "level, used to crop before taking moments"),
            ),
            style="readonly",
        ), VGroup(
            Item("object.process.capture.shutter",
                tooltip="exposure time per frame in seconds"),
            Item("object.process.capture.gain",
                tooltip="analog camera gain in dB"),
            Item("object.process.capture.framerate",
                tooltip="frames per second to attempt, may be limited by "
                "shutter time and processing speed"),
            Item("object.process.capture.average",
                tooltip="number of subsequent images to boxcar average"),
            Item("object.process.background",
                tooltip="background intensity percentile to subtract "
                "from image"),
            Item("object.process.ignore",
                tooltip="fraction of total intensity to ignore for "
                "cropping, determines include radius"),
        ), HGroup(
            Item("object.process.active",
                tooltip="capture and processing running"),
            Item("object.process.capture.auto_shutter",
                tooltip="adjust the shutter time to "
                "yield acceptably exposed frames with peak values "
                "between .25 and .75"),
            Item("object.process.track",
                tooltip="adjust the region of interest to track the "
                "beam center, the size is not adjusted"),
            Item("object.process.capture.dark",
                tooltip="capture a dark image and subtract it from "
                "subsequent images, reset if gain or shutter change"),
        ), HGroup(
            UItem("colormap", tooltip="image colormap"),
            Item("invert", tooltip="invert the colormap"),
        ), UItem("abplots", editor=ComponentEditor(),
                width=-200, height=-300, resizable=False,
                tooltip="line sums (red), moments (blue) and "
                "2-sigma markers (green) along the major and minor axes",
        ),
    ), UItem("plots", editor=ComponentEditor(), width=800,
            tooltip="top right: beam image with 2-sigma and 6-sigma "
            "radius ellipses and axis markers (green). top left and bottom "
            "right: vertial and horizontal line sums (red), moments "
            "(blue) and 2-sigma markers (green). bottom left: beam data "
            "from moments"),
    layout="split",
    ), resizable=True, title=u"Bullseye ― Beam Profiler", width=1000)

    def __init__(self, **k):
        super(Bullseye, self).__init__(**k)
        self.data = ArrayPlotData()
        self.process.initialize()
        self.setup_plots()
        self.update_data()
        self.populate_plots()

        self.on_trait_change(self.update_data, "process.new_data",
                dispatch="fast_ui")

    def setup_plots(self):
        self.screen = Plot(self.data,
                resizable="hv", padding=0, bgcolor="lightgray",
                border_visible=False)
        self.screen.index_grid.visible = False
        self.screen.value_grid.visible = False
        px = self.process.capture.pixelsize
        w, h = self.process.capture.width, self.process.capture.height
        # value_range last, see set_range()
        self.screen.index_range.low_setting = -w/2*px
        self.screen.index_range.high_setting = w/2*px
        self.screen.value_range.low_setting = -h/2*px
        self.screen.value_range.high_setting = h/2*px

        self.horiz = Plot(self.data,
                resizable="h", padding=0, height=100,
                bgcolor="lightgray", border_visible=False)
        self.horiz.value_mapper.range.low_setting = \
                -.1*self.process.capture.maxval
        self.horiz.index_range = self.screen.index_range
        self.vert = Plot(self.data, orientation="v",
                resizable="v", padding=0, width=100,
                bgcolor="lightgray", border_visible=False)
        for p in self.horiz, self.vert:
            p.index_axis.visible = False
            p.value_axis.visible = False
            p.index_grid.visible = True
            p.value_grid.visible = False
        self.vert.value_mapper.range.low_setting = \
                -.1*self.process.capture.maxval
        self.vert.index_range = self.screen.value_range

        #self.vert.value_range = self.horiz.value_range

        self.mini = Plot(self.data,
                width=100, height=100, resizable="", padding=0,
                bgcolor="lightgray", border_visible=False)
        self.mini.index_axis.visible = False
        self.mini.value_axis.visible = False
        self.label = PlotLabel(component=self.mini,
                overlay_position="inside left", font="modern 10",
                text=self.process.text)
        self.mini.overlays.append(self.label)

        self.plots = GridPlotContainer(shape=(2, 2), padding=0,
                spacing=(5, 5), use_backbuffer=True,
                bgcolor="lightgray")
        self.plots.component_grid = [[self.vert, self.screen],
                                     [self.mini, self.horiz ]]

        self.screen.overlays.append(ZoomTool(self.screen,
            x_max_zoom_factor=1e2, y_max_zoom_factor=1e2,
            x_min_zoom_factor=0.5, y_min_zoom_factor=0.5,
            zoom_factor=1.2))
        self.screen.tools.append(PanTool(self.screen))
        self.plots.tools.append(SaveTool(self.plots,
            filename="bullseye.pdf"))

        self.asum = Plot(self.data,
                padding=0, height=100, bgcolor="lightgray",
                title="major axis", border_visible=False)
        self.bsum = Plot(self.data,
                padding=0, height=100, bgcolor="lightgray",
                title="minor axis", border_visible=False)
        for p in self.asum, self.bsum:
            p.value_axis.visible = False
            p.value_grid.visible = False
            p.title_font = "modern 10"
            p.title_position = "left"
            p.title_angle = 90
        # lock scales
        #self.bsum.value_range = self.asum.value_range
        #self.bsum.index_range = self.asum.index_range

        self.abplots = VPlotContainer(padding=20, spacing=20,
                use_backbuffer=True,bgcolor="lightgray",
                fill_padding=True)
        self.abplots.add(self.bsum, self.asum)

    def populate_plots(self):
        self.screenplot = self.screen.img_plot("img",
                xbounds="xbounds", ybounds="ybounds",
                interpolation="nearest",
                colormap=color_map_name_dict[self.colormap],
                )[0]
        self.set_invert()
        self.grid = self.screenplot.index
        self.gridm = self.screenplot.index_mapper
        t = ImageInspectorTool(self.screenplot)
        self.screen.tools.append(t)
        self.screenplot.overlays.append(ImageInspectorOverlay(
            component=self.screenplot, image_inspector=t,
            border_size=0, bgcolor="transparent", align="ur",
            tooltip_mode=False, font="modern 10"))

        self.horiz.plot(("x", "imx"), type="line", color="red")
        self.vert.plot(("y", "imy"), type="line", color="red")
        self.horiz.plot(("x", "gx"), type="line", color="blue")
        self.vert.plot(("y", "gy"), type="line", color="blue")
        self.asum.plot(("a", "ima"), type="line", color="red")
        self.bsum.plot(("b", "imb"), type="line", color="red")
        self.asum.plot(("a", "ga"), type="line", color="blue")
        self.bsum.plot(("b", "gb"), type="line", color="blue")

        for p in [("ell1_x", "ell1_y"), ("ell3_x", "ell3_y"),
                ("a_x", "a_y"), ("b_x", "b_y")]:
            self.screen.plot(p, type="line", color="green", alpha=.5)

        for r, s in [("x", self.horiz), ("y", self.vert),
                ("a", self.asum), ("b", self.bsum)]:
            for p in "0 p m".split():
                q = ("%s%s_mark" % (r, p), "%s_bar" % r)
                s.plot(q, type="line", color="green")

    def __del__(self):
        self.close()

    def close(self):
        self.process.active = False

    @on_trait_change("colormap")
    def set_colormap(self):
        p = self.screenplot
        m = color_map_name_dict[self.colormap]
        p.color_mapper = m(p.value_range)
        self.set_invert()
        p.request_redraw()

    @on_trait_change("invert")
    def set_invert(self):
        p = self.screenplot
        if self.invert:
            a, b = self.process.capture.maxval, 0
        else:
            a, b = 0, self.process.capture.maxval
        p.color_mapper.range.low_setting = a
        p.color_mapper.range.high_setting = b

    # TODO: bad layout for one frame at activation, track
    # value_range seems to be updated after index_range, take this
    @on_trait_change("screen.value_range.updated")
    def set_range(self):
        if self.gridm is not None:
            #enforce data/screen aspect ratio 1
            sl, sr, sb, st = self.gridm.screen_bounds
            dl, db = self.gridm.range.low
            dr, dt = self.gridm.range.high
            #dsdx = float(sr-sl)/(dr-dl)
            dsdy = float(st-sb)/(dt-db)
            #dt_new = db+(st-sb)/dsdx
            if dsdy:
                dr_new = dl+(sr-sl)/dsdy
                self.gridm.range.x_range.high_setting = dr_new
        l, r = self.screen.index_range.low, self.screen.index_range.high
        b, t = self.screen.value_range.low, self.screen.value_range.high
        px = self.process.capture.pixelsize
        self.process.capture.roi = [l, b, r-l, t-b]

    def update_data(self):
        if self.label is not None:
            self.label.text = self.process.text
        upd = self.process.data
        self.data.arrays.update(upd)
        self.data.data_changed = {"changed": upd.keys()}
        if self.grid is not None:
            self.grid.set_data(upd["xbounds"], upd["ybounds"])
Exemple #29
0
    class Foo(HasTraits):
        a = Array()
        event_fired = Bool(False)

        def _a_changed(self):
            self.event_fired = True
Exemple #30
0
class TasksApplication(Application):
    """The entry point for an Envisage Tasks application.

    This class handles the common case for Tasks applications and is
    intended to be subclassed to modify its start/stop behavior, etc.

    """

    # Extension point IDs.
    TASK_FACTORIES = 'envisage.ui.tasks.tasks'
    TASK_EXTENSIONS = 'envisage.ui.tasks.task_extensions'

    #### 'TasksApplication' interface #########################################

    # The active task window (the last one to get focus).
    active_window = Instance('envisage.ui.tasks.task_window.TaskWindow')

    # The PyFace GUI for the application.
    gui = Instance('pyface.gui.GUI')

    # Icon for the whole application. Will be used to override all taskWindows
    # icons to have the same.
    icon = Instance('pyface.image_resource.ImageResource', allow_none=True)

    # The name of the application (also used on window title bars).
    name = Unicode

    # The splash screen for the application. By default, there is no splash
    # screen.
    splash_screen = Instance('pyface.splash_screen.SplashScreen')

    # The directory on the local file system used to persist window layout
    # information.
    state_location = Directory

    # Contributed task factories. This attribute is primarily for run-time
    # inspection; to instantiate a task, use the 'create_task' method.
    task_factories = ExtensionPoint(id=TASK_FACTORIES)

    # Contributed task extensions.
    task_extensions = ExtensionPoint(id=TASK_EXTENSIONS)

    # The list of task windows created by the application.
    windows = List(Instance('envisage.ui.tasks.task_window.TaskWindow'))

    # The factory for creating task windows.
    window_factory = Callable

    #### Application layout ###################################################

    # The default layout for the application. If not specified, a single window
    # will be created with the first available task factory.
    default_layout = List(
        Instance('pyface.tasks.task_window_layout.TaskWindowLayout'))

    # Whether to always apply the default *application level* layout when the
    # application is started. Even if this is False, the layout state of
    # individual tasks will be restored.
    always_use_default_layout = Bool(False)

    #### Application lifecycle events #########################################

    # Fired after the initial windows have been created and the GUI event loop
    # has been started.
    application_initialized = Event

    # Fired immediately before the extant windows are destroyed and the GUI
    # event loop is terminated.
    application_exiting = Event

    # Fired when a task window has been created.
    window_created = Event(
        Instance('envisage.ui.tasks.task_window_event.TaskWindowEvent'))

    # Fired when a task window is opening.
    window_opening = Event(
        Instance(
            'envisage.ui.tasks.task_window_event.VetoableTaskWindowEvent'))

    # Fired when a task window has been opened.
    window_opened = Event(
        Instance('envisage.ui.tasks.task_window_event.TaskWindowEvent'))

    # Fired when a task window is closing.
    window_closing = Event(
        Instance(
            'envisage.ui.tasks.task_window_event.VetoableTaskWindowEvent'))

    # Fired when a task window has been closed.
    window_closed = Event(
        Instance('envisage.ui.tasks.task_window_event.TaskWindowEvent'))

    #### Protected interface ##################################################

    # An 'explicit' exit is when the the 'exit' method is called.
    # An 'implicit' exit is when the user closes the last open window.
    _explicit_exit = Bool(False)

    # Application state.
    _state = Instance(
        'envisage.ui.tasks.tasks_application.TasksApplicationState')

    ###########################################################################
    # 'IApplication' interface.
    ###########################################################################

    def run(self):
        """ Run the application.

        Returns:
        --------
        Whether the application started successfully (i.e., without a veto).
        """
        # Make sure the GUI has been created (so that, if required, the splash
        # screen is shown).
        gui = self.gui

        started = self.start()
        if started:
            # Create windows from the default or saved application layout.
            self._create_windows()

            # Start the GUI event loop.
            gui.set_trait_later(self, 'application_initialized', self)
            gui.start_event_loop()

        return started

    ###########################################################################
    # 'TasksApplication' interface.
    ###########################################################################

    def create_task(self, id):
        """ Creates the Task with the specified ID.

        Returns:
        --------
        The new Task, or None if there is not a suitable TaskFactory.
        """
        # Get the factory for the task.
        factory = self._get_task_factory(id)
        if factory is None:
            return None

        # Create the task using suitable task extensions.
        extensions = [
            ext for ext in self.task_extensions
            if ext.task_id == id or not ext.task_id
        ]
        task = factory.create_with_extensions(extensions)
        task.id = factory.id
        return task

    def create_window(self, layout=None, restore=True, **traits):
        """Creates a new TaskWindow, possibly with some Tasks.

        Parameters:
        -----------
        layout : TaskWindowLayout, optional
             The layout to use for the window. The tasks described in
             the layout will be created and added to the window
             automatically. If not specified, the window will contain
             no tasks.

        restore : bool, optional (default True)
             If set, the application will restore old size and
             positions for the window and its panes, if possible. If a
             layout is not provided, this parameter has no effect.

        **traits : dict, optional
             Additional parameters to pass to ``window_factory()``
             when creating the TaskWindow.

        Returns:
        --------
        The new TaskWindow.

        """
        from .task_window_event import TaskWindowEvent
        from pyface.tasks.task_window_layout import TaskWindowLayout

        window = self.window_factory(application=self, **traits)

        # Listen for the window events.
        window.on_trait_change(self._on_window_activated, 'activated')
        window.on_trait_change(self._on_window_opening, 'opening')
        window.on_trait_change(self._on_window_opened, 'opened')
        window.on_trait_change(self._on_window_closing, 'closing')
        window.on_trait_change(self._on_window_closed, 'closed')

        # Event notification.
        self.window_created = TaskWindowEvent(window=window)

        if layout:
            # Create and add tasks.
            for task_id in layout.get_tasks():
                task = self.create_task(task_id)
                if task:
                    window.add_task(task)
                else:
                    logger.error('Missing factory for task with ID %r',
                                 task_id)

            # Apply a suitable layout.
            if restore:
                layout = self._restore_layout_from_state(layout)
        else:
            # Create an empty layout to set default size and position only
            layout = TaskWindowLayout()

        window.set_window_layout(layout)

        return window

    def exit(self, force=False):
        """Exits the application, closing all open task windows.

        Each window is sent a veto-able closing event. If any window vetoes the
        close request, no window will be closed. Otherwise, all windows will be
        closed and the GUI event loop will terminate.

        This method is not called when the user clicks the close
        button on a window or otherwise closes a window through his or
        her window manager. It is only called via the File->Exit menu
        item. It can also, of course, be called programatically.

        Parameters:
        -----------
        force : bool, optional (default False)
            If set, windows will receive no closing events and will be
            destroyed unconditionally. This can be useful for reliably
            tearing down regression tests, but should be used with
            caution.

        Returns:
        --------
        A boolean indicating whether the application exited.

        """
        self._explicit_exit = True
        try:
            if not force:
                for window in reversed(self.windows):
                    window.closing = event = Vetoable()
                    if event.veto:
                        return False

            self._prepare_exit()
            for window in reversed(self.windows):
                window.destroy()
                window.closed = True
        finally:
            self._explicit_exit = False
        return True

    ###########################################################################
    # Protected interface.
    ###########################################################################

    def _create_windows(self):
        """ Called at startup to create TaskWindows from the default or saved
            application layout.
        """
        # Build a list of TaskWindowLayouts.
        self._load_state()
        if (self.always_use_default_layout or
                not self._state.previous_window_layouts):
            window_layouts = self.default_layout
        else:
            # Choose the stored TaskWindowLayouts, but only if all the task IDs
            # are still valid.
            window_layouts = self._state.previous_window_layouts
            for layout in window_layouts:
                for task_id in layout.get_tasks():
                    if not self._get_task_factory(task_id):
                        logger.warning('Saved application layout references '
                                       'non-existent task %r. Falling back to '
                                       'default application layout.' % task_id)
                        window_layouts = self.default_layout
                        break
                else:
                    continue
                break

        # Create a TaskWindow for each TaskWindowLayout.
        for window_layout in window_layouts:
            window = self.create_window(
                window_layout, restore=self.always_use_default_layout)
            window.open()

    def _get_task_factory(self, id):
        """ Returns the TaskFactory with the specified ID, or None.
        """
        for factory in self.task_factories:
            if factory.id == id:
                return factory
        return None

    def _prepare_exit(self):
        """ Called immediately before the extant windows are destroyed and the
            GUI event loop is terminated.
        """
        self.application_exiting = self
        self._save_state()

    def _load_state(self):
        """ Loads saved application state, if possible.
        """
        state = TasksApplicationState()
        filename = os.path.join(self.state_location, 'application_memento')
        if os.path.exists(filename):
            # Attempt to unpickle the saved application state.
            try:
                with open(filename, 'rb') as f:
                    restored_state = pickle.load(f)
                if state.version == restored_state.version:
                    state = restored_state
                else:
                    logger.warn('Discarding outdated application layout')
            except:
                # If anything goes wrong, log the error and continue.
                logger.exception('Restoring application layout from %s',
                                 filename)
        self._state = state

    def _restore_layout_from_state(self, layout):
        """ Restores an equivalent layout from saved application state.
        """
        # First, see if a window layout matches exactly.
        match = self._state.get_equivalent_window_layout(layout)
        if match:
            # The active task is not part of the equivalency relation, so we
            # ensure that it is correct.
            match.active_task = layout.get_active_task()
            layout = match

        # If that fails, at least try to restore the layout of
        # individual tasks.
        else:
            layout = layout.clone_traits()
            for i, item in enumerate(layout.items):
                id = item if isinstance(item, STRING_BASE_CLASS) else item.id
                match = self._state.get_task_layout(id)
                if match:
                    layout.items[i] = match

        return layout

    def _save_state(self):
        """ Saves the application state.
        """
        # Grab the current window layouts.
        window_layouts = [w.get_window_layout() for w in self.windows]
        self._state.previous_window_layouts = window_layouts

        # Attempt to pickle the application state.
        filename = os.path.join(self.state_location, 'application_memento')
        try:
            with open(filename, 'wb') as f:
                pickle.dump(self._state, f)
        except:
            # If anything goes wrong, log the error and continue.
            logger.exception('Saving application layout')

    #### Trait initializers ###################################################

    def _window_factory_default(self):
        from envisage.ui.tasks.task_window import TaskWindow
        return TaskWindow

    def _default_layout_default(self):
        from pyface.tasks.task_window_layout import TaskWindowLayout
        window_layout = TaskWindowLayout()
        if self.task_factories:
            window_layout.items = [self.task_factories[0].id]
        return [window_layout]

    def _gui_default(self):
        from pyface.gui import GUI
        return GUI(splash_screen=self.splash_screen)

    def _state_location_default(self):
        state_location = os.path.join(ETSConfig.application_home, 'tasks',
                                      ETSConfig.toolkit)
        if not os.path.exists(state_location):
            os.makedirs(state_location)

        logger.debug('Tasks state location is %s', state_location)

        return state_location

    #### Trait change handlers ################################################

    def _on_window_activated(self, window, trait_name, event):
        self.active_window = window

    def _on_window_opening(self, window, trait_name, event):
        from .task_window_event import VetoableTaskWindowEvent
        # Event notification.
        self.window_opening = window_event = VetoableTaskWindowEvent(
            window=window)

        if window_event.veto:
            event.veto = True

    def _on_window_opened(self, window, trait_name, event):
        from .task_window_event import TaskWindowEvent
        self.windows.append(window)

        # Event notification.
        self.window_opened = TaskWindowEvent(window=window)

    def _on_window_closing(self, window, trait_name, event):
        from .task_window_event import VetoableTaskWindowEvent
        # Event notification.
        self.window_closing = window_event = VetoableTaskWindowEvent(
            window=window)

        if window_event.veto:
            event.veto = True
        else:
            # Store the layout of the window.
            window_layout = window.get_window_layout()
            self._state.push_window_layout(window_layout)

            # If we're exiting implicitly and this is the last window, save
            # state, because we won't get another chance.
            if len(self.windows) == 1 and not self._explicit_exit:
                self._prepare_exit()

    def _on_window_closed(self, window, trait_name, event):
        from .task_window_event import TaskWindowEvent
        self.windows.remove(window)

        # Event notification.
        self.window_closed = TaskWindowEvent(window=window)

        # Was this the last window?
        if len(self.windows) == 0:
            self.stop()