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
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)
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
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, ' '))
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
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]<>"], ] )
class UCustom(Custom): """ An Item using a 'custom' style with no label. """ show_label = Bool(False)
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
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)
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
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)
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
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
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}
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
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', )
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'
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')
class UItem(Item): """ An Item that has no label. """ show_label = Bool(False)
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()
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)
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
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"])
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
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
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"])
class Foo(HasTraits): a = Array() event_fired = Bool(False) def _a_changed(self): self.event_fired = True
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()