class _MenuItem(HasTraits): """ A menu item representation of an action item. """ #### '_MenuItem' interface ################################################ # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the menu item is not part of such # a group). group = Any # The toolkit control. control = Any() # The toolkit control id. control_id = None ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, menu, item, controller): """ Creates a new menu item for an action item. """ self.item = item action = item.action # FIXME v3: This is a wx'ism and should be hidden in the toolkit code. self.control_id = None if action.style == 'widget': self.control = PyfaceWidgetAction(parent, action) menu.addAction(self.control) elif action.image is None: self.control = menu.addAction(action.name, self._qt4_on_triggered, action.accelerator) else: self.control = menu.addAction(action.image.create_icon(), action.name, self._qt4_on_triggered, action.accelerator) menu.menu_items.append(self) self.control.setToolTip(action.tooltip) self.control.setWhatsThis(action.description) self.control.setEnabled(action.enabled) self.control.setVisible(action.visible) if getattr(action, 'menu_role', False): if action.menu_role == "About": self.control.setMenuRole(QtGui.QAction.AboutRole) elif action.menu_role == "Preferences": self.control.setMenuRole(QtGui.QAction.PreferencesRole) if action.style == 'toggle': self.control.setCheckable(True) self.control.setChecked(action.checked) elif action.style == 'radio': # Create an action group if it hasn't already been done. try: ag = item.parent._qt4_ag except AttributeError: ag = item.parent._qt4_ag = QtGui.QActionGroup(parent) self.control.setActionGroup(ag) self.control.setCheckable(True) self.control.setChecked(action.checked) # Listen for trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_visible_changed, 'visible') action.on_trait_change(self._on_action_checked_changed, 'checked') action.on_trait_change(self._on_action_name_changed, 'name') action.on_trait_change(self._on_action_accelerator_changed, 'accelerator') # Detect if the control is destroyed. self.control.destroyed.connect(self._qt4_on_destroyed) if controller is not None: self.controller = controller controller.add_to_menu(self) def dispose(self): action = self.item.action action.on_trait_change(self._on_action_enabled_changed, 'enabled', remove=True) action.on_trait_change(self._on_action_visible_changed, 'visible', remove=True) action.on_trait_change(self._on_action_checked_changed, 'checked', remove=True) action.on_trait_change(self._on_action_name_changed, 'name', remove=True) action.on_trait_change(self._on_action_accelerator_changed, 'accelerator', remove=True) ########################################################################### # Private interface. ########################################################################### def _qt4_on_destroyed(self, control=None): """ Delete the reference to the control to avoid attempting to talk to it again. """ self.control = None def _qt4_on_triggered(self): """ Called when the menu item has been clicked. """ action = self.item.action action_event = ActionEvent() is_checkable = action.style in ['radio', 'toggle'] # Perform the action! if self.controller is not None: if is_checkable: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. This is also # useful as Traits UI controllers *never* require the event. argspec = getargspec(self.controller.perform) # If the only arguments are 'self' and 'action' then don't pass # the event! if len(argspec.args) == 2: self.controller.perform(action) else: self.controller.perform(action, action_event) else: if is_checkable: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. argspec = getargspec(action.perform) # If the only argument is 'self' then don't pass the event! if len(argspec.argspecargs) == 1: action.perform() else: action.perform(action_event) #### Trait event handlers ################################################# def _enabled_changed(self): """ Called when our 'enabled' trait is changed. """ if self.control is not None: self.control.setEnabled(self.enabled) def _visible_changed(self): """ Called when our 'visible' trait is changed. """ if self.control is not None: self.control.setVisible(self.visible) def _checked_changed(self): """ Called when our 'checked' trait is changed. """ if self.control is not None: self.control.setChecked(self.checked) def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ if self.control is not None: self.control.setEnabled(action.enabled) def _on_action_visible_changed(self, action, trait_name, old, new): """ Called when the visible trait is changed on an action. """ if self.control is not None: self.control.setVisible(action.visible) def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if self.control is not None: self.control.setChecked(action.checked) def _on_action_name_changed(self, action, trait_name, old, new): """ Called when the name trait is changed on an action. """ if self.control is not None: self.control.setText(action.name) def _on_action_accelerator_changed(self, action, trait_name, old, new): """ Called when the accelerator trait is changed on an action. """ if self.control is not None: self.control.setShortcut(action.accelerator)
class RectGrid3D(RectGrid): """ Provides a cartesian 3D grid for the beamforming results. The grid has cubic or nearly cubic cells. It is defined by lower and upper x-, y- and z-limits. """ #: The lower z-limit that defines the grid, defaults to -1. z_min = Float(-1.0, desc="minimum z-value") #: The upper z-limit that defines the grid, defaults to 1. z_max = Float(1.0, desc="maximum z-value") #: Number of grid points along x-axis, readonly. nzsteps = Property(desc="number of grid points along x-axis") # Private trait for increment handling _increment = Any(0.1) #: The cell side length for the grid. This can either be a scalar (same #: increments in all 3 dimensions) or a (3,) array of floats with #: respective increments in x,y, and z-direction (in m). #: Defaults to 0.1. increment = Property(desc="step size") def _get_increment(self): return self._increment def _set_increment(self, increment): if isscalar(increment): try: self._increment = absolute(float(increment)) except: raise TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment) elif len(increment) == 3: self._increment = array(increment, dtype=float) else: raise (TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment)) # Respective increments in x,y, and z-direction (in m). # Deprecated: Use :attr:`~RectGrid.increment` for this functionality increment3D = Property(desc="3D step sizes") def _get_increment3D(self): if isscalar(self._increment): return array([self._increment, self._increment, self._increment]) else: return self._increment def _set_increment3D(self, inc): if not isscalar(inc) and len(inc) == 3: self._increment = array(inc, dtype=float) else: raise (TraitError(args=self, name='increment3D', info='CArray(3,)', value=inc)) # internal identifier digest = Property( depends_on = ['x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max', \ '_increment'] ) @property_depends_on('nxsteps, nysteps, nzsteps') def _get_size(self): return self.nxsteps * self.nysteps * self.nzsteps @property_depends_on('nxsteps, nysteps, nzsteps') def _get_shape(self): return (self.nxsteps, self.nysteps, self.nzsteps) @property_depends_on('x_min, x_max, increment3D') def _get_nxsteps(self): i = abs(self.increment3D[0]) if i != 0: return int(round((abs(self.x_max - self.x_min) + i) / i)) return 1 @property_depends_on('y_min, y_max, increment3D') def _get_nysteps(self): i = abs(self.increment3D[1]) if i != 0: return int(round((abs(self.y_max - self.y_min) + i) / i)) return 1 @property_depends_on('z_min, z_max, increment3D') def _get_nzsteps(self): i = abs(self.increment3D[2]) if i != 0: return int(round((abs(self.z_max - self.z_min) + i) / i)) return 1 @property_depends_on('digest') def _get_gpos(self): """ Calculates grid co-ordinates. Returns ------- array of floats of shape (3, :attr:`~Grid.size`) The grid point x, y, z-coordinates in one array. """ bpos = mgrid[self.x_min:self.x_max:self.nxsteps*1j, \ self.y_min:self.y_max:self.nysteps*1j, \ self.z_min:self.z_max:self.nzsteps*1j] bpos.resize((3, self.size)) return bpos @cached_property def _get_digest(self): return digest(self) def index(self, x, y, z): """ Queries the indices for a grid point near a certain co-ordinate. This can be used to query results or co-ordinates at/near a certain co-ordinate. Parameters ---------- x, y, z : float The co-ordinates for which the indices is queried. Returns ------- 3-tuple of integers The indices that give the grid point nearest to the given x, y, z co-ordinates from an array with the same shape as the grid. """ if x < self.x_min or x > self.x_max: raise ValueError("x-value out of range %f (%f, %f)" % \ (x,self.x_min,self.x_max)) if y < self.y_min or y > self.y_max: raise ValueError("y-value out of range %f (%f, %f)" % \ (y,self.y_min,self.y_max)) if z < self.z_min or z > self.z_max: raise ValueError("z-value out of range %f (%f, %f)" % \ (z,self.z_min,self.z_max)) xi = int(round((x - self.x_min) / self.increment3D[0])) yi = int(round((y - self.y_min) / self.increment3D[1])) zi = int(round((z - self.z_min) / self.increment3D[2])) return xi, yi, zi def indices(self, x1, y1, z1, x2, y2, z2): """ Queries the indices for a subdomain in the grid. Allows box-shaped subdomains. This can be used to mask or to query results from a certain sector or subdomain. Parameters ---------- x1, y1, z1, x2, y2, z2 : float A box-shaped sector is assumed that is given by two corners (x1,y1,z1) and (x2,y2,z2). Returns ------- 3-tuple of numpy slice objects The indices that can be used to mask/select the grid subdomain from an array with the same shape as the grid. """ xi1, yi1, zi1 = self.index(min(x1, x2), min(y1, y2), min(z1, z2)) xi2, yi2, zi2 = self.index(max(x1, x2), max(y1, y2), max(z1, z2)) return s_[xi1:xi2 + 1], s_[yi1:yi2 + 1], s_[zi1:zi2 + 1]
class HB_Referrer ( HasPrivateTraits ): """ Describes the set of objects which refer to a specific object. """ # A weak reference to the heap object being described: ref = Any # The actual object: object = Property # The name of this item: name = Property # The trait name this object referred to its referent by: trait_name = Str # The objects that refer to this object: referrers = Property( depends_on = 'update' ) # Event fired when the list of referrers should be updated: update = Event # The name space for all objects in this referrer graph: name_space = Any( {} ) #-- Traits UI View Definitions --------------------------------------------- view = View( Item( 'object', show_label = False, editor = ValueEditor() ), id = 'etsdevtools.developer.tools.heap_browser.HB_Referrer', width = 0.20, height = 0.25, resizable = True, handler = HB_ReferrerHandler ) #-- Property Implementations ----------------------------------------------- def _get_object ( self ): return self.ref() @cached_property def _get_name ( self ): object = self.object name = object_name( object ) instances = self.name_space.setdefault( name, {} ) n = instances.setdefault( id( object ), len( instances ) + 1 ) trait_name = self.trait_name if trait_name != '': trait_name = '.' + trait_name return '%s:%s%s[%s]' % ( name[ name.rfind( '.' ) + 1: ], n, trait_name, getrefcount( object ) - 2 ) @cached_property def _get_referrers ( self ): instance = self.object if isinstance( instance, FrameType ): return [] referrers = [] dicts = set() seqs = set() name_space = self.name_space for object in gc.get_referrers( instance ): try: referrers.append( HB_Referrer( ref = weakref.ref( object, self._instance_gone ), name_space = name_space ) ) except: if isinstance( object, dict ): dicts.add( id( object ) ) elif isinstance( object, SequenceTypes ): seqs.add( id( object ) ) else: referrers.append( HB_Referrer( ref = lambda object = object: object, name_space = name_space ) ) len_dicts = len( dicts ) len_seqs = len( seqs ) if (len_dicts > 0) or (len_seqs > 0): for object in gc.get_objects(): try: object_dict = object.__dict__ if (len_dicts > 0) and (id( object_dict ) in dicts): referrers.extend( [ HB_Referrer( ref = weakref.ref( object, self._instance_gone ), trait_name = name, name_space = name_space ) for name, value in object_dict.iteritems() if instance is value ] ) elif len_seqs > 0: isect = seqs.intersect( [ id( value ) for value in object_dict.itervalues() ] ) seqs.difference_update( isect ) for seq in isect: seqs.remove( seq ) for name, value in object_dict.iteritems(): if seq == id( value ): referrers.append( HB_Referrer( ref = weakref.ref( object, self._instance_gone ), trait_name = name, name_space = name_space ) ) break except: pass return referrers #-- Private Methods -------------------------------------------------------- def _instance_gone ( self, ref ): """ Handles the instance an HB_Referrer object refers to being garbage collected. """ self.update = True
class SingleValueTreeNodeObject(TreeNodeObject): """A tree node for objects of types that have a single value.""" # ------------------------------------------------------------------------- # Trait definitions: # ------------------------------------------------------------------------- #: The parent of this node parent = Instance(TreeNodeObject) #: Name of the value name = Str() #: User-specified override of the default label label = Str() #: The value itself value = Any() #: Is the value readonly? readonly = Bool(False) def tno_allows_children(self, node): """Returns whether this object can have children (False for this class). """ return False def tno_has_children(self, node): """Returns whether the object has children (False for this class).""" return False def tno_can_rename(self, node): """Returns whether the object's children can be renamed (False for this class). """ return False def tno_can_copy(self, node): """Returns whether the object's children can be copied (True for this class). """ return True def tno_can_delete(self, node): """Returns whether the object's children can be deleted (False for this class). """ return False def tno_can_insert(self, node): """Returns whether the object's children can be inserted (False, meaning children are appended, for this class). """ return False def tno_get_icon(self, node, is_expanded): """Returns the icon for a specified object.""" return "@icons:%s_node" % self.__class__.__name__[:-4].lower() def tno_set_label(self, node, label): """Sets the label for a specified object.""" if label == "?": label = "" self.label = label def tno_get_label(self, node): """Gets the label to display for a specified object.""" if self.label != "": return self.label if self.name == "": return self.format_value(self.value) return "%s: %s" % (self.name, self.format_value(self.value)) def format_value(self, value): """Returns the formatted version of the value.""" return repr(value) def node_for(self, name, value): """Returns the correct node type for a specified value.""" for type, node in basic_types(): if isinstance(value, type): break else: node = OtherNode if inspect.isclass(value): node = ClassNode elif hasattr(value, "__class__"): node = ObjectNode return node(parent=self, name=name, value=value, readonly=self.readonly)
class IWidget(Interface): """ The base interface for all pyface widgets. Pyface widgets delegate to a toolkit specific control. """ #: The toolkit specific control that represents the widget. control = Any() #: The control's optional parent control. parent = Any() #: Whether or not the control is visible visible = Bool(True) #: Whether or not the control is enabled enabled = Bool(True) # ------------------------------------------------------------------------ # 'IWidget' interface. # ------------------------------------------------------------------------ def show(self, visible): """ Show or hide the widget. Parameters ---------- visible : bool Visible should be ``True`` if the widget should be shown. """ def enable(self, enabled): """ Enable or disable the widget. Parameters ---------- enabled : bool The enabled state to set the widget to. """ def create(self): """ Creates the toolkit specific control. This method should create the control and assign it to the :py:attr:``control`` trait. """ def destroy(self): """ Destroy the control if it exists. """ # ------------------------------------------------------------------------ # Protected 'IWidget' interface. # ------------------------------------------------------------------------ def _create_control(self, parent): """ Create toolkit specific control that represents the widget. Parameters ---------- parent : toolkit control The toolkit control to be used as the parent for the widget's control. Returns ------- control : toolkit control A control for the widget. """ def _add_event_listeners(self): """ Set up toolkit-specific bindings for events """ def _remove_event_listeners(self): """ Remove toolkit-specific bindings for events """
class _ActorEditor(Editor): """ An editor for TVTK scenes. """ # The editor is scrollable, so override the default. scrollable = Bool(True) # Internal GUI traits. _sizer = Any() _scene = Any() #### Public 'Editor' interface ############################################# def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ factory = self.factory self.control = wx.Panel(parent, -1) self._sizer = wx.BoxSizer(wx.VERTICAL) self.control.SetSizer(self._sizer) self._create_scene() def update_editor(self): """ Updates the editor when the object trait changes external to the editor. """ # Everything should really be handled elsewhere in trait notifications. # Just pass here. pass def dispose(self): """ Disposes of the contents of an editor. """ # Remove notifications. self._setup_scene_notifications(remove=True) # Remove the current scene. if self._scene is not None: self._scene.close() self._scene = None self._sizer = None # This will destroy self.control and all of its children, including the # scene's control. super(_ActorEditor, self).dispose() #### Private '_ActorEditor' interface ################################## def _create_scene(self): """ Create the TVTK scene widget. """ factory = self.factory self._scene = factory.scene_class(self.control, **factory.scene_kwds) scene = self._scene # Disable rendering on the scene until we're finished. scene.disable_render = True # Add all of the actors in the current actor map. for obj, actors in self.value.items(): self._add_actors_widgets(actors) # Set up Traits notifications. self._setup_scene_notifications() # Re-enable rendering. scene.disable_render = False # Ensure the scene's wx control is sized to fill our view's area. Note # that the sizer doesn't automatically layout its contents upon adding # a new child so we have to force it to do a layout. self._sizer.Add(scene.control, 1, wx.EXPAND) self._sizer.Layout() wx.EVT_IDLE(scene.control, None) # Force a render. scene.render() def _setup_scene_notifications(self, remove=False): """ Set up or remove all of the Trait notifications that control the scene widget. """ self.object.on_trait_change( self._set_scene_disable_render, name=self.factory.disable_render_name, remove=remove, ) self.object.on_trait_event( self._scene.render, name=self.factory.do_render_name, remove=remove, ) self.object.on_trait_change( self._actors_changed, name=self.name + '_items', remove=remove, ) self.object.on_trait_change( self._actor_map_changed, name=self.name, remove=remove, ) def _set_scene_disable_render(self, new): """ A callback for Traits notifications. """ self._scene.disable_render = new def _actors_changed(self, event): """ Handle the event of the actors in the actor map changing. """ scene = self._scene # Temporarily turn off rendering. We (re)store the old value of # disable_render because it may already be True. old_disable_render = scene.disable_render scene.disable_render = True try: for obj, actors in event.removed.items(): self._remove_actors_widgets(actors) for obj, actors in event.added.items(): self._add_actors_widgets(actors) for obj, actors in event.changed.items(): # The actors in the event are the old ones. Grab the new ones # from the actor map itself. self._remove_actors_widgets(actors) self._add_actors_widgets(self.value[obj]) finally: scene.disable_render = old_disable_render scene.render() def _actor_map_changed(self, object, name, old, new): """ Handle the case when the entire actor map is set to something else. """ scene = self._scene # Temporarily turn off rendering. We (re)store the old value of # disable_render because it may already be True. old_disable_render = scene.disable_render scene.disable_render = True try: for obj, actors in old.items(): self._remove_actors_widgets(actors) for obj, actors in new.items(): self._add_actors_widgets(actors) finally: scene.disable_render = old_disable_render scene.render() def _separate_actors_widgets(self, actors_widgets): """Given a sequence (or single) of actors or widgets, this returns a list of just the actors and another of just the widgets. """ if not hasattr(actors_widgets, '__getitem__'): actors_widgets = [actors_widgets] actors = [] widgets = [] for actor in actors_widgets: if actor.is_a('vtk3DWidget'): widgets.append(actor) else: actors.append(actor) return actors, widgets def _add_actors_widgets(self, actors_widgets): """Add actors and widgets to scene.""" scene = self._scene actors, widgets = self._separate_actors_widgets(actors_widgets) scene.add_actors(actors) scene.add_widgets(widgets) def _remove_actors_widgets(self, actors_widgets): """Remove actors and widgets from scene.""" scene = self._scene actors, widgets = self._separate_actors_widgets(actors_widgets) scene.remove_actors(actors) scene.remove_widgets(widgets)
class WorkflowItem(HasStrictTraits): """ The basic unit of a Workflow: wraps an operation and a list of views. Notes ----- Because we serialize instances of this, we have to pay careful attention to which traits are ``transient`` (and aren't serialized) """ # the operation's id friendly_id = DelegatesTo('operation') # the operation's name name = DelegatesTo('operation') # the operation this Item wraps operation = Instance(IOperation, copy="ref") # for the vertical notebook view, is this page deletable? deletable = Bool(True) # the handler that's associated with this operation; we get it from the # operation plugin, and it controls what operation traits are in the UI # and any special handling (heh) of them. since the handler doesn't # maintain any state, we can make and destroy as needed. operation_handler = Property(depends_on='operation', trait=Instance(Handler), transient=True) operation_traits = View( Item('operation_handler', style='custom', show_label=False)) # the Experiment that is the result of applying *operation* to the # previous_wi WorkflowItem's ``result`` result = Instance(Experiment, transient=True) # the channels, conditions and statistics from result. usually these would be # Properties (ie, determined dynamically), but that's hard with the # multiprocess model. channels = List(Str, status=True) conditions = Dict(Str, pd.Series, status=True) metadata = Dict(Str, Any, status=True) statistics = Dict(Tuple(Str, Str), pd.Series, status=True) # the IViews associated with this operation views = List(IView, copy="ref") # the currently selected view current_view = Instance(IView, copy="ref") # the handler for the currently selected view current_view_handler = Property(depends_on='current_view', trait=Instance(Handler), transient=True) current_view_traits = View( Item('current_view_handler', style='custom', show_label=False)) # the view for the plot params plot_params_traits = View( Item('current_view_handler', editor=InstanceEditor(view='plot_params_traits'), style='custom', show_label=False)) # the view for the current plot current_plot_view = View( Item('current_view_handler', editor=InstanceEditor(view='current_plot_view'), style='custom', show_label=False)) # the default view for this workflow item default_view = Instance(IView, copy="ref") # the previous_wi WorkflowItem in the workflow previous_wi = Instance('WorkflowItem', transient=True) # the next_wi WorkflowItem in the workflow next_wi = Instance('WorkflowItem', transient=True) # is the wi valid? # MAGIC: first value is the default status = Enum("invalid", "estimating", "applying", "valid", "loading", status=True) # report the errors and warnings op_error = Str(status=True) op_error_trait = Str(status=True) op_warning = Str(status=True) op_warning_trait = Str(status=True) estimate_error = Str(status=True) estimate_warning = Str(status=True) view_error = Str(status=True) view_error_trait = Str(status=True) view_warning = Str(status=True) view_warning_trait = Str(status=True) # the central event to kick of WorkflowItem update logic changed = Event # the icon for the vertical notebook view. Qt specific, sadly. icon = Property(depends_on='status', transient=True) # synchronization primitive for updating wi traits lock = Instance(threading.RLock, (), transient=True) # synchronization primitives for plotting matplotlib_events = Any(transient=True) plot_lock = Any(transient=True) # events to track number of times apply() and plot() are called apply_called = Event plot_called = Event @cached_property def _get_icon(self): if self.status == "valid": return QtGui.QStyle.SP_DialogApplyButton # @UndefinedVariable elif self.status == "estimating" or self.status == "applying": return QtGui.QStyle.SP_BrowserReload # @UndefinedVariable else: # self.valid == "invalid" or None return QtGui.QStyle.SP_DialogCancelButton # @UndefinedVariable @cached_property def _get_operation_handler(self): return self.operation.handler_factory(model=self.operation, context=self) @cached_property def _get_current_view_handler(self): if self.current_view: return self.current_view.handler_factory(model=self.current_view, context=self) else: return None def __str__(self): return "<{}: {}>".format(self.__class__.__name__, self.operation.__class__.__name__) def __repr__(self): return "<{}: {}>".format(self.__class__.__name__, self.operation.__class__.__name__)
class DigSource(HasPrivateTraits): """Expose digitization information from a file. Parameters ---------- file : File Path to the BEM file (*.fif). Attributes ---------- fid : Array, shape = (3, 3) Each row contains the coordinates for one fiducial point, in the order Nasion, RAP, LAP. If no file is set all values are 0. """ file = File(exists=True, filter=['*.fif']) inst_fname = Property(Str, depends_on='file') inst_dir = Property(depends_on='file') _info = Property(depends_on='file') points_filter = Any(desc="Index to select a subset of the head shape " "points") n_omitted = Property(Int, depends_on=['points_filter']) # head shape _hsp_points = Property(depends_on='_info', desc="Head shape points in the file (n x 3 array)") points = Property(depends_on=['_hsp_points', 'points_filter'], desc="Head shape points selected by the filter (n x 3 " "array)") # fiducials lpa = Property(depends_on='_info', desc="LPA coordinates (1 x 3 array)") nasion = Property(depends_on='_info', desc="Nasion coordinates (1 x 3 array)") rpa = Property(depends_on='_info', desc="RPA coordinates (1 x 3 array)") # EEG eeg_points = Property(depends_on='_info', desc="EEG sensor coordinates (N x 3 array)") view = View( VGroup(Item('file'), Item('inst_fname', show_label=False, style='readonly'))) @cached_property def _get_n_omitted(self): if self.points_filter is None: return 0 else: return np.sum(self.points_filter == False) # noqa: E712 @cached_property def _get__info(self): if self.file: info = None _, tree, _ = fiff_open(self.file) if len(dir_tree_find(tree, FIFF.FIFFB_MEAS_INFO)) > 0: info = read_info(self.file, verbose=False) elif len(dir_tree_find(tree, FIFF.FIFFB_ISOTRAK)) > 0: info = read_dig_montage(fif=self.file) if info is None: error( None, "The selected FIFF file does not contain " "digitizer information. Please select a different " "file.", "Error Reading FIFF File") self.reset_traits(['file']) return elif isinstance(info, DigMontage): info.transform_to_head() digs = list() _append_fiducials(digs, info.lpa, info.nasion, info.rpa) for idx, pos in enumerate(info.hsp): dig = { 'coord_frame': FIFF.FIFFV_COORD_HEAD, 'ident': idx, 'kind': FIFF.FIFFV_POINT_EXTRA, 'r': pos } digs.append(dig) info = _empty_info(1) info['dig'] = digs else: # check that all fiducial points are present has_point = { FIFF.FIFFV_POINT_LPA: False, FIFF.FIFFV_POINT_NASION: False, FIFF.FIFFV_POINT_RPA: False } for d in info['dig']: if d['kind'] == FIFF.FIFFV_POINT_CARDINAL: has_point[d['ident']] = True if not all(has_point.values()): points = _fiducial_coords(info['dig']) if len(points) == 3: _append_fiducials(info['dig'], *points.T) else: missing = [] if not has_point[FIFF.FIFFV_POINT_LPA]: missing.append('LPA') if not has_point[FIFF.FIFFV_POINT_NASION]: missing.append('Nasion') if not has_point[FIFF.FIFFV_POINT_RPA]: missing.append('RPA') error( None, "The selected FIFF file does not contain " "all cardinal points (missing: %s). Please " "select a different file." % ', '.join(missing), "Error Reading FIFF File") self.reset_traits(['file']) return return info @cached_property def _get_inst_dir(self): return op.dirname(self.file) @cached_property def _get_inst_fname(self): if self.file: return op.basename(self.file) else: return '-' @cached_property def _get__hsp_points(self): if not self._info: return np.zeros((0, 3)) points = np.array([ d['r'] for d in self._info['dig'] if d['kind'] == FIFF.FIFFV_POINT_EXTRA ]) points = np.empty((0, 3)) if len(points) == 0 else points return points @cached_property def _get_points(self): if self.points_filter is None: return self._hsp_points else: return self._hsp_points[self.points_filter] def _cardinal_point(self, ident): """Coordinates for a cardinal point.""" if self._info: for d in self._info['dig']: if (d['kind'] == FIFF.FIFFV_POINT_CARDINAL and d['ident'] == ident): return d['r'][None, :] return np.zeros((1, 3)) @cached_property def _get_nasion(self): return self._cardinal_point(FIFF.FIFFV_POINT_NASION) @cached_property def _get_lpa(self): return self._cardinal_point(FIFF.FIFFV_POINT_LPA) @cached_property def _get_rpa(self): return self._cardinal_point(FIFF.FIFFV_POINT_RPA) @cached_property def _get_eeg_points(self): if self._info: return np.array([ d['r'] for d in self._info['dig'] if d['kind'] == FIFF.FIFFV_POINT_EEG ]) else: return np.empty((0, 3)) def _file_changed(self): self.reset_traits(('points_filter', ))
class Axis(ConfigLoadable): ''' ''' id = Int # name = Str position = Float negative_limit = Float positive_limit = Float pdir = Str parent = Any(transient=True) calculate_parameters = Bool(True) drive_ratio = Float(1) velocity = Property(depends_on='_velocity') _velocity = Float(enter_set=True, auto_set=False) acceleration = Property(depends_on='_acceleration') _acceleration = Float(enter_set=True, auto_set=False) deceleration = Property(depends_on='_deceleration') _deceleration = Float(enter_set=True, auto_set=False) machine_velocity = Float machine_acceleration = Float machine_deceleration = Float # sets handled by child class nominal_velocity = Float nominal_acceleration = Float nominal_deceleration = Float sign = CInt(1) def _get_velocity(self): return self._velocity def _get_acceleration(self): return self._acceleration def _get_deceleration(self): return self._deceleration def upload_parameters_to_device(self): pass @on_trait_change('_velocity, _acceleration, _deceleration') def update_machine_values(self, obj, name, old, new): setattr(self, 'machine{}'.format(name), new) def _calibration_changed(self): self.parent.update_axes() def simple_view(self): v = View( Item('calculate_parameters'), Item('velocity', format_str='%0.3f', enabled_when='not calculate_parameters'), Item('acceleration', format_str='%0.3f', enabled_when='not calculate_parameters'), Item('deceleration', format_str='%0.3f', enabled_when='not calculate_parameters'), Item('drive_ratio')) return v def full_view(self): return self.simple_view() def dump(self): ''' ''' pass # self.loaded = False # # p = os.path.join(self.pdir, '.%s' % self.name) # with open(p, 'w') as f: # pickle.dump(self, f) # def load_parameters_from_config(self, path): # self.config_path = path # self._load_parameters_from_config(path) # # def load_parameters(self, pdir): # ''' # ''' # # self.pdir = pdir # # p = os.path.join(pdir, '.%s' % self.name) # # # # if os.path.isfile(p): # # return p # # else: # self.load(pdir) def save(self): pass def ask(self, cmd): return self.parent.ask(cmd) def _get_parameters(self, path): ''' ''' # cp = ConfigParser.ConfigParser() # cp.read()) params = [] # if path is None: if not os.path.isfile(path): path = os.path.join(path, '{}axis.cfg'.format(self.name)) cp = self.get_configuration(path) if cp: params = [item for s in cp.sections() for item in cp.items(s)] # for ai in a: # print ai # # for s in cp.sections(): # for i in cp.items(s): # params.append(i) return params def _validate_float(self, v): try: v = float(v) return v except ValueError: pass
class GridModel(HasTraits): """ A model that provides data for a grid. """ # fixme : factor this default model into "SimpleGridModel" or similar # An optional 2-dimensional list/array containing the grid data. data = Any() # The rows in the model. rows = List(GridRow) # The columns in the model. columns = List(GridColumn) # Show row headers? show_row_headers = Bool(True) # Show column headers? show_column_headers = Bool(True) # Fired when the data in the model has changed. model_changed = Event() def __init__(self, **traits): """ Create a new grid model. """ # Base class constructors. HasTraits.__init__(self, **traits) # The wx virtual table hook. self._grid_table_base = _GridTableBase(self) if len(self.columns) == 0 and self.data is not None: print("Building default table column model") columns = [] # Assume data is rectangular and use the length of the first row. for i in range(len(self.data[0])): columns.append(GridColumn(label=str(i))) self.columns = columns return # ------------------------------------------------------------------------ # 'wxGridTableBase' interface. # ------------------------------------------------------------------------ def GetNumberRows(self): """ Return the number of rows in the model. """ return len(self.data) def GetNumberCols(self): """ Return the number of columns in the model. """ return len(self.columns) def IsEmptyCell(self, row, col): """ Is the specified cell empty? """ try: return not self.data[row][col] except IndexError: return True # Get/Set values in the table. The Python versions of these methods can # handle any data-type, (as long as the Editor and Renderer understands the # type too,) not just strings as in the C++ version. def GetValue(self, row, col): """ Get the value at the specified row and column. """ try: return self.data[row][col] except IndexError: pass return "" def SetValue(self, row, col, value): """ Set the value at the specified row and column. """ label = self.GetColLabelValue(col) try: self.data[row][col] = value except IndexError: # Add a new row. self.data.append([0] * self.GetNumberCols()) self.data[row][col] = value # Tell the grid that we've added a row. # # N.B wxGridTableMessage(table, whatWeDid, howMany) message = GridTableMessage(self, GRIDTABLE_NOTIFY_ROWS_APPENDED, 1) # Trait event notification. self.model_changed = message def GetRowLabelValue(self, row): """ Called when the grid needs to display a row label. """ return str(row) def GetColLabelValue(self, col): """ Called when the grid needs to display a column label. """ return self.columns[col].label def GetTypeName(self, row, col): """ Called to determine the kind of editor/renderer to use. This doesn't necessarily have to be the same type used natively by the editor/renderer if they know how to convert. """ return self.columns[col].type def CanGetValueAs(self, row, col, type_name): """ Called to determine how the data can be fetched. This allows you to enforce some type-safety in the grid. """ column_typename = self.GetTypeName(row, col) return type_name == column_typename def CanSetValueAs(self, row, col, type_name): """ Called to determine how the data can be stored. This allows you to enforce some type-safety in the grid. """ return self.CanGetValueAs(row, col, type_name) def DeleteRows(self, pos, num_rows): """ Called when the view is deleting rows. """ del self.data[pos:pos + num_rows] # Tell the grid that we've deleted some rows. # # N.B Because of a bug in wxPython we have to send a "rows appended" # --- message with a negative number, instead of the "rows deleted" # message 8^() TINSTAFS! message = GridTableMessage(self, GRIDTABLE_NOTIFY_ROWS_APPENDED, -num_rows) # Trait event notification. self.model_changed = message return True
class TransformStatisticOp(HasStrictTraits): """ Apply a function to a statistic, creating a new statistic. The function can be applied to the entire statistic, or it can be applied individually to groups of the statistic. The function should take a `pandas.Series` as its only argument. Return type is arbitrary, but a to be used with the rest of Cytoflow it should probably be a numeric type or an iterable of numeric types. As a special case, if the function returns a pandas.Series *with the same index that it was passed*, it is interpreted as a transformation. The resulting statistic will have the same length, index names and index levels as the original statistic. Attributes ---------- name : Str The operation name. Becomes the first element in the Experiment.statistics key tuple. statistic : Tuple(Str, Str) The statistic to apply the function to. function : Callable The function used to transform the statistic. `function` must take a Series as its only parameter. The return type is arbitrary, to work with the rest of Cytoflow it should probably be a numeric type or an iterable of numeric types.. If `statistic_name` is unset, the name of the function becomes the second in element in the Experiment.statistics key tuple. statistic_name : Str The name of the function; if present, becomes the second element in the Experiment.statistics key tuple. by : List(Str) A list of metadata attributes to aggregate the input statistic before applying the function. For example, if the statistic has two indices `Time` and `Dox`, setting `by = ["Time", "Dox"]` will apply `function` separately to each subset of the data with a unique combination of `Time` and `Dox`. fill : Any (default = 0) Value to use in the statistic if a slice of the data is empty. Examples -------- >>> stats_op = ChannelStatisticOp(name = "Mean", ... channel = "Y2-A", ... function = np.mean, ... by = ["Dox"]) >>> ex2 = stats_op.apply(ex) >>> log_op = TransformStatisticOp(name = "LogMean", ... statistic = ("Mean", "mean"), ... function = np.log) >>> ex3 = log_op.apply(ex2) """ id = Constant('edu.mit.synbio.cytoflow.operations.transform_statistic') friendly_id = Constant("Transform Statistic") name = CStr statistic = Tuple(Str, Str) function = Callable statistic_name = Str by = List(Str) fill = Any(0) def apply(self, experiment): if not experiment: raise util.CytoflowOpError("Must specify an experiment") if not self.name: raise util.CytoflowOpError("Must specify a name") if not self.statistic: raise util.CytoflowViewError("Statistic not set") if self.statistic not in experiment.statistics: raise util.CytoflowViewError( "Can't find the statistic {} in the experiment".format( self.statistic)) else: stat = experiment.statistics[self.statistic] if not self.function: raise util.CytoflowOpError("Must specify a function") if not self.by: raise util.CytoflowOpError("Must specify some grouping conditions " "in 'by'") for b in self.by: if b not in stat.index.names: raise util.CytoflowOpError("{} is not a statistic index; " " must be one of {}".format( b, stat.index.names)) data = stat.reset_index() idx = pd.MultiIndex.from_product([data[x].unique() for x in self.by], names=self.by) new_stat = pd.Series(data=self.fill, index=idx, dtype=np.dtype(object)).sort_index() for group in data[self.by].itertuples(index=False, name=None): s = stat.xs(group, level=self.by) if len(s) == 0: continue if len(group) == 1: group = group[0] try: new_stat[group] = self.function(s) except Exception as e: raise util.CytoflowOpError("On group {}, your function threw " "an error: {}".format(group, e)) # check for, and warn about, NaNs. if np.any(np.isnan(new_stat[group])): warn("Category {} returned {}".format(group, x), util.CytoflowOpWarning) matched_series = True for group in data[self.by].itertuples(index=False): s = stat.xs(group, level=self.by) if len(group) == 1: group = group[0] if isinstance(new_stat[group], pd.Series) and \ s.index.equals(new_stat[group].index): pass else: matched_series = False break if matched_series: names = set(self.by) | set(new_stat.iloc[0].index.names) new_stat = pd.concat(new_stat.to_dict(), names=names) # try to convert to numeric, but if there are non-numeric bits ignore new_stat = pd.to_numeric(new_stat, errors='ignore') new_experiment = experiment.clone() new_experiment.history.append( self.clone_traits(transient=lambda t: True)) if self.statistic_name: new_experiment.statistics[(self.name, self.statistic_name)] = new_stat else: new_experiment.statistics[(self.name, self.function.__name__)] = new_stat return new_experiment
class Surf(Pipeline): """ Plots a surface using regularly-spaced elevation data supplied as a 2D array. **Function signatures**:: surf(s, ...) surf(x, y, s, ...) surf(x, y, f, ...) s is the elevation matrix, a 2D array, where indices along the first array axis represent x locations, and indices along the second array axis represent y locations. x and y can be 1D or 2D arrays such as returned by numpy.ogrid or numpy.mgrid. Arrays returned by numpy.meshgrid require a transpose first to obtain correct indexing order. The points should be located on an orthogonal grid (possibly non-uniform). In other words, all the points sharing a same index in the s array need to have the same x or y value. For arbitrary-shaped position arrays (non-orthogonal grids), see the mesh function. If only 1 array s is passed, the x and y arrays are assumed to be made from the indices of arrays, and an uniformly-spaced data set is created. If 3 positional arguments are passed the last one must be an array s, or a callable, f, that returns an array. x and y give the coordinates of positions corresponding to the s values.""" _source_function = Callable(array2d_source) _pipeline = [WarpScalarFactory, PolyDataNormalsFactory, SurfaceFactory] warp_scale = Any(1, help="""scale of the z axis (warped from the value of the scalar). By default this scale is a float value. If you specify 'auto', the scale is calculated to give a pleasant aspect ratio to the plot, whatever the bounds of the data. If you specify a value for warp_scale in addition to an extent, the warp scale will be determined by the warp_scale, and the plot be positioned along the z axis with the zero of the data centered on the center of the extent. If you are using explicit extents, this is the best way to control the vertical scale of your plots. If you want to control the extent (or range) of the surface object, rather than its scale, see the `extent` keyword argument. """) mask = Array(help="""boolean mask array to suppress some data points. Note: this works based on colormapping of scalars and will not work if you specify a solid color using the `color` keyword.""") def __call_internal__(self, *args, **kwargs): """ Override the call to be able to scale automatically the axis. """ self.source = self._source_function(*args, **kwargs) kwargs.pop('name', None) # Deal with both explicit warp scale and extent, this is # slightly hairy. The wigner example is a good test case for # this. if not 'warp_scale' in kwargs and not 'extent' in kwargs: try: xi, xf, yi, yf, _, _ = self.source.data.bounds zi, zf = self.source.data.scalar_range except AttributeError: xi, xf, yi, yf, _, _ = self.source.image_data.bounds zi, zf = self.source.image_data.scalar_range aspect_ratios = [(zf - zi) / (xf - xi), (zf - zi) / (yf - yi)] if min(aspect_ratios) < 0.01 or max(aspect_ratios) > 100: print('Warning: the range of your scalar values differs by ' \ 'more than a factor 100 than the range of the grid values ' \ 'and you did not '\ 'specify a warp_scale. You could try warp_scale="auto".') if 'warp_scale' in kwargs and not kwargs['warp_scale'] == 'auto' \ and 'extent' in kwargs: # XXX: I should use the logging module. print('Warning: both warp_scale and extent keyword argument ' \ 'specified, the z bounds of the extents will be overridden') xi, xf, yi, yf, zi, zf = kwargs['extent'] zo = 0.5 * (zi + zf) try: si, sf = self.source.data.scalar_range except AttributeError: si, sf = self.source.image_data.scalar_range z_span = kwargs['warp_scale'] * abs(sf - si) zi = zo + si * kwargs['warp_scale'] zf = zi + z_span kwargs['extent'] = (xi, xf, yi, yf, zi, zf) kwargs['warp_scale'] = 1 elif kwargs.get('warp_scale', 1) == 'auto': if 'extent' in kwargs: if 'warp_scale' in kwargs: print("Warning: extent specified, warp_scale='auto' " \ "ignored.") else: try: xi, xf, yi, yf, _, _ = self.source.data.bounds zi, zf = self.source.data.scalar_range except AttributeError: xi, xf, yi, yf, _, _ = self.source.image_data.bounds zi, zf = self.source.image_data.scalar_range z0 = zf - zi dz = 0.3 * ((xf - xi) + (yf - yi)) zi = z0 - 0.5 * dz zf = z0 + 0.5 * dz kwargs['extent'] = (xi, xf, yi, yf, zi, zf) kwargs['warp_scale'] = 1. self.store_kwargs(kwargs) # Copy the pipeline so as not to modify it for the next call self.pipeline = self._pipeline[:] return self.build_pipeline()
class TabularEditor(Editor): """ A traits UI editor for editing tabular data (arrays, list of tuples, lists of objects, etc). """ #-- Trait Definitions ---------------------------------------------------- # The event fired when a table update is needed: update = Event # The event fired when a simple repaint is needed: refresh = Event # The current set of selected items (which one is used depends upon the # initial state of the editor factory 'multi_select' trait): selected = Any multi_selected = List # The current set of selected item indices (which one is used depends upon # the initial state of the editor factory 'multi_select' trait): selected_row = Int(-1) multi_selected_rows = List(Int) # The most recently actived item and its index: activated = Any(comparison_mode=NO_COMPARE) activated_row = Int(comparison_mode=NO_COMPARE) # The most recent left click data: clicked = Instance('TabularEditorEvent') # The most recent left double click data: dclicked = Instance('TabularEditorEvent') # The most recent right click data: right_clicked = Instance('TabularEditorEvent') # The most recent right double click data: right_dclicked = Instance('TabularEditorEvent') # The most recent column click data: column_clicked = Instance('TabularEditorEvent') # The most recent column click data: column_right_clicked = Instance('TabularEditorEvent') # The event triggering scrolling. scroll_to_row = Event(Int) # Is the tabular editor scrollable? This value overrides the default. scrollable = True # Row index of item to select after rebuilding editor list: row = Any # Should the selected item be edited after rebuilding the editor list: edit = Bool(False) # The adapter from trait values to editor values: adapter = Instance(TabularAdapter) # The table model associated with the editor: model = Instance(TabularModel) # Dictionary mapping image names to QIcons images = Any({}) # Dictionary mapping ImageResource objects to QIcons image_resources = Any({}) # An image being converted: image = Image header_event_filter = Any() widget_factory = Callable(lambda *args, **kwds: _TableView(*args, **kwds)) #------------------------------------------------------------------------- # Editor interface: #------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ factory = self.factory adapter = self.adapter = factory.adapter self.model = TabularModel(editor=self) # Create the control control = self.control = self.widget_factory(self) # Set up the selection listener if factory.multi_select: self.sync_value(factory.selected, 'multi_selected', 'both', is_list=True) self.sync_value(factory.selected_row, 'multi_selected_rows', 'both', is_list=True) else: self.sync_value(factory.selected, 'selected', 'both') self.sync_value(factory.selected_row, 'selected_row', 'both') # Connect to the mode specific selection handler if factory.multi_select: slot = self._on_rows_selection else: slot = self._on_row_selection selection_model = self.control.selectionModel() selection_model.selectionChanged.connect(slot) # Synchronize other interesting traits as necessary: self.sync_value(factory.update, 'update', 'from') self.sync_value(factory.refresh, 'refresh', 'from') self.sync_value(factory.activated, 'activated', 'to') self.sync_value(factory.activated_row, 'activated_row', 'to') self.sync_value(factory.clicked, 'clicked', 'to') self.sync_value(factory.dclicked, 'dclicked', 'to') self.sync_value(factory.right_clicked, 'right_clicked', 'to') self.sync_value(factory.right_dclicked, 'right_dclicked', 'to') self.sync_value(factory.column_clicked, 'column_clicked', 'to') self.sync_value(factory.column_right_clicked, 'column_right_clicked', 'to') self.sync_value(factory.scroll_to_row, 'scroll_to_row', 'from') # Connect other signals as necessary control.activated.connect(self._on_activate) control.clicked.connect(self._on_click) control.clicked.connect(self._on_right_click) control.doubleClicked.connect(self._on_dclick) control.horizontalHeader().sectionClicked.connect( self._on_column_click) control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) control.customContextMenuRequested.connect(self._on_context_menu) self.header_event_filter = HeaderEventFilter(self) control.horizontalHeader().installEventFilter(self.header_event_filter) # Make sure we listen for 'items' changes as well as complete list # replacements: try: self.context_object.on_trait_change(self.update_editor, self.extended_name + '_items', dispatch='ui') except: pass # If the user has requested automatic update, attempt to set up the # appropriate listeners: if factory.auto_update: self.context_object.on_trait_change(self.refresh_editor, self.extended_name + '.-', dispatch='ui') # Create the mapping from user supplied images to QImages: for image_resource in factory.images: self._add_image(image_resource) # Refresh the editor whenever the adapter changes: self.on_trait_change(self.refresh_editor, 'adapter.+update', dispatch='ui') # Rebuild the editor columns and headers whenever the adapter's # 'columns' changes: self.on_trait_change(self.update_editor, 'adapter.columns', dispatch='ui') def dispose(self): """ Disposes of the contents of an editor. """ self.context_object.on_trait_change(self.update_editor, self.extended_name + '_items', remove=True) if self.factory.auto_update: self.context_object.on_trait_change(self.refresh_editor, self.extended_name + '.-', remove=True) self.on_trait_change(self.refresh_editor, 'adapter.+update', remove=True) self.on_trait_change(self.update_editor, 'adapter.columns', remove=True) self.adapter.cleanup() super(TabularEditor, self).dispose() def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ if not self._no_update: self.model.beginResetModel() self.model.endResetModel() if self.factory.multi_select: self._multi_selected_changed(self.multi_selected) else: self._selected_changed(self.selected) #------------------------------------------------------------------------- # TabularEditor interface: #------------------------------------------------------------------------- def refresh_editor(self): """ Requests the table view to redraw itself. """ self.control.viewport().update() def callx(self, func, *args, **kw): """ Call a function without allowing the editor to update. """ old = self._no_update self._no_update = True try: func(*args, **kw) finally: self._no_update = old def setx(self, **keywords): """ Set one or more attributes without allowing the editor to update. """ old = self._no_notify self._no_notify = True try: for name, value in list(keywords.items()): setattr(self, name, value) finally: self._no_notify = old #------------------------------------------------------------------------- # UI preference save/restore interface: #------------------------------------------------------------------------- def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ cws = prefs.get('cached_widths') num_columns = len(self.adapter.columns) if cws is not None and num_columns == len(cws): for column in range(num_columns): self.control.setColumnWidth(column, cws[column]) def save_prefs(self): """ Returns any user preference information associated with the editor. """ widths = [ self.control.columnWidth(column) for column in range(len(self.adapter.columns)) ] return {'cached_widths': widths} #------------------------------------------------------------------------- # Private methods: #------------------------------------------------------------------------- def _add_image(self, image_resource): """ Adds a new image to the image map. """ image = image_resource.create_icon() self.image_resources[image_resource] = image self.images[image_resource.name] = image return image def _get_image(self, image): """ Converts a user specified image to a QIcon. """ if isinstance(image, str): self.image = image image = self.image if isinstance(image, ImageResource): result = self.image_resources.get(image) if result is not None: return result return self._add_image(image) return self.images.get(image) def _mouse_click(self, index, trait): """ Generate a TabularEditorEvent event for a specified model index and editor trait name. """ event = TabularEditorEvent(editor=self, row=index.row(), column=index.column()) setattr(self, trait, event) #-- Trait Event Handlers ------------------------------------------------- def _update_changed(self): self.update_editor() def _refresh_changed(self): self.refresh_editor() def _selected_changed(self, new): if not self._no_update: try: selected_row = self.value.index(new) except Exception as e: if TRAITS_DEBUG: from traits.api import raise_to_debug raise_to_debug() else: self._selected_row_changed(selected_row) def _selected_row_changed(self, selected_row): if not self._no_update: smodel = self.control.selectionModel() if selected_row == -1: smodel.clearSelection() else: smodel.select( self.model.index(selected_row, 0), QtGui.QItemSelectionModel.ClearAndSelect | QtGui.QItemSelectionModel.Rows) def _multi_selected_changed(self, new): if not self._no_update: values = self.value try: rows = [values.index(i) for i in new] except: pass else: self._multi_selected_rows_changed(rows) def _multi_selected_items_changed(self, event): values = self.value try: added = [values.index(item) for item in event.added] removed = [values.index(item) for item in event.removed] except: pass else: list_event = TraitListEvent(0, added, removed) self._multi_selected_rows_items_changed(list_event) def _multi_selected_rows_changed(self, selected_rows): if not self._no_update: smodel = self.control.selectionModel() selection = QtGui.QItemSelection() for row in selected_rows: selection.select(self.model.index(row, 0), self.model.index(row, 0)) smodel.clearSelection() smodel.select( selection, QtGui.QItemSelectionModel.Select | QtGui.QItemSelectionModel.Rows) def _multi_selected_rows_items_changed(self, event): smodel = self.control.selectionModel() for row in event.removed: smodel.select( self.model.index(row, 0), QtGui.QItemSelectionModel.Deselect | QtGui.QItemSelectionModel.Rows) for row in event.added: smodel.select( self.model.index(row, 0), QtGui.QItemSelectionModel.Select | QtGui.QItemSelectionModel.Rows) scroll_to_row_hint_map = { 'center': QtGui.QTableView.PositionAtCenter, 'top': QtGui.QTableView.PositionAtTop, 'bottom': QtGui.QTableView.PositionAtBottom, 'visible': QtGui.QTableView.EnsureVisible, } def _scroll_to_row_changed(self, row): """ Scroll to the given row. """ scroll_hint = self.scroll_to_row_hint_map.get( self.factory.scroll_to_row_hint, self.control.PositionAtCenter) self.control.scrollTo(self.model.index(row, 0), scroll_hint) #-- Table Control Event Handlers ----------------------------------------- def _on_activate(self, index): """ Handle a cell being activated. """ self.activated_row = row = index.row() self.activated = self.adapter.get_item(self.object, self.name, row) def _on_click(self, index): """ Handle a cell being clicked. """ self._mouse_click(index, 'clicked') def _on_dclick(self, index): """ Handle a cell being double clicked. """ self._mouse_click(index, 'dclicked') def _on_column_click(self, column): event = TabularEditorEvent(editor=self, row=0, column=column) setattr(self, 'column_clicked', event) def _on_right_click(self, column): event = TabularEditorEvent(editor=self, row=0, column=column) setattr(self, 'right_clicked', event) def _on_column_right_click(self, column): event = TabularEditorEvent(editor=self, row=0, column=column) setattr(self, 'column_right_clicked', event) def _on_row_selection(self, added, removed): """ Handle the row selection being changed. """ self._no_update = True try: indexes = self.control.selectionModel().selectedRows() if len(indexes): self.selected_row = indexes[0].row() self.selected = self.adapter.get_item(self.object, self.name, self.selected_row) else: self.selected_row = -1 self.selected = None finally: self._no_update = False def _on_rows_selection(self, added, removed): """ Handle the rows selection being changed. """ self._no_update = True try: indexes = self.control.selectionModel().selectedRows() selected_rows = [] selected = [] for index in indexes: row = index.row() selected_rows.append(row) selected.append( self.adapter.get_item(self.object, self.name, row)) self.multi_selected_rows = selected_rows self.multi_selected = selected finally: self._no_update = False def _on_context_menu(self, pos): column, row = self.control.columnAt(pos.x()), self.control.rowAt( pos.y()) menu = self.adapter.get_menu(self.object, self.name, row, column) if menu: self._menu_context = { 'selection': self.object, 'object': self.object, 'editor': self, 'column': column, 'row': row, 'item': self.adapter.get_item(self.object, self.name, row), 'info': self.ui.info, 'handler': self.ui.handler } qmenu = menu.create_menu(self.control, self) qmenu.exec_(self.control.mapToGlobal(pos)) self._menu_context = None def _on_column_context_menu(self, pos): column = self.control.columnAt(pos.x()) menu = self.adapter.get_column_menu(self.object, self.name, -1, column) if menu: self._menu_context = { 'selection': self.object, 'object': self.object, 'editor': self, 'column': column, 'info': self.ui.info, 'handler': self.ui.handler } qmenu = menu.create_menu(self.control, self) qmenu.exec_(self.control.mapToGlobal(pos)) self._menu_context = None else: #If no menu is defined on the adapter, just trigger a click event. self._on_column_right_click(column)
class TimeSamples( SamplesGenerator ): """ Container for time data in `*.h5` format. This class loads measured data from h5 files and and provides information about this data. It also serves as an interface where the data can be accessed (e.g. for use in a block chain) via the :meth:`result` generator. """ #: Full name of the .h5 file with data. name = File(filter=['*.h5'], desc="name of data file") #: Basename of the .h5 file with data, is set automatically. basename = Property( depends_on = 'name', #filter=['*.h5'], desc="basename of data file") #: Calibration data, instance of :class:`~acoular.calib.Calib` class, optional . calib = Trait( Calib, desc="Calibration data") #: Number of channels, is set automatically / read from file. numchannels = CLong(0, desc="number of input channels") #: Number of time data samples, is set automatically / read from file. numsamples = CLong(0, desc="number of samples") #: The time data as array of floats with dimension (numsamples, numchannels). data = Any( transient = True, desc="the actual time data array") #: HDF5 file object h5f = Instance(tables.File, transient = True) # internal identifier digest = Property( depends_on = ['basename', 'calib.digest']) traits_view = View( ['name{File name}', ['sample_freq~{Sampling frequency}', 'numchannels~{Number of channels}', 'numsamples~{Number of samples}', '|[Properties]'], '|' ], title='Time data', buttons = OKCancelButtons ) @cached_property def _get_digest( self ): return digest(self) @cached_property def _get_basename( self ): return path.splitext(path.basename(self.name))[0] @on_trait_change('basename') def load_data( self ): """ Open the .h5 file and set attributes. """ if not path.isfile(self.name): # no file there self.numsamples = 0 self.numchannels = 0 self.sample_freq = 0 raise IOError("No such file: %s" % self.name) if self.h5f != None: try: self.h5f.close() except IOError: pass self.h5f = tables.open_file(self.name) self.data = self.h5f.root.time_data self.sample_freq = self.data.get_attr('sample_freq') (self.numsamples, self.numchannels) = self.data.shape def result(self, num=128): """ Python generator that yields the output block-wise. Parameters ---------- num : integer, defaults to 128 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block) . Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ if self.numsamples == 0: raise IOError("no samples available") i = 0 if self.calib: if self.calib.num_mics == self.numchannels: cal_factor = self.calib.data[newaxis] else: raise ValueError("calibration data not compatible: %i, %i" % \ (self.calib.num_mics, self.numchannels)) while i < self.numsamples: yield self.data[i:i+num]*cal_factor i += num else: while i < self.numsamples: yield self.data[i:i+num] i += num
class Test(HasTraits): i = Int(99) lf = List(Float) foo = Instance(Foo, ()) any = Any([1, 2, 3])
class Team(HasTraits): leader = Instance(Person) member_names = List(Str()) any_value = Any()
class TreeItem(HasTraits): """ A generic base-class for items in a tree data structure. """ # 'TreeItem' interface ------------------------------------------------- # Does this item allow children? allows_children = Bool(True) # The item's children. children = List(Instance("TreeItem")) # Arbitrary data associated with the item. data = Any() # Does the item have any children? has_children = Property(Bool) # The item's parent. parent = Instance("TreeItem") # ------------------------------------------------------------------------ # 'object' interface. # ------------------------------------------------------------------------ def __str__(self): """ Returns the informal string representation of the object. """ if self.data is None: s = "" else: s = str(self.data) return s # ------------------------------------------------------------------------ # 'TreeItem' interface. # ------------------------------------------------------------------------ # Properties ----------------------------------------------------------- # has_children def _get_has_children(self): """ True iff the item has children. """ return len(self.children) != 0 # Methods -------------------------------------------------------------# def append(self, child): """ Appends a child to this item. This removes the child from its current parent (if it has one). """ return self.insert(len(self.children), child) def insert(self, index, child): """ Inserts a child into this item at the specified index. This removes the child from its current parent (if it has one). """ if child.parent is not None: child.parent.remove(child) child.parent = self self.children.insert(index, child) return child def remove(self, child): """ Removes a child from this item. """ child.parent = None self.children.remove(child) return child def insert_before(self, before, child): """ Inserts a child into this item before the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(before) self.insert(index, child) return (index, child) def insert_after(self, after, child): """ Inserts a child into this item after the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(after) self.insert(index + 1, child) return (index, child)
class PlotApp(HasTraits): plotdata = Instance(ArrayPlotData) numpoints = Int(300) symbols = List() sym1 = Enum(values="symbols") sym2 = Enum(values="symbols") returns_plot = Instance(Plot) times_ds = Any() # arraydatasource for the time axis data corr_plot = Instance(Plot) corr_renderer = Any() traits_view = View( HSplit( Item( 'returns_plot', editor=ComponentEditor(), show_label=False), VGroup( VGroup( Item( 'sym1', width=-225), Item( 'sym2', width=-225), ), Item( 'corr_plot', editor=ComponentEditor(), show_label=False, width=-275), ), ), width=1024, height=500, resizable=True, title="Correlations of returns") def __init__(self, *symbols, **kwtraits): super(PlotApp, self).__init__(symbols=list(symbols), **kwtraits) self._create_data(*symbols) self._create_returns_plot() self._create_corr_plot() if len(self.symbols) > 1: self.sym2 = self.symbols[1] return def _create_returns_plot(self): plot = Plot(self.plotdata) plot.legend.visible = True #FIXME: The legend move tool doesn't seem to quite work right now #plot.legend.tools.append(LegendTool(plot.legend)) plot.x_axis = None x_axis = PlotAxis( plot, orientation="bottom", tick_generator=ScalesTickGenerator(scale=CalendarScaleSystem())) plot.overlays.append(x_axis) plot.x_grid.tick_generator = x_axis.tick_generator for i, name in enumerate(self.plotdata.list_data()): if name == "times": continue renderer = plot.plot( ("times", name), type="line", name=name, color=tuple(COLOR_PALETTE[i]))[0] # Tricky: need to set auto_handle_event on the RangeSelection # so that it passes left-clicks to the PanTool # FIXME: The range selection is still getting the initial left down renderer.tools.append( RangeSelection( renderer, left_button_selects=False, auto_handle_event=False)) plot.tools.append( PanTool( plot, drag_button="left", constrain=True, constrain_direction="x")) plot.overlays.append( ZoomTool( plot, tool_mode="range", max_zoom_out=1.0)) # Attach the range selection to the last renderer; any one will do self._range_selection_overlay = RangeSelectionOverlay( renderer, metadata_name="selections") renderer.overlays.append(self._range_selection_overlay) # Grab a reference to the Time axis datasource and add a listener to its # selections metadata self.times_ds = renderer.index self.times_ds.on_trait_change(self._selections_changed, 'metadata_changed') self.returns_plot = plot def _create_corr_plot(self): plot = Plot(self.plotdata, padding=0) plot.padding_left = 25 plot.padding_bottom = 25 plot.tools.append(PanTool(plot)) plot.overlays.append(ZoomTool(plot)) self.corr_plot = plot def _create_data(self, *names): numpoints = self.numpoints plotdata = ArrayPlotData(times=create_dates(numpoints)) for name in names: plotdata.set_data( name, cumprod(random.lognormal( 0.0, 0.04, size=numpoints))) self.plotdata = plotdata def _selections_changed(self, event): if self.corr_renderer is None: return if not isinstance(event, dict) or "selections" not in event: return corr_index = self.corr_renderer.index selections = event["selections"] if selections is None: corr_index.metadata.pop("selections", None) return else: low, high = selections data = self.times_ds.get_data() low_ndx = data.searchsorted(low) high_ndx = data.searchsorted(high) corr_index.metadata["selections"] = arange( low_ndx, high_ndx + 1, 1, dtype=int) self.corr_plot.request_redraw() @on_trait_change("sym1,sym2") def _update_corr_symbols(self): plot = self.corr_plot if self.corr_renderer is not None: # Remove the old one plot.remove(self.corr_renderer) self.corr_renderer = None self.corr_renderer = plot.plot( (self.sym1, self.sym2), type="scatter", color="blue")[0] self.corr_renderer.overlays.append( ScatterInspectorOverlay( self.corr_renderer, selection_color="lightgreen")) plot.request_redraw()
class QualityAgentView(Dialog): size = Tuple((700, 900)) title = Str("Quality Agent") # The associated LoggerService. service = Any() msg = Str("") subject = Str("Untitled Error Report") to_address = Str() cc_address = Str("") from_address = Str() smtp_server = Str() priority = Str(priority_levels[2]) comments = Str("None") include_userdata = Any ########################################################################### # Protected 'Dialog' interface. ########################################################################### # fixme: Ideally, this should be passed in; this topic ID belongs to the # Enlib help project/plug-in. help_id = "enlib|HID_Quality_Agent_Dlg" def _create_dialog_area(self, parent): """ Creates the main content of the dialog. """ import wx parent.SetSizeHints(minW=300, minH=575) # Add the main panel sizer = wx.BoxSizer(wx.VERTICAL) panel = wx.Panel(parent, -1) panel.SetSizer(sizer) panel.SetAutoLayout(True) # Add a descriptive label at the top ... label = wx.StaticText(panel, -1, "Send a comment or bug report ...") sizer.Add(label, 0, wx.ALL, border=5) # Add the stack trace view ... error_panel = self._create_error_panel(panel) sizer.Add( error_panel, 1, wx.ALL | wx.EXPAND | wx.CLIP_CHILDREN, border=5 ) # Update the layout: sizer.Fit(panel) # Add the error report view ... report_panel = self._create_report_panel(panel) sizer.Add( report_panel, 2, wx.ALL | wx.EXPAND | wx.CLIP_CHILDREN, border=5 ) # Update the layout: sizer.Fit(panel) return panel def _create_buttons(self, parent): """ Creates the buttons. """ import wx sizer = wx.BoxSizer(wx.HORIZONTAL) # 'Send' button. send = wx.Button(parent, wx.ID_OK, "Send") wx.EVT_BUTTON(parent, wx.ID_OK, self._on_send) sizer.Add(send) send.SetDefault() # 'Cancel' button. cancel = wx.Button(parent, wx.ID_CANCEL, "Cancel") wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel) sizer.Add(cancel, 0, wx.LEFT, 10) # 'Help' button. if len(self.help_id) > 0: help = wx.Button(parent, wx.ID_HELP, "Help") wx.EVT_BUTTON(parent, wx.ID_HELP, self._wx_on_help) sizer.Add(help, 0, wx.LEFT, 10) return sizer ### Utility methods ####################################################### def _create_error_panel(self, parent): import wx box = wx.StaticBox(parent, -1, "Message:") sizer = wx.StaticBoxSizer(box, wx.VERTICAL) # Print the stack trace label2 = wx.StaticText( parent, -1, "The following information will be included in the report:", ) sizer.Add( label2, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.CLIP_CHILDREN, border=5, ) details = wx.TextCtrl( parent, -1, self.msg, size=(-1, 75), style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL | wx.VSCROLL | wx.TE_RICH2 | wx.CLIP_CHILDREN, ) details.SetSizeHints(minW=-1, minH=75) # Set the font to not be proportional font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL) details.SetStyle(0, len(self.msg), wx.TextAttr(font=font)) sizer.Add(details, 1, wx.EXPAND | wx.ALL | wx.CLIP_CHILDREN, 5) return sizer def _create_report_panel(self, parent): import wx box = wx.StaticBox(parent, -1, "Report Information:") sizer = wx.StaticBoxSizer(box, wx.VERTICAL) # Add email info ... sizer.Add(self._create_email_info(parent), 0, wx.ALL | wx.EXPAND, 5) # Add priority combo: sizer.Add(self._create_priority_combo(parent), 0, wx.ALL | wx.RIGHT, 5) # Extra comments from the user: label3 = wx.StaticText(parent, -1, "Additional Comments:") sizer.Add( label3, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.CLIP_CHILDREN, 5 ) comments_field = wx.TextCtrl( parent, -1, self.comments, size=(-1, 75), style=wx.TE_MULTILINE | wx.TE_RICH2 | wx.CLIP_CHILDREN, ) comments_field.SetSizeHints(minW=-1, minH=75) font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL) comments_field.SetStyle(0, len(self.comments), wx.TextAttr(font=font)) sizer.Add(comments_field, 1, wx.ALL | wx.EXPAND | wx.CLIP_CHILDREN, 5) wx.EVT_TEXT(parent, comments_field.GetId(), self._on_comments) # Include the project combobox? if len(self.service.mail_files) > 0: sizer.Add(self._create_project_upload(parent), 0, wx.ALL, border=5) return sizer def _create_email_info(self, parent): import wx # Layout setup .. sizer = wx.FlexGridSizer(5, 2, 10, 10) sizer.AddGrowableCol(1) title_label = wx.StaticText(parent, -1, "Subject:") sizer.Add(title_label, 0, wx.ALL | wx.ALIGN_RIGHT) title_field = wx.TextCtrl(parent, -1, self.subject, wx.Point(-1, -1)) sizer.Add( title_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN, ) wx.EVT_TEXT(parent, title_field.GetId(), self._on_subject) to_label = wx.StaticText(parent, -1, "To:") sizer.Add(to_label, 0, wx.ALL | wx.ALIGN_RIGHT) to_field = wx.TextCtrl(parent, -1, self.to_address) sizer.Add( to_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN ) wx.EVT_TEXT(parent, to_field.GetId(), self._on_to) cc_label = wx.StaticText(parent, -1, "Cc:") sizer.Add(cc_label, 0, wx.ALL | wx.ALIGN_RIGHT) cc_field = wx.TextCtrl(parent, -1, "") sizer.Add( cc_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN ) wx.EVT_TEXT(parent, cc_field.GetId(), self._on_cc) from_label = wx.StaticText(parent, -1, "From:") sizer.Add(from_label, 0, wx.ALL | wx.ALIGN_RIGHT) from_field = wx.TextCtrl(parent, -1, self.from_address) sizer.Add( from_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN, ) wx.EVT_TEXT(parent, from_field.GetId(), self._on_from) smtp_label = wx.StaticText(parent, -1, "SMTP Server:") sizer.Add(smtp_label, 0, wx.ALL | wx.ALIGN_RIGHT) smtp_server_field = wx.TextCtrl(parent, -1, self.smtp_server) sizer.Add( smtp_server_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN, ) wx.EVT_TEXT(parent, smtp_server_field.GetId(), self._on_smtp_server) return sizer def _create_priority_combo(self, parent): import wx sizer = wx.BoxSizer(wx.HORIZONTAL) label = wx.StaticText(parent, -1, "How critical is this issue?") sizer.Add(label, 0, wx.ALL, border=0) cb = wx.ComboBox( parent, -1, self.priority, wx.Point(90, 50), wx.Size(95, -1), priority_levels, wx.CB_READONLY, ) sizer.Add(cb, 1, wx.EXPAND | wx.LEFT | wx.CLIP_CHILDREN, border=10) wx.EVT_COMBOBOX(parent, cb.GetId(), self._on_priority) return sizer def _create_project_upload(self, parent): import wx id = wx.NewId() cb = wx.CheckBox( parent, id, "Include Workspace Files (will increase email size) ", wx.Point(65, 80), wx.Size(-1, 20), wx.NO_BORDER, ) wx.EVT_CHECKBOX(parent, id, self._on_project) return cb ## UI Listeners ########################################################### def _on_subject(self, event): self.subject = event.GetEventObject().GetValue() def _on_to(self, event): self.to_address = event.GetEventObject().GetValue() def _on_cc(self, event): self.cc_address = event.GetEventObject().GetValue() def _on_from(self, event): self.from_address = event.GetEventObject().GetValue() def _on_smtp_server(self, event): self.smtp_server = event.GetEventObject().GetValue() def _on_priority(self, event): self.priority = event.GetEventObject().GetStringSelection() def _on_comments(self, event): self.comments = event.GetEventObject().GetValue() def _on_project(self, event): self.include_userdata = event.Checked() cb = event.GetEventObject() if event.Checked(): cb.SetLabel( "Include Workspace Files (approx. %.2f MBytes)" % self._compute_project_size() ) else: cb.SetLabel("Include Workspace Files (will increase email size)") def _on_send(self, event): # Disable the Send button while we go through the possibly # time-consuming email-sending process. button = event.GetEventObject() button.Enable(0) fromaddr, toaddrs, ccaddrs = self._create_email_addresses() message = self._create_email(fromaddr, toaddrs, ccaddrs) self.service.send_bug_report( self.smtp_server, fromaddr, toaddrs, ccaddrs, message ) # save the user's preferences self.service.preferences.smtp_server = self.smtp_server self.service.preferences.to_address = self.to_address self.service.preferences.from_address = self.from_address # finally we close the dialog self._wx_on_ok(event) ## Private ################################################################ def _create_email_addresses(self): # utility function map addresses from ui into the standard format # FIXME: We should use standard To: header parsing instead of this ad # hoc whitespace-only approach. fromaddr = self.from_address if "" == fromaddr.strip(): fromaddr = "anonymous" toaddrs = self.to_address.split() ccaddrs = self.cc_address.split() return fromaddr, toaddrs, ccaddrs def _compute_project_size(self): # determine size of email in MBytes fromaddr, toaddrs, ccaddrs = self._create_email_addresses() message = self._create_email(fromaddr, toaddrs, ccaddrs) return len(message.as_string()) / (2.0 ** 20) def _create_email(self, fromaddr, toaddrs, ccaddrs): return self.service.create_email_message( fromaddr, toaddrs, ccaddrs, self.subject, self.priority, self.include_userdata, self.msg, self.comments, ) def _to_address_default(self): return self.service.preferences.to_address def _from_address_default(self): return self.service.preferences.from_address def _smtp_server_default(self): return self.service.preferences.smtp_server
class Slider(Component): """ A horizontal or vertical slider bar """ #------------------------------------------------------------------------ # Model traits #------------------------------------------------------------------------ min = Float() max = Float() value = Float() # The number of ticks to show on the slider. num_ticks = Int(4) #------------------------------------------------------------------------ # Bar and endcap appearance #------------------------------------------------------------------------ # Whether this is a horizontal or vertical slider orientation = Enum("h", "v") # The thickness, in pixels, of the lines used to render the ticks, # endcaps, and main slider bar. bar_width = Int(4) bar_color = ColorTrait("black") # Whether or not to render endcaps on the slider bar endcaps = Bool(True) # The extent of the endcaps, in pixels. This is a read-only property, # since the endcap size can be set as either a fixed number of pixels or # a percentage of the widget's size in the transverse direction. endcap_size = Property # The extent of the tickmarks, in pixels. This is a read-only property, # since the endcap size can be set as either a fixed number of pixels or # a percentage of the widget's size in the transverse direction. tick_size = Property #------------------------------------------------------------------------ # Slider appearance #------------------------------------------------------------------------ # The kind of marker to use for the slider. slider = SliderMarkerTrait("rect") # If the slider marker is "rect", this is the thickness of the slider, # i.e. its extent in the dimension parallel to the long axis of the widget. # For other slider markers, this has no effect. slider_thickness = Int(9) # The size of the slider, in pixels. This is a read-only property, since # the slider size can be set as either a fixed number of pixels or a # percentage of the widget's size in the transverse direction. slider_size = Property # For slider markers with a filled area, this is the color of the filled # area. For slider markers that are just lines/strokes (e.g. cross, plus), # this is the color of the stroke. slider_color = ColorTrait("red") # For slider markers with a filled area, this is the color of the outline # border drawn around the filled area. For slider markers that have just # lines/strokes, this has no effect. slider_border = ColorTrait("none") # For slider markers with a filled area, this is the width, in pixels, # of the outline around the area. For slider markers that are just lines/ # strokes, this is the thickness of the stroke. slider_outline_width = Int(1) # The kiva.CompiledPath representing the custom path to render for the # slider, if the **slider** trait is set to "custom". custom_slider = Any() #------------------------------------------------------------------------ # Interaction traits #------------------------------------------------------------------------ # Can this slider be interacted with, or is it just a display interactive = Bool(True) mouse_button = Enum("left", "right") event_state = Enum("normal", "dragging") #------------------------------------------------------------------------ # Private traits #------------------------------------------------------------------------ # Returns the coordinate index (0 or 1) corresponding to our orientation. # Used internally; read-only property. axis_ndx = Property() _slider_size_mode = Enum("fixed", "percent") _slider_percent = Float(0.0) _cached_slider_size = Int(10) _endcap_size_mode = Enum("fixed", "percent") _endcap_percent = Float(0.0) _cached_endcap_size = Int(20) _tick_size_mode = Enum("fixed", "percent") _tick_size_percent = Float(0.0) _cached_tick_size = Int(20) # A tuple of (dx, dy) of the difference between the mouse position and # center of the slider. _offset = Any((0, 0)) def set_range(self, min, max): self.min = min self.max = max def map_screen(self, val): """ Returns an (x,y) coordinate corresponding to the location of **val** on the slider. """ # Some local variables to handle orientation dependence axis_ndx = self.axis_ndx other_ndx = 1 - axis_ndx screen_low = self.position[axis_ndx] screen_high = screen_low + self.bounds[axis_ndx] # The return coordinate. The return value along the non-primary # axis will be the same in all cases. coord = [0, 0] coord[ other_ndx] = self.position[other_ndx] + self.bounds[other_ndx] / 2 # Handle exceptional/boundary cases if val <= self.min: coord[axis_ndx] = screen_low return coord elif val >= self.max: coord[axis_ndx] = screen_high return coord elif self.min == self.max: coord[axis_ndx] = (screen_low + screen_high) / 2 return coord # Handle normal cases coord[axis_ndx] = (val - self.min) / ( self.max - self.min) * self.bounds[axis_ndx] + screen_low return coord def map_data(self, x, y, clip=True): """ Returns a value between min and max that corresponds to the given x and y values. Parameters ========== x, y : Float The screen coordinates to map clip : Bool (default=True) Whether points outside the range should be clipped to the max or min value of the slider (depending on which it's closer to) Returns ======= value : Float """ # Some local variables to handle orientation dependence axis_ndx = self.axis_ndx other_ndx = 1 - axis_ndx screen_low = self.position[axis_ndx] screen_high = screen_low + self.bounds[axis_ndx] if self.orientation == "h": coord = x else: coord = y # Handle exceptional/boundary cases if coord >= screen_high: return self.max elif coord <= screen_low: return self.min elif screen_high == screen_low: return (self.max + self.min) / 2 # Handle normal cases return (coord - screen_low) /self.bounds[axis_ndx] * \ (self.max - self.min) + self.min def set_slider_pixels(self, pixels): """ Sets the width of the slider to be a fixed number of pixels Parameters ========== pixels : int The number of pixels wide that the slider should be """ self._slider_size_mode = "fixed" self._cached_slider_size = pixels def set_slider_percent(self, percent): """ Sets the width of the slider to be a percentage of the width of the slider widget. Parameters ========== percent : float The percentage, between 0.0 and 1.0 """ self._slider_size_mode = "percent" self._slider_percent = percent self._update_sizes() def set_endcap_pixels(self, pixels): """ Sets the width of the endcap to be a fixed number of pixels Parameters ========== pixels : int The number of pixels wide that the endcap should be """ self._endcap_size_mode = "fixed" self._cached_endcap_size = pixels def set_endcap_percent(self, percent): """ Sets the width of the endcap to be a percentage of the width of the endcap widget. Parameters ========== percent : float The percentage, between 0.0 and 1.0 """ self._endcap_size_mode = "percent" self._endcap_percent = percent self._update_sizes() def set_tick_pixels(self, pixels): """ Sets the width of the tick marks to be a fixed number of pixels Parameters ========== pixels : int The number of pixels wide that the endcap should be """ self._tick_size_mode = "fixed" self._cached_tick_size = pixels def set_tick_percent(self, percent): """ Sets the width of the tick marks to be a percentage of the width of the endcap widget. Parameters ========== percent : float The percentage, between 0.0 and 1.0 """ self._tick_size_mode = "percent" self._tick_percent = percent self._update_sizes() #------------------------------------------------------------------------ # Rendering methods #------------------------------------------------------------------------ def _draw_mainlayer(self, gc, view_bounds=None, mode="normal"): start = [0, 0] end = [0, 0] axis_ndx = self.axis_ndx other_ndx = 1 - axis_ndx bar_x = self.x + self.width / 2 bar_y = self.y + self.height / 2 # Draw the bar and endcaps gc.set_stroke_color(self.bar_color_) gc.set_line_width(self.bar_width) if self.orientation == "h": gc.move_to(self.x, bar_y) gc.line_to(self.x2, bar_y) gc.stroke_path() if self.endcaps: start_y = bar_y - self._cached_endcap_size / 2 end_y = bar_y + self._cached_endcap_size / 2 gc.move_to(self.x, start_y) gc.line_to(self.x, end_y) gc.move_to(self.x2, start_y) gc.line_to(self.x2, end_y) if self.num_ticks > 0: x_pts = linspace(self.x, self.x2, self.num_ticks + 2).astype(int) starts = zeros((len(x_pts), 2), dtype=int) starts[:, 0] = x_pts starts[:, 1] = bar_y - self._cached_tick_size / 2 ends = starts.copy() ends[:, 1] = bar_y + self._cached_tick_size / 2 gc.line_set(starts, ends) else: gc.move_to(bar_x, self.y) gc.line_to(bar_x, self.y2) if self.endcaps: start_x = bar_x - self._cached_endcap_size / 2 end_x = bar_x + self._cached_endcap_size / 2 gc.move_to(start_x, self.y) gc.line_to(end_x, self.y) gc.move_to(start_x, self.y2) gc.line_to(end_x, self.y2) if self.num_ticks > 0: y_pts = linspace(self.y, self.y2, self.num_ticks + 2).astype(int) starts = zeros((len(y_pts), 2), dtype=int) starts[:, 1] = y_pts starts[:, 0] = bar_x - self._cached_tick_size / 2 ends = starts.copy() ends[:, 0] = bar_x + self._cached_tick_size / 2 gc.line_set(starts, ends) gc.stroke_path() # Draw the slider pt = self.map_screen(self.value) if self.slider == "rect": gc.set_fill_color(self.slider_color_) gc.set_stroke_color(self.slider_border_) gc.set_line_width(self.slider_outline_width) rect = self._get_rect_slider_bounds() gc.rect(*rect) gc.draw_path() else: self._render_marker(gc, pt, self._cached_slider_size, self.slider_(), self.custom_slider) def _get_rect_slider_bounds(self): """ Returns the (x, y, w, h) bounds of the rectangle representing the slider. Used for rendering and hit detection. """ bar_x = self.x + self.width / 2 bar_y = self.y + self.height / 2 pt = self.map_screen(self.value) if self.orientation == "h": slider_height = self._cached_slider_size return (pt[0] - self.slider_thickness, bar_y - slider_height / 2, self.slider_thickness, slider_height) else: slider_width = self._cached_slider_size return (bar_x - slider_width / 2, pt[1] - self.slider_thickness, slider_width, self.slider_thickness) def _render_marker(self, gc, point, size, marker, custom_path): with gc: gc.begin_path() if marker.draw_mode == STROKE: gc.set_stroke_color(self.slider_color_) gc.set_line_width(self.slider_thickness) else: gc.set_fill_color(self.slider_color_) gc.set_stroke_color(self.slider_border_) gc.set_line_width(self.slider_outline_width) if hasattr(gc, "draw_marker_at_points") and \ (marker.__class__ != CustomMarker) and \ (gc.draw_marker_at_points([point], size, marker.kiva_marker) != 0): pass elif hasattr(gc, "draw_path_at_points"): if marker.__class__ != CustomMarker: path = gc.get_empty_path() marker.add_to_path(path, size) mode = marker.draw_mode else: path = custom_path mode = STROKE if not marker.antialias: gc.set_antialias(False) gc.draw_path_at_points([point], path, mode) else: if not marker.antialias: gc.set_antialias(False) if marker.__class__ != CustomMarker: gc.translate_ctm(*point) # Kiva GCs have a path-drawing interface marker.add_to_path(gc, size) gc.draw_path(marker.draw_mode) else: path = custom_path gc.translate_ctm(*point) gc.add_path(path) gc.draw_path(STROKE) #------------------------------------------------------------------------ # Interaction event handlers #------------------------------------------------------------------------ def normal_left_down(self, event): if self.mouse_button == "left": return self._mouse_pressed(event) def dragging_left_up(self, event): if self.mouse_button == "left": return self._mouse_released(event) def normal_right_down(self, event): if self.mouse_button == "right": return self._mouse_pressed(event) def dragging_right_up(self, event): if self.mouse_button == "right": return self._mouse_released(event) def dragging_mouse_move(self, event): dx, dy = self._offset self.value = self.map_data(event.x - dx, event.y - dy) event.handled = True self.request_redraw() def dragging_mouse_leave(self, event): self.event_state = "normal" def _mouse_pressed(self, event): # Determine the slider bounds so we can hit test it pt = self.map_screen(self.value) if self.slider == "rect": x, y, w, h = self._get_rect_slider_bounds() x2 = x + w y2 = y + h else: x, y = pt size = self._cached_slider_size x -= size / 2 y -= size / 2 x2 = x + size y2 = y + size # Hit test both the slider and against the bar. If the user has # clicked on the bar but outside of the slider, we set the _offset # and call dragging_mouse_move() to teleport the slider to the # mouse click position. if self.orientation == "v" and (x <= event.x <= x2): if not (y <= event.y <= y2): self._offset = (event.x - pt[0], 0) self.dragging_mouse_move(event) else: self._offset = (event.x - pt[0], event.y - pt[1]) elif self.orientation == "h" and (y <= event.y <= y2): if not (x <= event.x <= x2): self._offset = (0, event.y - pt[1]) self.dragging_mouse_move(event) else: self._offset = (event.x - pt[0], event.y - pt[1]) else: # The mouse click missed the bar and the slider. return event.handled = True self.event_state = "dragging" return def _mouse_released(self, event): self.event_state = "normal" event.handled = True #------------------------------------------------------------------------ # Private trait event handlers and property getters/setters #------------------------------------------------------------------------ def _get_axis_ndx(self): if self.orientation == "h": return 0 else: return 1 def _get_slider_size(self): return self._cached_slider_size def _get_endcap_size(self): return self._cached_endcap_size def _get_tick_size(self): return self._cached_tick_size @on_trait_change("bounds,bounds_items") def _update_sizes(self): if self._slider_size_mode == "percent": if self.orientation == "h": self._cached_slider_size = int(self.height * self._slider_percent) else: self._cached_slider_size = int(self.width * self._slider_percent) if self._endcap_size_mode == "percent": if self.orientation == "h": self._cached_endcap_size = int(self.height * self._endcap_percent) else: self._cached_endcap_size = int(self.width * self._endcap_percent) return
class Comparator(HasTraits): """ The main application. """ #### Configuration traits ################################################## # The root directory of the test suite. suitedir = Str() # Mapping of SVG basenames to their reference PNGs. Use None if there is no # reference PNG. svg_png = Dict() # The list of SVG file names. svg_files = List() # The name of the default PNG file to display when no reference PNG exists. default_png = Str(os.path.join(this_dir, 'images/default.png')) #### State traits ########################################################## # The currently selected SVG file. current_file = Str() abs_current_file = Property(depends_on=['current_file']) # The current XML ElementTree root Element and its XMLTree view model. current_xml = Any() current_xml_view = Any() # The profilers. profile_this = Instance(ProfileThis, args=()) #### GUI traits ############################################################ # The text showing the current mouse coordinates over any of the components. mouse_coords = Property(Str, depends_on=['ch_controller.svg_coords']) # Move forward and backward through the list of SVG files. move_forward = Button('>>') move_backward = Button('<<') # The description of the test. description = HTML() document = Instance(document.SVGDocument) # The components to view. kiva_component = ComponentTrait(klass=SVGComponent) ref_component = ComponentTrait(klass=ImageComponent, args=()) ch_controller = Instance(MultiController) # The profiler views. parsing_sike = Instance(Sike, args=()) drawing_sike = Instance(Sike, args=()) wx_doc_sike = Instance(Sike, args=()) kiva_doc_sike = Instance(Sike, args=()) traits_view = tui.View( tui.Tabbed( tui.VGroup( tui.HGroup( tui.Item('current_file', editor=tui.EnumEditor(name='svg_files'), style='simple', width=1.0, show_label=False), tui.Item( 'move_backward', show_label=False, enabled_when="svg_files.index(current_file) != 0"), tui.Item( 'move_forward', show_label=False, enabled_when= "svg_files.index(current_file) != len(svg_files)-1"), ), tui.VSplit( tui.HSplit( tui.Item('description', label='Description', show_label=False), tui.Item('current_xml_view', editor=xml_tree_editor, show_label=False), ), tui.HSplit( tui.Item('document', editor=SVGEditor(), show_label=False), tui.Item('kiva_component', show_label=False), tui.Item('ref_component', show_label=False), # TODO: tui.Item('agg_component', show_label=False), ), ), label='SVG', ), tui.Item('parsing_sike', style='custom', show_label=False, label='Parsing Profile'), tui.Item('drawing_sike', style='custom', show_label=False, label='Kiva Drawing Profile'), tui.Item('wx_doc_sike', style='custom', show_label=False, label='Creating WX document'), tui.Item('kiva_doc_sike', style='custom', show_label=False, label='Creating WX document'), ), width=1280, height=768, resizable=True, statusbar='mouse_coords', title='SVG Comparator', ) def __init__(self, **traits): super(Comparator, self).__init__(**traits) kiva_ch = activate_tool(self.kiva_component, Crosshair(self.kiva_component)) ref_ch = activate_tool(self.ref_component, Crosshair(self.ref_component)) self.ch_controller = MultiController(kiva_ch, ref_ch) @classmethod def fromsuitedir(cls, dirname, **traits): """ Find all SVG files and their related reference PNG files under a directory. This assumes that the SVGs are located under <dirname>/svg/ and the related PNGs under <dirname>/png/ and that there are no subdirectories. """ dirname = os.path.abspath(dirname) svgs = glob.glob(os.path.join(dirname, 'svg', '*.svg')) pngdir = os.path.join(dirname, 'png') d = {} for svg in svgs: png = None base = os.path.splitext(os.path.basename(svg))[0] for prefix in ('full-', 'basic-', 'tiny-', ''): fn = os.path.join(pngdir, prefix + base + '.png') if os.path.exists(fn): png = os.path.basename(fn) break d[os.path.basename(svg)] = png svgs = sorted(d) x = cls(suitedir=dirname, svg_png=d, svg_files=svgs, **traits) x.current_file = svgs[0] return x def display_reference_png(self, filename): """ Read the image file and shove its data into the display component. """ img = Image.open(filename) arr = np.array(img) self.ref_component.image = arr def display_test_description(self): """ Extract the test description for display. """ html = ET.Element('html') title = self.current_xml.find('.//{http://www.w3.org/2000/svg}title') if title is not None: title_text = title.text else: title_text = os.path.splitext(self.current_file)[0] p = ET.SubElement(html, 'p') b = ET.SubElement(p, 'b') b.text = 'Title: ' b.tail = title_text desc_text = None version_text = None desc = self.current_xml.find('.//{http://www.w3.org/2000/svg}desc') if desc is not None: desc_text = desc.text else: testcase = self.current_xml.find( './/{http://www.w3.org/2000/02/svg/testsuite/description/}SVGTestCase' ) if testcase is not None: desc_text = testcase.get('desc', None) version_text = testcase.get('version', None) if desc_text is not None: p = ET.SubElement(html, 'p') b = ET.SubElement(p, 'b') b.text = 'Description: ' b.tail = normalize_text(desc_text) if version_text is None: script = self.current_xml.find( './/{http://www.w3.org/2000/02/svg/testsuite/description/}OperatorScript' ) if script is not None: version_text = script.get('version', None) if version_text is not None: p = ET.SubElement(html, 'p') b = ET.SubElement(p, 'b') b.text = 'Version: ' b.tail = version_text paras = self.current_xml.findall( './/{http://www.w3.org/2000/02/svg/testsuite/description/}Paragraph' ) if len(paras) > 0: div = ET.SubElement(html, 'div') for para in paras: p = ET.SubElement(div, 'p') p.text = normalize_text(para.text) # Copy over any children elements like <a>. p[:] = para[:] tree = ET.ElementTree(html) f = StringIO() tree.write(f) text = f.getvalue() self.description = text def locate_file(self, name, kind): """ Find the location of the given file in the suite. Parameters ---------- name : str Path of the file relative to the suitedir. kind : either 'svg' or 'png' The kind of file. Returns ------- path : str The full path to the file. """ return os.path.join(self.suitedir, kind, name) def _kiva_component_default(self): return SVGComponent(profile_this=self.profile_this) def _move_backward_fired(self): idx = self.svg_files.index(self.current_file) idx = max(idx - 1, 0) self.current_file = self.svg_files[idx] def _move_forward_fired(self): idx = self.svg_files.index(self.current_file) idx = min(idx + 1, len(self.svg_files) - 1) self.current_file = self.svg_files[idx] def _get_abs_current_file(self): return self.locate_file(self.current_file, 'svg') def _current_file_changed(self, new): # Reset the warnings filters. While it's good to only get 1 warning per # file, we want to get the same warning again if a new file issues it. warnings.resetwarnings() self.profile_this.start('Parsing') self.current_xml = ET.parse(self.abs_current_file).getroot() self.current_xml_view = xml_to_tree(self.current_xml) resources = document.ResourceGetter.fromfilename(self.abs_current_file) self.profile_this.stop() try: self.profile_this.start('Creating WX document') self.document = document.SVGDocument(self.current_xml, resources=resources, renderer=WxRenderer) except: logger.exception('Error parsing document %s', new) self.document = None self.profile_this.stop() try: self.profile_this.start('Creating Kiva document') self.kiva_component.document = document.SVGDocument( self.current_xml, resources=resources, renderer=KivaRenderer) except Exception as e: logger.exception('Error parsing document %s', new) self.kiva_component.document self.profile_this.stop() png_file = self.svg_png.get(new, None) if png_file is None: png_file = self.default_png else: png_file = self.locate_file(png_file, 'png') self.display_test_description() self.display_reference_png(png_file) def _get_mouse_coords(self): if self.ch_controller is None: return '' else: return '%1.3g %1.3g' % self.ch_controller.svg_coords @on_trait_change('profile_this:profile_ended') def _update_profiling(self, new): if new is not None: name, p = new stats = pstats.Stats(p) if name == 'Parsing': self.parsing_sike.stats = stats elif name == 'Drawing': self.drawing_sike.stats = stats elif name == 'Creating WX document': self.wx_doc_sike.stats = stats elif name == 'Creating Kiva document': self.kiva_doc_sike.stats = stats
class QuadSelection(cytoflow.views.ScatterplotView): """Plots, and lets the user interact with, a quadrant gate. Attributes ---------- op : Instance(Range2DOp) The instance of Range2DOp that we're viewing / editing huefacet : Str The conditioning variable to plot multiple colors subset : Str The string passed to `Experiment.query()` to subset the data before plotting interactive : Bool is this view interactive? Ie, can the user set the threshold with a mouse click? Notes ----- We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterplotView`, but they must both be unset! Examples -------- In an IPython notebook with `%matplotlib notebook` >>> q = flow.QuadOp(name = "Quad", ... xchannel = "V2-A", ... ychannel = "Y2-A")) >>> qv = q.default_view() >>> qv.interactive = True >>> qv.plot(ex2) """ id = Constant('edu.mit.synbio.cytoflow.views.quad') friendly_id = Constant("Quadrant Selection") op = Instance(IOperation) name = DelegatesTo('op') xchannel = DelegatesTo('op') ychannel = DelegatesTo('op') interactive = Bool(False, transient=True) # internal state. _ax = Any(transient=True) _hline = Instance(Line2D, transient=True) _vline = Instance(Line2D, transient=True) _cursor = Instance(Cursor, transient=True) def plot(self, experiment, **kwargs): """Plot the underlying scatterplot and then plot the selection on top of it.""" if not experiment: raise util.CytoflowOpError("No experiment specified") if not experiment: raise util.CytoflowViewError("No experiment specified") if self.xfacet: raise util.CytoflowViewError( "RangeSelection.xfacet must be empty or `Undefined`") if self.yfacet: raise util.CytoflowViewError( "RangeSelection.yfacet must be empty or `Undefined`") super(QuadSelection, self).plot(experiment, **kwargs) self._ax = plt.gca() self._draw_lines() self._interactive() @on_trait_change('op.xthreshold, op.ythreshold', post_init=True) def _draw_lines(self): if not self._ax: return if self._hline and self._hline in self._ax.lines: self._hline.remove() if self._vline and self._vline in self._ax.lines: self._vline.remove() if self.op.xthreshold and self.op.ythreshold: self._hline = plt.axhline(self.op.ythreshold, linewidth=3, color='blue') self._vline = plt.axvline(self.op.xthreshold, linewidth=3, color='blue') plt.draw_if_interactive() @on_trait_change('interactive', post_init=True) def _interactive(self): if self._ax and self.interactive: self._cursor = Cursor(self._ax, horizOn=True, vertOn=True, color='blue') self._cursor.connect_event('button_press_event', self._onclick) elif self._cursor: self._cursor.disconnect_events() self._cursor = None def _onclick(self, event): """Update the threshold location""" self.op.xthreshold = event.xdata self.op.ythreshold = event.ydata
class VolumeFactory(PipeFactory): """ Applies the Volume mayavi module to the given VTK data source (Mayavi source, or VTK dataset). **Note** The range of the colormap can be changed simply using the vmin/vmax parameters (see below). For more complex modifications of the colormap, here is some pseudo code to change the ctf (color transfer function), or the otf (opacity transfer function):: vol = mlab.pipeline.volume(src) # Changing the ctf: from tvtk.util.ctf import ColorTransferFunction ctf = ColorTransferFunction() ctf.add_rgb_point(value, r, g, b) # r, g, and b are float # between 0 and 1 ctf.add_hsv_point(value, h, s, v) # ... vol._volume_property.set_color(ctf) vol._ctf = ctf vol.update_ctf = True # Changing the otf: from tvtk.util.ctf import PiecewiseFunction otf = PiecewiseFunction() otf.add_point(value, opacity) vol._otf = otf vol._volume_property.set_scalar_opacity(otf) Also, it might be useful to change the range of the ctf:: ctf.range = [0, 1] """ color = Trait( None, None, TraitTuple(Range(0., 1.), Range(0., 1.), Range(0., 1.)), help="""the color of the vtk object. Overides the colormap, if any, when specified. This is specified as a triplet of float ranging from 0 to 1, eg (1, 1, 1) for white.""", ) vmin = Trait(None, None, CFloat, help="""vmin is used to scale the transparency gradient. If None, the min of the data will be used""") vmax = Trait(None, None, CFloat, help="""vmax is used to scale the transparency gradient. If None, the max of the data will be used""") _target = Instance(modules.Volume, ()) __last_vrange = Any(None) ###################################################################### # Non-public interface. ###################################################################### def _color_changed(self): if not self.color: return range_min, range_max = self._target.current_range from tvtk.util.ctf import ColorTransferFunction ctf = ColorTransferFunction() try: ctf.range = (range_min, range_max) except Exception: # VTK versions < 5.2 don't seem to need this. pass r, g, b = self.color ctf.add_rgb_point(range_min, r, g, b) ctf.add_rgb_point(range_max, r, g, b) self._target._ctf = ctf self._target._volume_property.set_color(ctf) self._target.update_ctf = True def _vmin_changed(self): vmin = self.vmin vmax = self.vmax range_min, range_max = self._target.current_range if vmin is None: vmin = range_min if vmax is None: vmax = range_max # Change the opacity function from tvtk.util.ctf import PiecewiseFunction, save_ctfs otf = PiecewiseFunction() if range_min < vmin: otf.add_point(range_min, 0.) if range_max > vmax: otf.add_point(range_max, 0.2) otf.add_point(vmin, 0.) otf.add_point(vmax, 0.2) self._target._otf = otf self._target._volume_property.set_scalar_opacity(otf) if self.color is None and \ ((self.vmin is not None) or (self.vmax is not None)): # FIXME: We don't use 'rescale_ctfs' because it screws up the # nodes, this is because, the values are actually scaled between # the specified vmin/vmax and NOT the full range of values # specified in the CTF or in the volume object. if self.__last_vrange: last_min, last_max = self.__last_vrange else: last_min, last_max = range_min, range_max def _rescale_value(x): nx = (x - last_min) / (last_max - last_min) return vmin + nx * (vmax - vmin) # The range of the existing ctf can vary. scale_min, scale_max = self._target._ctf.range def _rescale_node(x): nx = (x - scale_min) / (scale_max - scale_min) return range_min + nx * (range_max - range_min) if hasattr(self._target._ctf, 'nodes'): rgb = list() for value in self._target._ctf.nodes: r, g, b = \ self._target._ctf.get_color(value) rgb.append((_rescale_node(value), r, g, b)) else: rgb = save_ctfs(self._target.volume_property)['rgb'] from tvtk.util.ctf import ColorTransferFunction ctf = ColorTransferFunction() try: ctf.range = (range_min, range_max) except Exception: # VTK versions < 5.2 don't seem to need this. pass rgb.sort() v = rgb[0] ctf.add_rgb_point(range_min, v[1], v[2], v[3]) for v in rgb: ctf.add_rgb_point(_rescale_value(v[0]), v[1], v[2], v[3]) ctf.add_rgb_point(range_max, v[1], v[2], v[3]) self._target._ctf = ctf self._target._volume_property.set_color(ctf) self.__last_vrange = vmin, vmax self._target.update_ctf = True # This is not necessary: the job is already done by _vmin_changed _vmax_changed = _vmin_changed
class _ListStrEditor(Editor): """ Traits UI editor for editing lists of strings. """ #-- Trait Definitions ------------------------------------------------------ # The title of the editor: title = Str # The current set of selected items (which one is used depends upon the # initial state of the editor factory 'multi_select' trait): selected = Any multi_selected = List # The current set of selected item indices (which one is used depends upon # the initial state of the editor factory 'multi_select' trait): selected_index = Int multi_selected_indices = List(Int) # The most recently actived item and its index: activated = Any activated_index = Int # The most recently right_clicked item and its index: right_clicked = Event right_clicked_index = Event # Is the list editor scrollable? This value overrides the default. scrollable = True # Index of item to select after rebuilding editor list: index = Any # Should the selected item be edited after rebuilding the editor list: edit = Bool(False) # The adapter from list items to editor values: adapter = Instance(ListStrAdapter) # Dictionaly mapping image names to wx.ImageList indices: images = Any({}) # Dictionary mapping ImageResource objects to wx.ImageList indices: image_resources = Any({}) # The current number of item currently in the list: item_count = Property # The current search string: search = Str #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ factory = self.factory # Set up the adapter to use: self.adapter = factory.adapter self.sync_value(factory.adapter_name, 'adapter', 'from') # Determine the style to use for the list control: style = wx.LC_REPORT | wx.LC_VIRTUAL if factory.editable: style |= wx.LC_EDIT_LABELS if factory.horizontal_lines: style |= wx.LC_HRULES if not factory.multi_select: style |= wx.LC_SINGLE_SEL if (factory.title == '') and (factory.title_name == ''): style |= wx.LC_NO_HEADER # Create the list control and link it back to us: self.control = control = wxListCtrl(parent, -1, style=style) control._editor = self # Create the list control column: control.InsertColumn(0, '') # Set up the list control's event handlers: id = control.GetId() wx.EVT_LIST_BEGIN_DRAG(parent, id, self._begin_drag) wx.EVT_LIST_BEGIN_LABEL_EDIT(parent, id, self._begin_label_edit) wx.EVT_LIST_END_LABEL_EDIT(parent, id, self._end_label_edit) wx.EVT_LIST_ITEM_SELECTED(parent, id, self._item_selected) wx.EVT_LIST_ITEM_DESELECTED(parent, id, self._item_selected) wx.EVT_LIST_ITEM_RIGHT_CLICK(parent, id, self._right_clicked) wx.EVT_LIST_ITEM_ACTIVATED(parent, id, self._item_activated) wx.EVT_SIZE(control, self._size_modified) # Handle key events: wx.EVT_CHAR(control, self._key_pressed) # Handle mouse events: if 'edit' in factory.operations: wx.EVT_LEFT_DOWN(control, self._left_down) # Set up the drag and drop target: if PythonDropTarget is not None: control.SetDropTarget(PythonDropTarget(self)) # Initialize the editor title: self.title = factory.title self.sync_value(factory.title_name, 'title', 'from') # Set up the selection listener (if necessary): if factory.multi_select: self.sync_value(factory.selected, 'multi_selected', 'both', is_list=True) self.sync_value(factory.selected_index, 'multi_selected_indices', 'both', is_list=True) else: self.sync_value(factory.selected, 'selected', 'both') self.sync_value(factory.selected_index, 'selected_index', 'both') # Synchronize other interesting traits as necessary: self.sync_value(factory.activated, 'activated', 'to') self.sync_value(factory.activated_index, 'activated_index', 'to') self.sync_value(factory.right_clicked, 'right_clicked', 'to') self.sync_value(factory.right_clicked_index, 'right_clicked_index', 'to') # Make sure we listen for 'items' changes as well as complete list # replacements: self.context_object.on_trait_change(self.update_editor, self.extended_name + '_items', dispatch='ui') # Create the mapping from user supplied images to wx.ImageList indices: for image_resource in factory.images: self._add_image(image_resource) # Refresh the editor whenever the adapter changes: self.on_trait_change(self._refresh, 'adapter.+update', dispatch='ui') # Set the list control's tooltip: self.set_tooltip() def dispose(self): """ Disposes of the contents of an editor. """ disconnect(self.control, wx.EVT_LIST_BEGIN_DRAG, wx.EVT_LIST_BEGIN_LABEL_EDIT, wx.EVT_LIST_END_LABEL_EDIT, wx.EVT_LIST_ITEM_SELECTED, wx.EVT_LIST_ITEM_DESELECTED, wx.EVT_LIST_ITEM_RIGHT_CLICK, wx.EVT_LIST_ITEM_ACTIVATED) disconnect_no_id(self.control, wx.EVT_SIZE, wx.EVT_CHAR, wx.EVT_LEFT_DOWN) self.context_object.on_trait_change(self.update_editor, self.extended_name + '_items', remove=True) self.on_trait_change(self._refresh, 'adapter.+update', remove=True) super(_ListStrEditor, self).dispose() def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ control = self.control top = control.GetTopItem() pn = control.GetCountPerPage() n = self.adapter.len(self.object, self.name) if self.factory.auto_add: n += 1 control.DeleteAllItems() control.SetItemCount(n) if control.GetItemCount() > 0: control.RefreshItems(0, control.GetItemCount() - 1) control.SetColumnWidth(0, control.GetClientSizeTuple()[0]) edit, self.edit = self.edit, False index, self.index = self.index, None if index is not None: if index >= n: index -= 1 if index < 0: index = None if index is None: visible = top + pn - 2 if visible >= 0 and visible < control.GetItemCount(): control.EnsureVisible(visible) if self.factory.multi_select: for index in self.multi_selected_indices: if 0 <= index < n: control.SetItemState(index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) else: if 0 <= self.selected_index < n: control.SetItemState(self.selected_index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) return if 0 <= (index - top) < pn: control.EnsureVisible(max(0, top + pn - 2)) elif index < top: control.EnsureVisible(min(n, index + pn - 1)) else: control.EnsureVisible(index) control.SetItemState(index, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED) if edit: control.EditLabel(index) #-- Property Implementations ----------------------------------------------- def _get_item_count(self): return (self.control.GetItemCount() - self.factory.auto_add) #-- Trait Event Handlers --------------------------------------------------- def _title_changed(self, title): """ Handles the editor title being changed. """ list_item = wx.ListItem() list_item.SetText(title) self.control.SetColumn(0, list_item) def _selected_changed(self, selected): """ Handles the editor's 'selected' trait being changed. """ if not self._no_update: try: self.control.SetItemState(self.value.index(selected), wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) except Exception: pass def _selected_index_changed(self, selected_index): """ Handles the editor's 'selected_index' trait being changed. """ if not self._no_update: try: self.control.SetItemState(selected_index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) except Exception: pass def _multi_selected_changed(self, selected): """ Handles the editor's 'multi_selected' trait being changed. """ if not self._no_update: values = self.value try: self._multi_selected_indices_changed( [values.index(item) for item in selected]) except Exception: pass def _multi_selected_items_changed(self, event): """ Handles the editor's 'multi_selected' trait being modified. """ values = self.values try: self._multi_selected_indices_items_changed( TraitListEvent(0, [values.index(item) for item in event.removed], [values.index(item) for item in event.added])) except Exception: pass def _multi_selected_indices_changed(self, selected_indices): """ Handles the editor's 'multi_selected_indices' trait being changed. """ if not self._no_update: control = self.control selected = self._get_selected() # Select any new items that aren't already selected: for index in selected_indices: if index in selected: selected.remove(index) else: try: control.SetItemState(index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) except Exception: pass # Unselect all remaining selected items that aren't selected now: for index in selected: control.SetItemState(index, 0, wx.LIST_STATE_SELECTED) def _multi_selected_indices_items_changed(self, event): """ Handles the editor's 'multi_selected_indices' trait being modified. """ control = self.control # Remove all items that are no longer selected: for index in event.removed: control.SetItemState(index, 0, wx.LIST_STATE_SELECTED) # Select all newly added items: for index in event.added: control.SetItemState(index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) #-- List Control Event Handlers -------------------------------------------- def _begin_drag(self, event): """ Handles the user beginning a drag operation with the left mouse button. """ if PythonDropSource is not None: adapter = self.adapter object, name = self.object, self.name index = event.GetIndex() selected = self._get_selected() drag_items = [] # Collect all of the selected items to drag: for index in selected: drag = adapter.get_drag(object, name, index) if drag is None: return drag_items.append(drag) # Save the drag item indices, so that we can later handle a # completed 'move' operation: self._drag_indices = selected try: # If only one item is being dragged, drag it as an item, not a # list: if len(drag_items) == 1: drag_items = drag_items[0] # Perform the drag and drop operation: ds = PythonDropSource(self.control, drag_items) # If moves are allowed and the result was a drag move: if ((ds.result == wx.DragMove) and (self._drag_local or self.factory.drag_move)): # Then delete all of the original items (in reverse order # from highest to lowest, so the indices don't need to be # adjusted): indices = self._drag_indices indices.reverse() for index in indices: adapter.delete(object, name, index) finally: self._drag_indices = None self._drag_local = False def _begin_label_edit(self, event): """ Handles the user starting to edit an item label. """ index = event.GetIndex() if ((not self._is_auto_add(index)) and (not self.adapter.get_can_edit(self.object, self.name, index))): event.Veto() def _end_label_edit(self, event): """ Handles the user finishing editing an item label. """ self._set_text_current(event.GetIndex(), event.GetText()) def _item_selected(self, event): """ Handles an item being selected. """ self._no_update = True try: get_item = self.adapter.get_item object, name = self.object, self.name selected_indices = self._get_selected() if self.factory.multi_select: self.multi_selected_indices = selected_indices self.multi_selected = [ get_item(object, name, index) for index in selected_indices ] elif len(selected_indices) == 0: self.selected_index = -1 self.selected = None else: self.selected_index = selected_indices[0] self.selected = get_item(object, name, selected_indices[0]) finally: self._no_update = False def _item_activated(self, event): """ Handles an item being activated (double-clicked or enter pressed). """ self.activated_index = event.GetIndex() if 'edit' in self.factory.operations: self._edit_current() else: self.activated = self.adapter.get_item(self.object, self.name, self.activated_index) def _right_clicked(self, event): """ Handles an item being right clicked. """ self.right_clicked_index = index = event.GetIndex() self.right_clicked = self.adapter.get_item(self.object, self.name, index) def _key_pressed(self, event): key = event.GetKeyCode() control = event.ControlDown() if 32 <= key <= 126: self.search += chr(key).lower() self._search_for_string() elif key in (wx.WXK_HOME, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN): self.search = '' event.Skip() elif key == wx.WXK_END: self.search = '' self._append_new() elif (key == wx.WXK_UP) and control: self._search_for_string(-1) elif (key == wx.WXK_DOWN) and control: self._search_for_string(1) elif key in (wx.WXK_BACK, wx.WXK_DELETE): self._delete_current() elif key == wx.WXK_INSERT: self._insert_current() elif key == wx.WXK_LEFT: self._move_up_current() elif key == wx.WXK_RIGHT: self._move_down_current() elif key == wx.WXK_RETURN: self._edit_current() elif key == 3: # Ctrl-C self._copy_current() elif key == 22: # Ctrl-V self._paste_current() elif key == 24: # Ctrl-X self._cut_current() else: event.Skip() def _size_modified(self, event): """ Handles the size of the list control being changed. """ dx, dy = self.control.GetClientSizeTuple() self.control.SetColumnWidth(0, dx - 1) event.Skip() def _left_down(self, event): """ Handles the user pressing the left mouse button. """ index, flags = self.control.HitTest( wx.Point(event.GetX(), event.GetY())) selected = self._get_selected() if (len(selected) == 1) and (index == selected[0]): self._edit_current() else: event.Skip() #-- Drag and Drop Event Handlers ------------------------------------------- def wx_dropped_on(self, x, y, data, drag_result): """ Handles a Python object being dropped on the list control. """ index, flags = self.control.HitTest(wx.Point(x, y)) # If the user dropped it on an empty list, set the target as past the # end of the list: if ((index == -1) and ((flags & wx.LIST_HITTEST_NOWHERE) != 0) and (self.control.GetItemCount() == 0)): index = 0 # If we have a valid drop target index, proceed: if index != -1: if not isinstance(data, list): # Handle the case of just a single item being dropped: self._wx_dropped_on(index, data) else: # Handles the case of a list of items being dropped, being # careful to preserve the original order of the source items if # possible: data.reverse() for item in data: self._wx_dropped_on(index, item) # If this was an inter-list drag, mark it as 'local': if self._drag_indices is not None: self._drag_local = True # Return a successful drop result: return drag_result # Indicate we could not process the drop: return wx.DragNone def _wx_dropped_on(self, index, item): """ Helper method for handling a single item dropped on the list control. """ adapter = self.adapter object, name = self.object, self.name # Obtain the destination of the dropped item relative to the target: destination = adapter.get_dropped(object, name, index, item) # Adjust the target index accordingly: if destination == 'after': index += 1 # Insert the dropped item at the requested position: adapter.insert(object, name, index, item) # If the source for the drag was also this list control, we need to # adjust the original source indices to account for their new position # after the drag operation: indices = self._drag_indices if indices is not None: for i in range(len(indices) - 1, -1, -1): if indices[i] < index: break indices[i] += 1 def wx_drag_over(self, x, y, data, drag_result): """ Handles a Python object being dragged over the tree. """ if isinstance(data, list): rc = wx.DragNone for item in data: rc = self.wx_drag_over(x, y, item, drag_result) if rc == wx.DragNone: break return rc index, flags = self.control.HitTest(wx.Point(x, y)) # If the user is dragging over an empty list, set the target to the end # of the list: if ((index == -1) and ((flags & wx.LIST_HITTEST_NOWHERE) != 0) and (self.control.GetItemCount() == 0)): index = 0 # If the drag target index is valid and the adapter says it is OK to # drop the data here, then indicate the data can be dropped: if ((index != -1) and self.adapter.get_can_drop( self.object, self.name, index, data)): return drag_result # Else indicate that we will not accept the data: return wx.DragNone #-- Private Methods -------------------------------------------------------- def _refresh(self): """ Refreshes the contents of the editor's list control. """ self.control.RefreshItems(0, len(self.value) - 1) def _add_image(self, image_resource): """ Adds a new image to the wx.ImageList and its associated mapping. """ bitmap = image_resource.create_image().ConvertToBitmap() image_list = self._image_list if image_list is None: self._image_list = image_list = wx.ImageList( bitmap.GetWidth(), bitmap.GetHeight()) self.control.AssignImageList(image_list, wx.IMAGE_LIST_SMALL) self.image_resources[image_resource] = \ self.images[ image_resource.name ] = index = image_list.Add( bitmap ) return index def _get_image(self, image): """ Converts a user specified image to a wx.ListCtrl image index. """ if isinstance(image, ImageResource): result = self.image_resources.get(image) if result is not None: return result return self._add_image(image) return self.images.get(image) def _get_selected(self): """ Returns a list of the indices of all currently selected list items. """ selected = [] item = -1 control = self.control while True: item = control.GetNextItem(item, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) if item == -1: break selected.append(item) return selected def _search_for_string(self, increment=0): """ Searches for the next occurrence of the current search string. """ selected = self._get_selected() if len(selected) > 1: return start = 0 if len(selected) == 1: start = selected[0] + increment get_text = self.adapter.get_text search = self.search object = self.object name = self.name if increment >= 0: items = xrange(start, self.item_count) else: items = xrange(start, -1, -1) for index in items: if search in get_text(object, name, index).lower(): self.index = index self.update_editor() break def _append_new(self): """ Append a new item to the end of the list control. """ if 'append' in self.factory.operations: self.edit = True adapter = self.adapter index = self.control.GetItemCount() if self.factory.auto_add: self.index = index - 1 self.update_editor() else: self.index = index adapter.insert( self.object, self.name, self.index, adapter.get_default_value(self.object, self.name)) def _copy_current(self): """ Copies the currently selected list control item to the clipboard. """ selected = self._get_selected() if len(selected) == 1: index = selected[0] if index < self.item_count: try: from pyface.wx.clipboard import clipboard clipboard.data = self.adapter.get_text( self.object, self.name, index) except: # Handle the traits.util package not being installed by # just ignoring the request: pass def _cut_current(self): """ Cuts the currently selected list control item and places its value in the clipboard. """ ops = self.factory.operations if ('insert' in ops) and ('delete' in ops): selected = self._get_selected() if len(selected) == 1: index = selected[0] if index < self.item_count: try: from pyface.wx.clipboard import clipboard clipboard.data = self.adapter.get_text( self.object, self.name, index) self.index = index self.adapter.delete(self.object, self.name, index) except: # Handle the traits.util package not being installed # by just ignoring the request: pass def _paste_current(self): """ Pastes the clipboard contents into the currently selected list control item. """ if 'insert' in self.factory.operations: selected = self._get_selected() if len(selected) == 1: try: from pyface.wx.clipboard import clipboard self._set_text_current(selected[0], clipboard.text_data, insert=True) except: # Handle the traits.util package not being installed by # just ignoring the request: pass def _insert_current(self): """ Inserts a new item after the currently selected list control item. """ if 'insert' in self.factory.operations: selected = self._get_selected() if len(selected) == 1: self.index = selected[0] self.edit = True adapter = self.adapter adapter.insert( self.object, self.name, selected[0], adapter.get_default_value(self.object, self.name)) def _delete_current(self): """ Deletes the currently selected items from the list control. """ if 'delete' in self.factory.operations: selected = self._get_selected() if len(selected) == 0: return n = self.item_count delete = self.adapter.delete selected.reverse() self.index = selected[-1] for index in selected: if index < n: delete(self.object, self.name, index) def _move_up_current(self): """ Moves the currently selected item up one line in the list control. """ if 'move' in self.factory.operations: selected = self._get_selected() if len(selected) == 1: index = selected[0] n = self.item_count if 0 < index < n: adapter = self.adapter object, name = self.object, self.name item = adapter.get_item(object, name, index) adapter.delete(object, name, index) self.index = index - 1 adapter.insert(object, name, index - 1, item) def _move_down_current(self): """ Moves the currently selected item down one line in the list control. """ if 'move' in self.factory.operations: selected = self._get_selected() if len(selected) == 1: index = selected[0] n = self.item_count - 1 if index < n: adapter = self.adapter object, name = self.object, self.name item = adapter.get_item(object, name, index) adapter.delete(object, name, index) self.index = index + 1 adapter.insert(object, name, index + 1, item) def _edit_current(self): """ Allows the user to edit the current item in the list control. """ if 'edit' in self.factory.operations: selected = self._get_selected() if len(selected) == 1: self.control.EditLabel(selected[0]) def _is_auto_add(self, index): """ Returns whether or not the index is the special 'auto add' item at the end of the list. """ return (self.factory.auto_add and (index >= self.adapter.len(self.object, self.name))) def _set_text_current(self, index, text, insert=False): """ Sets the text value of the specified list control item. """ if text.strip() != '': object, name, adapter = self.object, self.name, self.adapter if insert or self._is_auto_add(index): adapter.insert(object, name, index, adapter.get_default_value(object, name)) self.edit = (not insert) self.index = index + 1 adapter.set_text(object, name, index, text)
class RangeSelection2D(cytoflow.views.ScatterplotView): """Plots, and lets the user interact with, a 2D selection. Attributes ---------- op : Instance(Range2DOp) The instance of Range2DOp that we're viewing / editing huefacet : Str The conditioning variable to plot multiple colors subset : Str The string passed to `Experiment.query()` to subset the data before plotting interactive : Bool is this view interactive? Ie, can the user set min and max with a mouse drag? Notes ----- We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterplotView`, but they must both be unset! Examples -------- In an IPython notebook with `%matplotlib notebook` >>> r = flow.Range2DOp(name = "Range2D", ... xchannel = "V2-A", ... ychannel = "Y2-A")) >>> rv = r.default_view() >>> rv.interactive = True >>> rv.plot(ex2) """ id = Constant('edu.mit.synbio.cytoflow.views.range2d') friendly_id = Constant("2D Range Selection") op = Instance(IOperation) name = DelegatesTo('op') xchannel = DelegatesTo('op') xlow = DelegatesTo('op') xhigh = DelegatesTo('op') ychannel = DelegatesTo('op') ylow = DelegatesTo('op') yhigh = DelegatesTo('op') interactive = Bool(False, transient=True) # internal state. _ax = Any(transient=True) _selector = Instance(RectangleSelector, transient=True) _box = Instance(Rectangle, transient=True) def plot(self, experiment, **kwargs): """Plot the underlying scatterplot and then plot the selection on top of it.""" if not experiment: raise util.CytoflowViewError("No experiment specified") if not experiment: raise util.CytoflowViewError("No experiment specified") if self.xfacet: raise util.CytoflowViewError( "RangeSelection.xfacet must be empty or `Undefined`") if self.yfacet: raise util.CytoflowViewError( "RangeSelection.yfacet must be empty or `Undefined`") super(RangeSelection2D, self).plot(experiment, **kwargs) self._ax = plt.gca() self._draw_rect() self._interactive() @on_trait_change('xlow, xhigh, ylow, yhigh', post_init=True) def _draw_rect(self): if not self._ax: return if self._box and self._box in self._ax.patches: self._box.remove() if self.xlow and self.xhigh and self.ylow and self.yhigh: self._box = Rectangle((self.xlow, self.ylow), (self.xhigh - self.xlow), (self.yhigh - self.ylow), facecolor="grey", alpha=0.2) self._ax.add_patch(self._box) plt.draw() @on_trait_change('interactive', post_init=True) def _interactive(self): if self._ax and self.interactive: self._selector = RectangleSelector(self._ax, onselect=self._onselect, rectprops={ 'alpha': 0.2, 'color': 'grey' }, useblit=True) else: self._selector = None def _onselect(self, pos1, pos2): """Update selection traits""" self.xlow = min(pos1.xdata, pos2.xdata) self.xhigh = max(pos1.xdata, pos2.xdata) self.ylow = min(pos1.ydata, pos2.ydata) self.yhigh = max(pos1.ydata, pos2.ydata)
class TableEditor(Editor, BaseTableEditor): """ Editor that presents data in a table. Optionally, tables can have a set of filters that reduce the set of data displayed, according to their criteria. """ #------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------- # The table view control associated with the editor: table_view = Any def _table_view_default(self): return TableView(editor=self) # A wrapper around the source model which provides filtering and sorting: model = Instance(SortFilterTableModel) def _model_default(self): return SortFilterTableModel(editor=self) # The table model associated with the editor: source_model = Instance(TableModel) def _source_model_default(self): return TableModel(editor=self) # The set of columns currently defined on the editor: columns = List(TableColumn) # The currently selected row(s), column(s), or cell(s). selected = Any # The current selected row selected_row = Property(Any, depends_on='selected') selected_indices = Property(Any, depends_on='selected') # Current filter object (should be a TableFilter or callable or None): filter = Any # The indices of the table items currently passing the table filter: filtered_indices = List(Int) # Current filter summary message filter_summary = Str('All items') # Update the filtered contents. update_filter = Event() # The event fired when a cell is clicked on: click = Event # The event fired when a cell is double-clicked on: dclick = Event # The Traits UI associated with the table editor toolbar: toolbar_ui = Instance(UI) # The context menu associated with empty space in the table empty_menu = Instance(QtGui.QMenu) # The context menu associated with the vertical header header_menu = Instance(QtGui.QMenu) # The context menu actions for moving rows up and down header_menu_up = Instance(QtGui.QAction) header_menu_down = Instance(QtGui.QAction) # The index of the row that was last right clicked on its vertical header header_row = Int # Whether to auto-size the columns or not. auto_size = Bool(False) # Dictionary mapping image names to QIcons images = Any({}) # Dictionary mapping ImageResource objects to QIcons image_resources = Any({}) # An image being converted: image = Image #------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #------------------------------------------------------------------------- def init(self, parent): """Finishes initializing the editor by creating the underlying toolkit widget.""" factory = self.factory self.columns = factory.columns[:] if factory.table_view_factory is not None: self.table_view = factory.table_view_factory(editor=self) if factory.source_model_factory is not None: self.source_model = factory.source_model_factory(editor=self) if factory.model_factory is not None: self.model = factory.model_factory(editor=self) # Create the table view and model self.model.setDynamicSortFilter(True) self.model.setSourceModel(self.source_model) self.table_view.setModel(self.model) # Create the vertical header context menu and connect to its signals self.header_menu = QtGui.QMenu(self.table_view) signal = QtCore.SIGNAL('triggered()') insertable = factory.row_factory is not None and not factory.auto_add if factory.editable: if insertable: action = self.header_menu.addAction('Insert new item') QtCore.QObject.connect(action, signal, self._on_context_insert) if factory.deletable: action = self.header_menu.addAction('Delete item') QtCore.QObject.connect(action, signal, self._on_context_remove) if factory.reorderable: if factory.editable and (insertable or factory.deletable): self.header_menu.addSeparator() self.header_menu_up = self.header_menu.addAction('Move item up') QtCore.QObject.connect(self.header_menu_up, signal, self._on_context_move_up) self.header_menu_down = self.header_menu.addAction( 'Move item down') QtCore.QObject.connect(self.header_menu_down, signal, self._on_context_move_down) # Create the empty space context menu and connect its signals self.empty_menu = QtGui.QMenu(self.table_view) action = self.empty_menu.addAction('Add new item') QtCore.QObject.connect(action, signal, self._on_context_append) # When sorting is enabled, the first column is initially displayed with # the triangle indicating it is the sort index, even though no sorting # has actually been done. Sort here for UI/model consistency. if self.factory.sortable and not self.factory.reorderable: self.model.sort(0, QtCore.Qt.AscendingOrder) # Connect to the mode specific selection handler and select the first # row/column/cell. Do this before creating the edit_view to make sure # that it has a valid item to use when constructing its view. smodel = self.table_view.selectionModel() signal = QtCore.SIGNAL( 'selectionChanged(QItemSelection, QItemSelection)') mode_slot = getattr(self, '_on_%s_selection' % factory.selection_mode) QtCore.QObject.connect(smodel, signal, mode_slot) self.table_view.setCurrentIndex(self.model.index(0, 0)) # Create the toolbar if necessary if factory.show_toolbar and len(factory.filters) > 0: main_view = QtGui.QWidget() layout = QtGui.QVBoxLayout(main_view) layout.setContentsMargins(0, 0, 0, 0) self.toolbar_ui = self.edit_traits( parent=parent, kind='subpanel', view=View(Group(Item('filter{View}', editor=factory._filter_editor), Item('filter_summary{Results}', style='readonly'), spring, orientation='horizontal'), resizable=True)) self.toolbar_ui.parent = self.ui layout.addWidget(self.toolbar_ui.control) layout.addWidget(self.table_view) else: main_view = self.table_view # Create auxillary editor and encompassing splitter if necessary mode = factory.selection_mode if (factory.edit_view == ' ') or not mode in ('row', 'rows'): self.control = main_view else: if factory.orientation == 'horizontal': self.control = QtGui.QSplitter(QtCore.Qt.Horizontal) else: self.control = QtGui.QSplitter(QtCore.Qt.Vertical) self.control.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.control.addWidget(main_view) self.control.setStretchFactor(0, 2) # Create the row editor below the table view editor = InstanceEditor(view=factory.edit_view, kind='subpanel') self._ui = self.edit_traits( parent=self.control, kind='subpanel', view=View(Item('selected_row', style='custom', editor=editor, show_label=False, resizable=True, width=factory.edit_view_width, height=factory.edit_view_height), resizable=True, handler=factory.edit_view_handler)) self._ui.parent = self.ui self.control.addWidget(self._ui.control) self.control.setStretchFactor(1, 1) # Connect to the click and double click handlers signal = QtCore.SIGNAL('clicked(QModelIndex)') QtCore.QObject.connect(self.table_view, signal, self._on_click) signal = QtCore.SIGNAL('doubleClicked(QModelIndex)') QtCore.QObject.connect(self.table_view, signal, self._on_dclick) # Make sure we listen for 'items' changes as well as complete list # replacements self.context_object.on_trait_change( self.update_editor, self.extended_name + '_items', dispatch='ui') # Listen for changes to traits on the objects in the list self.context_object.on_trait_change( self.refresh_editor, self.extended_name + '.-', dispatch='ui') # Listen for changes on column definitions self.on_trait_change(self._update_columns, 'columns', dispatch='ui') self.on_trait_change(self._update_columns, 'columns_items', dispatch='ui') # Set up the required externally synchronized traits is_list = (mode in ('rows', 'columns', 'cells')) self.sync_value(factory.click, 'click', 'to') self.sync_value(factory.dclick, 'dclick', 'to') self.sync_value(factory.columns_name, 'columns', is_list=True) self.sync_value(factory.selected, 'selected', is_list=is_list) self.sync_value( factory.selected_indices, 'selected_indices', is_list=is_list) self.sync_value(factory.filter_name, 'filter', 'from') self.sync_value(factory.filtered_indices, 'filtered_indices', 'to') self.sync_value(factory.update_filter_name, 'update_filter', 'from') self.auto_size = self.factory.auto_size # Initialize the ItemDelegates for each column self._update_columns() #------------------------------------------------------------------------- # Disposes of the contents of an editor: #------------------------------------------------------------------------- def dispose(self): """ Disposes of the contents of an editor.""" # Make sure that the auxillary UIs are properly disposed if self.toolbar_ui is not None: self.toolbar_ui.dispose() if self._ui is not None: self._ui.dispose() # Remove listener for 'items' changes on object trait self.context_object.on_trait_change( self.update_editor, self.extended_name + '_items', remove=True) # Remove listener for changes to traits on the objects in the list self.context_object.on_trait_change( self.refresh_editor, self.extended_name + '.-', remove=True) # Remove listeners for column definition changes self.on_trait_change(self._update_columns, 'columns', remove=True) self.on_trait_change( self._update_columns, 'columns_items', remove=True) super(TableEditor, self).dispose() #------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: #------------------------------------------------------------------------- def update_editor(self): """Updates the editor when the object trait changes externally to the editor.""" if self._no_notify: return self.table_view.setUpdatesEnabled(False) try: filtering = len( self.factory.filters) > 0 or self.filter is not None if filtering: self._update_filtering() # invalidate the model, but do not reset it. Resetting the model # may cause problems if the selection sync'ed traits are being used # externally to manage the selections self.model.invalidate() self.table_view.resizeColumnsToContents() if self.auto_size: self.table_view.resizeRowsToContents() finally: self.table_view.setUpdatesEnabled(True) def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ header = self.table_view.horizontalHeader() if header is not None and 'column_state' in prefs: header.restoreState(prefs['column_state']) def save_prefs(self): """ Returns any user preference information associated with the editor. """ prefs = {} header = self.table_view.horizontalHeader() if header is not None: prefs['column_state'] = str(header.saveState()) return prefs #------------------------------------------------------------------------- # Requests that the underlying table widget to redraw itself: #------------------------------------------------------------------------- def refresh_editor(self): """Requests that the underlying table widget to redraw itself.""" self.table_view.viewport().update() #------------------------------------------------------------------------- # Creates a new row object using the provided factory: #------------------------------------------------------------------------- def create_new_row(self): """Creates a new row object using the provided factory.""" factory = self.factory kw = factory.row_factory_kw.copy() if '__table_editor__' in kw: kw['__table_editor__'] = self return self.ui.evaluate(factory.row_factory, *factory.row_factory_args, **kw) #------------------------------------------------------------------------- # Returns the raw list of model objects: #------------------------------------------------------------------------- def items(self): """Returns the raw list of model objects.""" items = self.value if not isinstance(items, SequenceTypes): items = [items] if self.factory and self.factory.reverse: items = ReversedList(items) return items #------------------------------------------------------------------------- # Perform actions without notifying the underlying table view or model: #------------------------------------------------------------------------- def callx(self, func, *args, **kw): """Call a function without notifying the underlying table view or model.""" old = self._no_notify self._no_notify = True try: func(*args, **kw) finally: self._no_notify = old def setx(self, **keywords): """Set one or more attributes without notifying the underlying table view or model.""" old = self._no_notify self._no_notify = True try: for name, value in keywords.items(): setattr(self, name, value) finally: self._no_notify = old #------------------------------------------------------------------------- # Sets the current selection to a set of specified objects: #------------------------------------------------------------------------- def set_selection(self, objects=[], notify=True): """Sets the current selection to a set of specified objects.""" if not isinstance(objects, SequenceTypes): objects = [objects] mode = self.factory.selection_mode indexes = [] flags = QtGui.QItemSelectionModel.ClearAndSelect # In the case of row or column selection, we need a dummy value for the # other dimension that has not been filtered. source_index = self.model.mapToSource(self.model.index(0, 0)) source_row, source_column = source_index.row(), source_index.column() # Selection mode is 'row' or 'rows' if mode.startswith('row'): flags |= QtGui.QItemSelectionModel.Rows items = self.items() for obj in objects: try: row = items.index(obj) except ValueError: continue indexes.append(self.source_model.index(row, source_column)) # Selection mode is 'column' or 'columns' elif mode.startswith('column'): flags |= QtGui.QItemSelectionModel.Columns for name in objects: column = self._column_index_from_name(name) if column != -1: indexes.append(self.source_model.index(source_row, column)) # Selection mode is 'cell' or 'cells' else: items = self.items() for obj, name in objects: try: row = items.index(obj) except ValueError: continue column = self._column_index_from_name(name) if column != -1: indexes.append(self.source_model.index(row, column)) # Perform the selection so that only one signal is emitted selection = QtGui.QItemSelection() for index in indexes: index = self.model.mapFromSource(index) if index.isValid(): self.table_view.setCurrentIndex(index) selection.select(index, index) smodel = self.table_view.selectionModel() try: smodel.blockSignals(not notify) if len(selection.indexes()): smodel.select(selection, flags) else: smodel.clear() finally: smodel.blockSignals(False) #------------------------------------------------------------------------- # Private methods: #------------------------------------------------------------------------- def _column_index_from_name(self, name): """Returns the index of the column with the given name or -1 if no column exists with that name.""" for i, column in enumerate(self.columns): if name == column.name: return i return -1 def _customize_filters(self, filter): """Allows the user to customize the current set of table filters.""" filter_editor = TableFilterEditor(editor=self) ui = filter_editor.edit_traits(parent=self.control) if ui.result: self.factory.filters = filter_editor.templates self.filter = filter_editor.selected_filter else: self.setx(filter=filter) def _update_filtering(self): """Update the filter summary and the filtered indices.""" items = self.items() num_items = len(items) f = self.filter if f is None: self._filtered_cache = None self.filtered_indices = range(num_items) self.filter_summary = 'All %i items' % num_items else: if not callable(f): f = f.filter self._filtered_cache = fc = [f(item) for item in items] self.filtered_indices = fi = [i for i, ok in enumerate(fc) if ok] self.filter_summary = '%i of %i items' % (len(fi), num_items) def _add_image(self, image_resource): """ Adds a new image to the image map. """ image = image_resource.create_icon() self.image_resources[image_resource] = image self.images[image_resource.name] = image return image def _get_image(self, image): """ Converts a user specified image to a QIcon. """ if isinstance(image, basestring): self.image = image image = self.image if isinstance(image, ImageResource): result = self.image_resources.get(image) if result is not None: return result return self._add_image(image) return self.images.get(image) #-- Trait Property getters/setters --------------------------------------- @cached_property def _get_selected_row(self): """Gets the selected row, or the first row if multiple rows are selected.""" mode = self.factory.selection_mode if mode.startswith('column'): return None elif mode == 'row': return self.selected try: if mode == 'rows': return self.selected[0] elif mode == 'cell': return self.selected[0] elif mode == 'cells': return self.selected[0][0] except IndexError: return None @cached_property def _get_selected_indices(self): """Gets the row,column indices which match the selected trait""" selection_items = self.table_view.selectionModel().selection() indices = self.model.mapSelectionToSource(selection_items).indexes() return [(index.row(), index.column()) for index in indices] def _set_selected_indices(self, indices): selected = [] for row, col in indices: selected.append((self.value[row], self.columns[col].name)) self.selected = selected self.set_selection(self.selected, False) return #-- Trait Change Handlers ------------------------------------------------ def _filter_changed(self, old_filter, new_filter): """Handles the current filter being changed.""" if not self._no_notify: if new_filter is customize_filter: do_later(self._customize_filters, old_filter) else: self._update_filtering() self.model.invalidate() self.set_selection(self.selected) def _update_columns(self): """Handle the column list being changed.""" self.table_view.setItemDelegate(TableDelegate(self.table_view)) for i, column in enumerate(self.columns): if column.renderer: self.table_view.setItemDelegateForColumn(i, column.renderer) self.model.reset() self.table_view.resizeColumnsToContents() if self.auto_size: self.table_view.resizeRowsToContents() def _selected_changed(self, new): """Handle the selected row/column/cell being changed externally.""" if not self._no_notify: self.set_selection(self.selected, notify=False) def _update_filter_changed(self): """ The filter has changed internally. """ self._filter_changed(self.filter, self.filter) #-- Event Handlers ------------------------------------------------------- def _on_row_selection(self, added, removed): """Handle the row selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedRows() if len(indexes): index = self.model.mapToSource(indexes[0]) selected = items[index.row()] else: selected = None self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_rows_selection(self, added, removed): """Handle the rows selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedRows() selected = [items[self.model.mapToSource(index).row()] for index in indexes] self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_column_selection(self, added, removed): """Handle the column selection being changed.""" indexes = self.table_view.selectionModel().selectedColumns() if len(indexes): index = self.model.mapToSource(indexes[0]) selected = self.columns[index.column()].name else: selected = '' self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_columns_selection(self, added, removed): """Handle the columns selection being changed.""" indexes = self.table_view.selectionModel().selectedColumns() selected = [self.columns[self.model.mapToSource(index).column()].name for index in indexes] self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_cell_selection(self, added, removed): """Handle the cell selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedIndexes() if len(indexes): index = self.model.mapToSource(indexes[0]) obj = items[index.row()] column_name = self.columns[index.column()].name else: obj = None column_name = '' selected = (obj, column_name) self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_cells_selection(self, added, removed): """Handle the cells selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedIndexes() selected = [] for index in indexes: index = self.model.mapToSource(index) obj = items[index.row()] column_name = self.columns[index.column()].name selected.append((obj, column_name)) self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_click(self, index): """Handle a cell being clicked.""" index = self.model.mapToSource(index) column = self.columns[index.column()] obj = self.items()[index.row()] # Fire the same event on the editor after mapping it to a model object # and column name: self.click = (obj, column) # Invoke the column's click handler: column.on_click(obj) def _on_dclick(self, index): """Handle a cell being double clicked.""" index = self.model.mapToSource(index) column = self.columns[index.column()] obj = self.items()[index.row()] # Fire the same event on the editor after mapping it to a model object # and column name: self.dclick = (obj, column) # Invoke the column's double-click handler: column.on_dclick(obj) def _on_context_insert(self): """Handle 'insert item' being selected from the header context menu.""" self.model.insertRow(self.header_row) def _on_context_append(self): """Handle 'add item' being selected from the empty space context menu.""" self.model.insertRow(self.model.rowCount()) def _on_context_remove(self): """Handle 'remove item' being selected from the header context menu.""" self.model.removeRow(self.header_row) def _on_context_move_up(self): """Handle 'move up' being selected from the header context menu.""" self.model.moveRow(self.header_row, self.header_row - 1) def _on_context_move_down(self): """Handle 'move down' being selected from the header context menu.""" self.model.moveRow(self.header_row, self.header_row + 1)
class JitterPlot(AbstractPlotRenderer): """A renderer for a jitter plot, a 1D plot with some width in the dimension perpendicular to the primary axis. Useful for understanding dense collections of points. """ # The data source of values index = Instance(ArrayDataSource) # The single mapper that this plot uses mapper = Instance(AbstractMapper) # Just an alias for "mapper" index_mapper = Property(lambda obj, attr: getattr(obj, "mapper"), lambda obj, attr, val: setattr(obj, "mapper", val)) x_mapper = Property() y_mapper = Property() orientation = Enum("h", "v") # The size, in pixels, of the area over which to spread the data points # along the dimension orthogonal to the index direction. jitter_width = Int(50) # How the plot should center itself along the orthogonal dimension if the # component's width is greater than the jitter_width #align = Enum("center", "left", "right", "top", "bottom") # The type of marker to use. This is a mapped trait using strings as the # keys. marker = MarkerTrait # The pixel size of the marker, not including the thickness of the outline. marker_size = Float(4.0) # The CompiledPath to use if **marker** is set to "custom". This attribute # must be a compiled path for the Kiva context onto which this plot will # be rendered. Usually, importing kiva.GraphicsContext will do # the right thing. custom_symbol = Any # The function which actually renders the markers render_markers_func = Callable(render_markers) # The thickness, in pixels, of the outline to draw around the marker. If # this is 0, no outline is drawn. line_width = Float(1.0) # The fill color of the marker. color = black_color_trait # The color of the outline to draw around the marker. outline_color = black_color_trait # Override the base class default for **origin**, which specifies corners. # Since this is a 1D plot, it only makes sense to have the origin at the # edges. origin = Enum("bottom", "top", "left", "right") #------------------------------------------------------------------------ # Built-in selection handling #------------------------------------------------------------------------ # The name of the metadata attribute to look for on the datasource for # determine which points are selected and which are not. The metadata # value returned should be a *list* of numpy arrays suitable for masking # the values returned by index.get_data(). selection_metadata_name = Str("selections") # The color to use to render selected points selected_color = black_color_trait # Alpha value to apply to points that are not in the set of "selected" # points unselected_alpha = Float(0.3) unselected_line_width = Float(0.0) #------------------------------------------------------------------------ # Private traits #------------------------------------------------------------------------ _cache_valid = Bool(False) _cached_data_pts = Any() _cached_data_pts_sorted = Any() _cached_data_argsort = Any() _screen_cache_valid = Bool(False) _cached_screen_pts = Any() _cached_screen_map = Any() # dict mapping index to value points # The random number seed used to generate the jitter. We store this # so that the jittering is stable as the data is replotted. _jitter_seed = Trait(None, None, Int) #------------------------------------------------------------------------ # Component/AbstractPlotRenderer interface #------------------------------------------------------------------------ def map_screen(self, data_array): """ Maps an array of data points into screen space and returns it as an array. Although the orthogonal (non-scaled) axis does not have a mapper, this method returns the scattered values in that dimension. Implements the AbstractPlotRenderer interface. """ if len(data_array) == 0: return np.zeros(0) if self._screen_cache_valid: sm = self._cached_screen_map new_x = [x for x in data_array if x not in sm] if new_x: new_y = self._make_jitter_vals(len(new_x)) sm.update(dict( (new_x[i], new_y[i]) for i in range(len(new_x)))) xs = self.mapper.map_screen(data_array) ys = [sm[x] for x in xs] else: if self._jitter_seed is None: self._set_seed(data_array) xs = self.mapper.map_screen(data_array) ys = self._make_jitter_vals(len(data_array)) if self.orientation == "h": return np.vstack((xs, ys)).T else: return np.vstack((ys, xs)).T def _make_jitter_vals(self, numpts): vals = np.random.uniform(0, self.jitter_width, numpts) if self.orientation == "h": ymin = self.y height = self.height vals += ymin + height / 2 - self.jitter_width / 2 else: xmin = self.x width = self.width vals += xmin + width / 2 - self.jitter_width / 2 return vals def map_data(self, screen_pt): """ Maps a screen space point into the index space of the plot. """ x, y = screen_pt if self.orientation == "v": x, y = y, x return self.mapper.map_data(x) def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, \ index_only = True): """ Maps a screen space point to an index into the plot's index array(s). """ screen_points = self._cached_screen_pts if len(screen_points) == 0: return None data_pt = self.map_data(screen_pt) if ((data_pt < self.mapper.range.low) or \ (data_pt > self.mapper.range.high)) and outside_returns_none: return None if self._cached_data_pts_sorted is None: self._cached_data_argsort = np.argsort(self._cached_data_pts) self._cached_data_pts_sorted = self._cached_data_pts[ self._cached_data_argsort] data = self._cached_data_pts_sorted try: ndx = reverse_map_1d(data, data_pt, "ascending") except IndexError, e: if outside_returns_none: return None else: if data_pt < data[0]: return 0 else: return len(data) - 1 orig_ndx = self._cached_data_argsort[ndx] if threshold == 0.0: return orig_ndx sx, sy = screen_points[orig_ndx] if sqrt((screen_pt[0] - sx)**2 + (screen_pt[1] - sy)**2) <= threshold: return orig_ndx else: return None
class InstSource(HasPrivateTraits): """Expose measurement information from a inst file. Parameters ---------- file : File Path to the BEM file (*.fif). Attributes ---------- fid : Array, shape = (3, 3) Each row contains the coordinates for one fiducial point, in the order Nasion, RAP, LAP. If no file is set all values are 0. """ file = File(exists=True, filter=['*.fif']) inst_fname = Property(Str, depends_on='file') inst_dir = Property(depends_on='file') inst = Property(depends_on='file') points_filter = Any(desc="Index to select a subset of the head shape " "points") n_omitted = Property(Int, depends_on=['points_filter']) # head shape inst_points = Property(depends_on='inst', desc="Head shape points in the " "inst file(n x 3 array)") points = Property(depends_on=['inst_points', 'points_filter'], desc="Head " "shape points selected by the filter (n x 3 array)") # fiducials fid_dig = Property(depends_on='inst', desc="Fiducial points " "(list of dict)") fid_points = Property(depends_on='fid_dig', desc="Fiducial points {ident: " "point} dict}") lpa = Property(depends_on='fid_points', desc="LPA coordinates (1 x 3 " "array)") nasion = Property(depends_on='fid_points', desc="Nasion coordinates (1 x " "3 array)") rpa = Property(depends_on='fid_points', desc="RPA coordinates (1 x 3 " "array)") view = View( VGroup(Item('file'), Item('inst_fname', show_label=False, style='readonly'))) @cached_property def _get_n_omitted(self): if self.points_filter is None: return 0 else: return np.sum(self.points_filter == False) # noqa: E712 @cached_property def _get_inst(self): if self.file: info = read_info(self.file) if info['dig'] is None: error( None, "The selected FIFF file does not contain " "digitizer information. Please select a different " "file.", "Error Reading FIFF File") self.reset_traits(['file']) else: return info @cached_property def _get_inst_dir(self): return os.path.dirname(self.file) @cached_property def _get_inst_fname(self): if self.file: return os.path.basename(self.file) else: return '-' @cached_property def _get_inst_points(self): if not self.inst: return np.zeros((1, 3)) points = np.array([ d['r'] for d in self.inst['dig'] if d['kind'] == FIFF.FIFFV_POINT_EXTRA ]) return points @cached_property def _get_points(self): if self.points_filter is None: return self.inst_points else: return self.inst_points[self.points_filter] @cached_property def _get_fid_dig(self): # noqa: D401 """Fiducials for info['dig'].""" if not self.inst: return [] dig = self.inst['dig'] dig = [d for d in dig if d['kind'] == FIFF.FIFFV_POINT_CARDINAL] return dig @cached_property def _get_fid_points(self): if not self.inst: return {} digs = dict((d['ident'], d) for d in self.fid_dig) return digs @cached_property def _get_nasion(self): if self.fid_points: return self.fid_points[FIFF.FIFFV_POINT_NASION]['r'][None, :] else: return np.zeros((1, 3)) @cached_property def _get_lpa(self): if self.fid_points: return self.fid_points[FIFF.FIFFV_POINT_LPA]['r'][None, :] else: return np.zeros((1, 3)) @cached_property def _get_rpa(self): if self.fid_points: return self.fid_points[FIFF.FIFFV_POINT_RPA]['r'][None, :] else: return np.zeros((1, 3)) def _file_changed(self): self.reset_traits(('points_filter', ))
class HB_HeapBrowser ( HasPrivateTraits ): # The name of the plugin: name = Str( 'Heap Browser' ) # The current heap statistics table entries: current_counts = List( HB_ClassCount ) # The list of currently selected counts: selected_counts = List( HB_ClassCount ) # The sting that all class names must contain: filter_name = Str( event = 'heap_updated' ) # The threshold for the number of instances to display: filter_instances = Int( event = 'heap_updated' ) # The threshold for the amount of change to display: filter_change = Int( event = 'heap_updated' ) # Number of items in the current_counts list: current_classes = Property( depends_on = 'current_counts' ) # Total number of instances in the current_counts list: current_instances = Property( depends_on = 'current_counts' ) # Total change in the current_counts list: current_change = Property( depends_on = 'current_counts' ) # Indicates whether or not the selection set is empty or not: has_counts = Property # The set of all current active object ids: current_ids = Any( set() ) # Current heap statistics: current_heap = Any( {} ) # Previous heap statistics: previous_heap = Any( {} ) # Event fired when anything changes that affects the heap statistics view: heap_updated = Event # The list of object classes currently being ignored: ignored_classes = Any( set() ) # The list of object classes currently being shown: shown_classes = Any( set() ) # Refresh button: refresh = Button( 'Refresh' ) # Baseline button (to create a new 'baseline' set): baseline = Button( 'Baseline' ) # Hide all selected items button: hide_selected = Button( 'Hide Selected' ) # Show only the selected items button: show_selected = Button( 'Show Selected' ) # Show all items button: show_all = Button( 'Show All' ) # Show the details of the current selected items button: show_details = Button( 'Show Details' ) # Event fired when a 'counts' item is double-clicked: counts_dclick = Event #-- View 2 Traits ---------------------------------------------------------- # The list of objects which the user has requested detailed information for: details = List( HB_Detail ) #-- View 3 Traits ---------------------------------------------------------- # The list of object referrer trees: referrers = List( HB_Referrers ) #-- View 4 Traits ---------------------------------------------------------- # The list of object baselines: baselines = List( HB_Baseline ) #-- Traits View Definitions ------------------------------------------------ heap_stats_view = View( VSplit( VGroup( HGroup( Item( 'filter_name', id = 'filter_name', label = 'Name', width = -90, editor = HistoryEditor( auto_set = True ), ), '_', Item( 'filter_instances', label = 'Min Instances', width = -36 ), '_', Item( 'filter_change', label = 'Min Change', width = -36 ), '_', spring, '_', Item( 'current_classes', label = 'Classes', style = 'readonly', format_func = commatize, width = -35 ), '_', Item( 'current_instances', label = 'Instances', style = 'readonly', format_func = commatize, width = -50 ), '_', Item( 'current_change', label = 'Change', style = 'readonly', format_func = commatize, width = -50 ), ), Item( 'current_counts', id = 'current_counts', editor = counts_table_editor ), '_', HGroup( Item( 'refresh', tooltip = 'Refresh the heap statistics' ), Item( 'baseline', tooltip = 'Create a new baseline set' ), spring, Item( 'hide_selected', tooltip = 'Remove all selected items from the view', enabled_when = 'has_counts' ), Item( 'show_selected', tooltip = 'Show only the selected items in the view', enabled_when = 'has_counts' ), Item( 'show_all', tooltip = 'Show all items again', enabled_when = '(len( ignored_classes ) > 0) ' 'or (len( shown_classes ) > 0)' ), '_', Item( 'show_details', tooltip = 'Show the instances of the selected ' 'classes', enabled_when = 'has_counts' ), show_labels = False, ), label = 'Heap', show_labels = False, dock = 'tab' ), Item( 'details', id = 'details', style = 'custom', dock = 'tab', editor = ListEditor( use_notebook = True, deletable = True, dock_style = 'tab', export = 'DockWindowShell', page_name = '.short_name' ) ), Item( 'referrers', id = 'referrers', style = 'custom', dock = 'tab', editor = ListEditor( use_notebook = True, deletable = True, dock_style = 'tab', export = 'DockWindowShell', page_name = '.name' ) ), Item( 'baselines', id = 'baselines', style = 'custom', dock = 'tab', editor = ListEditor( use_notebook = True, deletable = True, dock_style = 'tab', export = 'DockWindowShell', page_name = 'Baseline' ) ), id = 'splitter', show_labels = False ), title = 'Heap Browser', id = 'etsdevtools.developer.tools.heap_browser.HB_HeapBrowser', width = 0.5, height = 0.75, resizable = True, key_bindings = heap_browser_key_bindings ) #-- Property Implementations ----------------------------------------------- def _get_has_counts ( self ): return (len( self.selected_counts ) > 0) def _get_current_classes ( self ): return len( self.current_counts ) @cached_property def _get_current_instances ( self ): return reduce( lambda l, r: l + r.instances, self.current_counts, 0 ) @cached_property def _get_current_change ( self ): return reduce( lambda l, r: l + r.change, self.current_counts, 0 ) #-- Event Handlers --------------------------------------------------------- def _heap_updated_changed ( self ): """ Handles the 'heap_updated' event being fired by rebuilding the heap class counts list. """ ignored = ignored_classes shown = self.shown_classes no_show = (len( shown ) == 0) if no_show: ignored = ignored_classes.union( self.ignored_classes ) old = self.previous_heap filter_name = self.filter_name.lower() filter_instances = self.filter_instances filter_change = self.filter_change counts = [ HB_ClassCount( name = name, instances = instances, change = instances - old.get( name, 0 ) ) for name, instances in self.current_heap.iteritems() if (name not in ignored) and (no_show or (name in shown)) and (name.lower().find( filter_name ) >= 0) and (instances >= filter_instances) and (abs( instances - old.get( name, 0 ) ) >= filter_change) ] counts.sort( lambda l, r: cmp( l.lower_name, r.lower_name ) ) self.current_counts = counts def _refresh_changed ( self, ignore = None ): """ Handles the 'Refresh' button being clicked. """ self.update() def _baseline_changed ( self, ignore = None ): """ Handles the 'Baseline' button being clicked. """ self.baselines.append( self._create_baseline() ) def _hide_selected_changed ( self, info = None ): """ Handles the 'Hide Selected' button being clicked. """ self.ignored_classes.update( set( [ item.name for item in self.selected_counts ] ) ) self.shown_classes.clear() del self.selected_counts[:] self.heap_updated = True def _show_selected_changed ( self, info = None ): """ Handles the 'Show Selected' button being clicked. """ self.shown_classes = set( set( [ item.name for item in self.selected_counts ] ) ) self.ignored_classes.clear() del self.selected_counts[:] self.heap_updated = True def _show_all_changed ( self, info = None ): """ Handles the 'Show All' button being clicked. """ self.ignored_classes.clear() self.shown_classes.clear() del self.selected_counts[:] self.heap_updated = True def _show_details_changed ( self, ignore = True ): """ Handles the 'Show Details' button being clicked. """ self.details.extend( [ HB_Detail( name = item.name, owner = self ) for item in self.selected_counts ] ) def _counts_dclick_changed ( self, event ): """ Handles the user double-clicking a 'counts' table entry in order to show its details. """ item, column = event self.details.append( HB_Detail( name = item.name, owner = self ) ) def _set_filter_change_1 ( self, info = None ): """ Sets the filter change value to 1. """ self.filter_change = 1 def _clear_filter ( self, info = None ): """ Clears all current filter variable settings back to their defaults. """ self.filter_change = filter_instances = 0 self.filter_name = '' def _select_all ( self, info = None ): """ Selects all current displayed items. """ self.selected_counts = self.current_counts def _unselect_all ( self, info = None ): """ Unselects all currently selected items. """ self.selected_counts = [] #-- Public Methods --------------------------------------------------------- def update ( self ): """ Updates the heap statistics. """ self.previous_heap = old = self.current_heap self.current_heap = new = {} self.current_ids = ids = set() gc.collect() for object in gc.get_objects(): name = object_name( object ) if name not in ignored_classes: new[ name ] = new.setdefault( name, 0 ) + 1 ids.add( id( object ) ) self.heap_updated = True # Indicate that each class detail should update its instance # information: for detail in self.details: detail.update = True # Indicate that each referrer should update its instance information: for referrer in self.referrers: referrer.root.update = True # Indicate that each baseline view should update its contents: for baseline in self.baselines: baseline.update = ids #-- Private Methods -------------------------------------------------------- def _create_baseline ( self ): """ Creates a new baseline object. """ current_ids = self.current_ids object_ids = {} gc.collect() for object in gc.get_objects(): if id( object ) not in current_ids: name = object_name( object ) if name not in ignored_classes: object_ids[ id( object ) ] = name return HB_Baseline( object_ids = object_ids, owner = self )
class _Tool(HasTraits): """ A tool bar tool representation of an action item. """ #### '_Tool' interface #################################################### # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the tool is not part of such a # group). group = Any # The toolkit control. control = Any() # The toolkit control id. control_id = None ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, tool_bar, image_cache, item, controller, show_labels): """ Creates a new tool bar tool for an action item. """ self.item = item self.tool_bar = tool_bar action = item.action if action.style == 'widget': widget = action.create_control(tool_bar) self.control = tool_bar.addWidget(widget) elif action.image is None: self.control = tool_bar.addAction(action.name) else: size = tool_bar.iconSize() image = action.image.create_icon((size.width(), size.height())) self.control = tool_bar.addAction(image, action.name) self.control.triggered.connect(self._qt4_on_triggered) self.control.setToolTip(action.tooltip) self.control.setWhatsThis(action.description) self.control.setEnabled(action.enabled) self.control.setVisible(action.visible) if action.style == 'toggle': self.control.setCheckable(True) self.control.setChecked(action.checked) elif action.style == 'radio': # Create an action group if it hasn't already been done. try: ag = item.parent._qt4_ag except AttributeError: ag = item.parent._qt4_ag = QtGui.QActionGroup(parent) self.control.setActionGroup(ag) self.control.setCheckable(True) self.control.setChecked(action.checked) # Keep a reference in the action. This is done to make sure we live as # long as the action (and still respond to its signals) and don't die # if the manager that created us is garbage collected. self.control._tool_instance = self # Listen for trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_visible_changed, 'visible') action.on_trait_change(self._on_action_checked_changed, 'checked') action.on_trait_change(self._on_action_name_changed, 'name') action.on_trait_change(self._on_action_accelerator_changed, 'accelerator') # Detect if the control is destroyed. self.control.destroyed.connect(self._qt4_on_destroyed) if controller is not None: self.controller = controller controller.add_to_toolbar(self) ########################################################################### # Private interface. ########################################################################### def _qt4_on_destroyed(self, control=None): """ Delete the reference to the control to avoid attempting to talk to it again. """ self.control = None def _qt4_on_triggered(self): """ Called when the tool bar tool is clicked. """ action = self.item.action action_event = ActionEvent() # Perform the action! if self.controller is not None: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. This is also # useful as Traits UI controllers *never* require the event. argspec = getargspec(self.controller.perform) # If the only arguments are 'self' and 'action' then don't pass # the event! if len(argspec.args) == 2: self.controller.perform(action) else: self.controller.perform(action, action_event) else: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. argspec = getargspec(action.perform) # If the only argument is 'self' then don't pass the event! if len(argspec.args) == 1: action.perform() else: action.perform(action_event) #### Trait event handlers ################################################# def _enabled_changed(self): """ Called when our 'enabled' trait is changed. """ if self.control is not None: self.control.setEnabled(self.enabled) def _visible_changed(self): """ Called when our 'visible' trait is changed. """ if self.control is not None: self.control.setVisible(self.visible) def _checked_changed(self): """ Called when our 'checked' trait is changed. """ if self.control is not None: self.control.setChecked(self.checked) def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ if self.control is not None: self.control.setEnabled(action.enabled) def _on_action_visible_changed(self, action, trait_name, old, new): """ Called when the visible trait is changed on an action. """ if self.control is not None: self.control.setVisible(action.visible) def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if self.control is not None: self.control.setChecked(action.checked) def _on_action_name_changed(self, action, trait_name, old, new): """ Called when the name trait is changed on an action. """ if self.control is not None: self.control.setText(action.name) def _on_action_accelerator_changed(self, action, trait_name, old, new): """ Called when the accelerator trait is changed on an action. """ if self.control is not None: self.control.setShortcut(action.accelerator)