def __init__(self, default_value, allow_none=True, **metadata): from openmdao.main.vartree import VariableTree # Break import loop on VariableTree if isinstance(default_value, VariableTree): klass = default_value.__class__ if 'iotype' in metadata: default_value._iotype = metadata['iotype'] else: metadata['iotype'] = default_value.iotype else: raise TypeError('default_value must be an instance of VariableTree' ' or subclass') metadata.setdefault('copy', 'deep') self._allow_none = allow_none self.klass = klass self._instance = Instance(klass=klass, allow_none=False, factory=None, args=None, kw=None, **metadata) self._instance.default_value = default_value super(VarTree, self).__init__(default_value, **metadata)
def __init__(self, klass=object, allow_none=True, factory=None, args=None, kw=None, **metadata): default_value = None try: iszopeiface = issubclass(klass, zope.interface.Interface) except TypeError: iszopeiface = False if not isclass(klass): default_value = klass klass = klass.__class__ metadata.setdefault('copy', 'deep') self._allow_none = allow_none self.klass = klass if has_interface(klass, IContainer) or \ (isclass(klass) and IContainer.implementedBy(klass)): self._is_container = True else: self._is_container = False if iszopeiface: self._instance = None self.factory = factory self.args = args self.kw = kw else: self._instance = Instance(klass=klass, allow_none=allow_none, factory=factory, args=args, kw=kw, **metadata) if default_value: self._instance.default_value = default_value else: default_value = self._instance.default_value if klass.__name__ == 'VariableTree': raise TypeError('Slotting of VariableTrees is not supported,' ' please use VarTree instead') super(Slot, self).__init__(default_value, **metadata)
def __init__(self, **kwargs): '''Constructor for UserInterface object Adds panels and plots to a userinterface window. ''' super(UserInterface, self).__init__() self.add_trait('rawviewer', RawViewer()) self.add_trait('cpanel', ControlPanel()) self.add_trait('mdpanel', MetadataPanel()) self.add_trait('messagelog', MessageLog()) self.rawviewer.startProcessJob() self.cpanel.sync_trait('datalistlength', self.rawviewer) self.imagepanel = Instance(Component) self.createImagePanel() self.rrpanel = Instance(Component) self.rrpanel = VPlotContainer(stack_order = 'top_to_bottom', resizeable='', use_backbuffer=True, bgcolor='transparent') self.rrpanel.get_preferred_size()
class Graph3D(HasTraits): scene = Instance(MlabSceneModel, ()) # def _scene_default(self): # s = MlabSceneModel() # pt_gen = point_generator(5, 0, 0) # j = 0 # xx = [] # yy = [] # zz = [] # for p1, p2 in [((-5, 0), (5, 0)), ((5, 1), (-5, 1))]: # # for p1, p2 in pt_gen: # # #move to p1 # #move to p2 # moving = True # i = 0 # zs = [] # xs = [] # ys = [] # while moving: # if i == 11: # break # # x = p1[0] - i if j % 2 else p1[0] + i # mag = 40 - x * x # zs.append(mag) # # xs.append(x) # ys.append(p1[1]) # # if j % 2: # data = sorted(zip(xs, ys, zs), key = lambda d:d[0]) # # xs = [d[0] for d in data] # ys = [d[1] for d in data] # zs = [d[2] for d in data] # # i += 1 # j += 1 # xx.append(xs) # yy.append(ys) # zz.append(zs) # # s.mlab.mesh(asarray(xx), asarray(yy), asarray(zz)) # # s.mlab.plot3d(asarray(xx)[0], asarray(yy)[0], asarray(zz)[0], asarray(zz)[0]) # return s # xs = [] # ys = [] # zs = [] # for i in range(5): # # x = linspace(-5, 5, 100) # y = ones(100) * i # z = 10 - 0.2 * x * x # xs.append(x) # ys.append(y) # zs.append(z) # # s.mlab.mesh(asarray(xs), asarray(ys), asarray(zs) # #z, # # line_width = 50, # # tube_radius = None, # #representation = 'wireframe' # ) # return s def clear(self): self.scene.mlab.clf() def plot_data(self, z, func='surf', outline=True, **kw): mlab = self.scene.mlab getattr(mlab, func)(z, **kw) if outline: mlab.outline() def plot_surf(self, *args, **kw): mlab = self.scene.mlab mlab.surf(*args, **kw) mlab.outline() mlab.axes() def plot_points(self, x, y, z, color=None): mlab = self.scene.mlab if color: pts = mlab.points3d(x, y, z, z, scale_mode='none', scale_factor=0.1) else: pts = mlab.points3d(x, y, z, scale_mode='none', scale_factor=0.1) mesh = mlab.pipeline.delaunay2d(pts) mlab.pipeline.surface(mesh) def plot_lines(self, x, y, z): import numpy as np # n_mer, n_long = 6, 11 # pi = numpy.pi # dphi = pi / 1000.0 # phi = numpy.arange(0.0, 2 * pi + 0.5 * dphi, dphi) # mu = phi * n_mer # x = numpy.cos(mu) * (1 + numpy.cos(n_long * mu / n_mer) * 0.5) # y = numpy.sin(mu) * (1 + numpy.cos(n_long * mu / n_mer) * 0.5) # print x # z = numpy.sin(n_long * mu / n_mer) * 0.5 # # l = self.scene.mlab.plot3d(x, y, z, numpy.sin(mu), tube_radius=0.025, colormap='Spectral') # return l x = np.array(x) y = np.array(y) z = np.array(z) self.scene.mlab.plot3d(x, y, z) def traits_view(self): # self.scene.mlab.points3d(x, y, z) kw = dict() klass = Scene use_mayavi_toolbar = True use_raw_toolbar = False if use_mayavi_toolbar: klass = MayaviScene elif use_raw_toolbar: klass = None if klass is not None: kw['scene_class'] = klass v = View(Item('scene', show_label=False, height=400, width=400, resizable=True, editor=SceneEditor(**kw ))) return v
class UserInterface(HasTraits): '''UserInterface Class GUI for SrXes. Uses traits to watch for user interaction and then adds jobs to a queue for processing. ''' def __init__(self, **kwargs): '''Constructor for UserInterface object Adds panels and plots to a userinterface window. ''' super(UserInterface, self).__init__() self.add_trait('rawviewer', RawViewer()) self.add_trait('cpanel', ControlPanel()) self.add_trait('mdpanel', MetadataPanel()) self.add_trait('messagelog', MessageLog()) self.rawviewer.startProcessJob() self.cpanel.sync_trait('datalistlength', self.rawviewer) self.imagepanel = Instance(Component) self.createImagePanel() self.rrpanel = Instance(Component) self.rrpanel = VPlotContainer(stack_order = 'top_to_bottom', resizeable='', use_backbuffer=True, bgcolor='transparent') self.rrpanel.get_preferred_size() # TODO: Adjust view view = View( HSplit( VSplit( UItem('imagepanel', editor=ComponentEditor(), padding=0), UItem('mdpanel', style="custom", padding=5, height=85, width=700) ), VGroup( UItem('cpanel', style="custom", width=-430, padding=10), UItem('messagelog', style ="custom", width=-430, padding =10), UItem('rrpanel', editor=ComponentEditor(), style='custom') ), show_labels=False, ), resizable = True, height = 0.96, width = 1.0, handler = PyXDAHandler(), buttons = NoButtons, title = 'SrXes', icon = LOGO) ############################# # UI Action Handling ############################# @on_trait_change('cpanel.left_arrow', post_init=True) def _left_arrow_fired(self): '''Left arrow has been pushed Changes the image display to the left one over if it exists. ''' self.rawviewer.jobqueue.put(['updatecache', ['left']]) return @on_trait_change('cpanel.right_arrow', post_init=True) def _right_arrow_fired(self): '''Right arrow has been pushed Changes the image display to the right one over if it exists. ''' self.rawviewer.jobqueue.put(['updatecache', ['right']]) return @on_trait_change('cpanel.generate', post_init=True) def _generate_fired(self): '''Generate Intensity button has been pushed Creates a reduced representation plot in the GUI. ''' self.rawviewer.jobqueue.put(['plotrr', [self.cpanel.rrchoice]]) time.sleep(0.5) self.updateRRPanel(self.cpanel.rrchoice) return @on_trait_change('cpanel.dirpath', post_init=True) def _dirpath_changed(self): '''Directory path has changed If there are tiff images in the folder path, they will be loaded and the first image will be plotted to the screen. If there are no tiff images or the path is invalid, rawviewer.message will be changed to a string explaining the error. ''' self.rawviewer.jobqueue.put(['startload', [self.cpanel.dirpath]]) @on_trait_change('rawviewer.pic', post_init=True) def _pic_changed(self): '''The displayed 2D image has been changed Changes the control panel index, and the metadata associated with it. ''' pic = self.rawviewer.pic self.cpanel.index = pic.n + 1 self.mdpanel.name = pic.name if pic.metadata: for key in pic.metadata.keys(): setattr(self.mdpanel, key, pic.metadata[key]) return @on_trait_change('rawviewer.display.filename', post_init=True) def _filename_changed(self): '''The filename of the 2D image has changed Changes the displayed filename to the updated one. ''' print 'filename changed' if self.rawviewer.display.filename == -1: self.cpanel.filename = '' else: self.cpanel.filename = self.rawviewer.datalist[self.rawviewer.display.filename].name @on_trait_change('rawviewer.loadimage.message', post_init=True) def handleMessage(self): '''Rawviewer.message has changed Displays the new message in messagelog. If there is already a message inside messagelog, the new one is plotted below it. ''' if self.rawviewer.loadimage.message != '': if self.messagelog.line_pos == 0: self.messagelog.line1 = 'Out: ' + self.rawviewer.loadimage.message self.messagelog.line_pos = self.messagelog.line_pos +1 return if self.messagelog.line_pos == 1: self.messagelog.line2 = 'Out: ' + self.rawviewer.loadimage.message self.messagelog.line_pos = self.messagelog.line_pos + 1 return if self.messagelog.line_pos == 2: self.messagelog.line3 = 'Out: ' + self.rawviewer.loadimage.message self.messagelog.line_pos = 0 return return # TODO: Update def createImagePanel(self): '''Creates the Image Panel Creates the image panel that contains the 2D image, colorbarm histogram, and 1D slice. ''' cont = VPlotContainer(stack_order = 'top_to_bottom', bgcolor = 'transparent', use_backbuffer=True) imageplot = getattr(self.rawviewer, 'imageplot') colorbar = getattr(self.rawviewer.display, 'colorbar') histogram = getattr(self.rawviewer, 'histogram') plot1d = getattr(self.rawviewer, 'plot1d') imgcont = HPlotContainer(imageplot, colorbar, bgcolor = 'transparent', spacing = 20.0) cont.add(imgcont) cont.add(histogram) cont.add(plot1d) self.imagepanel = cont self.imagepanel.get_preferred_size() self.imagepanel.invalidate_draw() return def updateRRPanel(self, choice): '''Updates the Reduced Representation Panel Args: choice: the new variable for the RR. eg: mean, total intensity... ''' rrplots = getattr(self.rawviewer, 'rrplots') if rrplots[choice] not in self.rrpanel._components: self.rrpanel.add(rrplots[choice]) self.rrpanel.invalidate_and_redraw() return
class Explorer3D(HasTraits): """This class basically allows you to create a 3D cube of data (a numpy array), specify an equation for the scalars and view it using the mayavi plugin. """ ######################################## # Traits. # Set by envisage when this is offered as a service offer. window = Instance('pyface.workbench.api.WorkbenchWindow') # The equation that generates the scalar field. equation = Str('sin(x*y*z)/(x*y*z)', desc='equation to evaluate (enter to set)', auto_set=False, enter_set=True) # Dimensions of the cube of data. dimensions = Array(value=(128, 128, 128), dtype=int, shape=(3,), cols=1, labels=['nx', 'ny', 'nz'], desc='the array dimensions') # The volume of interest (VOI). volume = Array(dtype=float, value=(-5,5,-5,5,-5,5), shape=(6,), cols=2, labels=['xmin','xmax','ymin','ymax','zmin','zmax'], desc='the volume of interest') # Clicking this button resets the data with the new dimensions and # VOI. update_data = Button('Update data') ######################################## # Private traits. # Our data source. _x = Array _y = Array _z = Array data = Array source = Any _ipw1 = Any _ipw2 = Any _ipw3 = Any ######################################## # Our UI view. view = View(Item('equation', editor=TextEditor(auto_set=False, enter_set=True)), Item('dimensions'), Item('volume'), Item('update_data', show_label=False), resizable=True, scrollable=True, ) ###################################################################### # `object` interface. ###################################################################### def __init__(self, **traits): super(Explorer3D, self).__init__(**traits) # Make some default data. if len(self.data) == 0: self._make_data() # Note: to show the visualization by default we must wait till # the mayavi engine has started. To do this we hook into the # mayavi engine's started event and setup our visualization. # Now, when this object is constructed (i.e. when this method # is invoked), the services are not running yet and our own # application instance has not been set. So we can't even # get hold of the mayavi instance. So, we do the hooking up # when our application instance is set by listening for # changes to our application trait. def get_mayavi(self): from mayavi.plugins.script import Script return self.window.get_service(Script) ###################################################################### # Non-public methods. ###################################################################### def _make_data(self): dims = self.dimensions.tolist() np = dims[0]*dims[1]*dims[2] xmin, xmax, ymin, ymax, zmin, zmax = self.volume x, y, z = numpy.ogrid[xmin:xmax:dims[0]*1j, ymin:ymax:dims[1]*1j, zmin:zmax:dims[2]*1j] self._x = x.astype('f') self._y = y.astype('f') self._z = z.astype('f') self._equation_changed('', self.equation) def _show_data(self): if self.source is not None: return mayavi = self.get_mayavi() if mayavi.engine.current_scene is None: mayavi.new_scene() from mayavi.sources.array_source import ArraySource vol = self.volume origin = vol[::2] spacing = (vol[1::2] - origin)/(self.dimensions -1) src = ArraySource(transpose_input_array=False, scalar_data=self.data, origin=origin, spacing=spacing) self.source = src mayavi.add_source(src) from mayavi.modules.outline import Outline from mayavi.modules.image_plane_widget import ImagePlaneWidget from mayavi.modules.axes import Axes # Visualize the data. o = Outline() mayavi.add_module(o) a = Axes() mayavi.add_module(a) self._ipw1 = ipw = ImagePlaneWidget() mayavi.add_module(ipw) ipw.module_manager.scalar_lut_manager.show_scalar_bar = True self._ipw2 = ipw_y = ImagePlaneWidget() mayavi.add_module(ipw_y) ipw_y.ipw.plane_orientation = 'y_axes' self._ipw3 = ipw_z = ImagePlaneWidget() mayavi.add_module(ipw_z) ipw_z.ipw.plane_orientation = 'z_axes' ###################################################################### # Traits static event handlers. ###################################################################### def _equation_changed(self, old, new): try: g = numpy.__dict__ s = eval(new, g, {'x':self._x, 'y':self._y, 'z':self._z}) # The copy makes the data contiguous and the transpose # makes it suitable for display via tvtk. s = s.transpose().copy() # Reshaping the array is needed since the transpose # messes up the dimensions of the data. The scalars # themselves are ravel'd and used internally by VTK so the # dimension does not matter for the scalars. s.shape = s.shape[::-1] self.data = s except: pass def _dimensions_changed(self): """This does nothing and only changes to update_data do anything. """ return def _volume_changed(self): return def _update_data_fired(self): self._make_data() src = self.source if src is not None: vol = self.volume origin = vol[::2] spacing = (vol[1::2] - origin)/(self.dimensions -1) # Set the source spacing and origin. src.trait_set(spacing=spacing, origin=origin) # Update the sources data. src.update_image_data = True self._reset_ipw() def _reset_ipw(self): ipw1, ipw2, ipw3 = self._ipw1, self._ipw2, self._ipw3 if ipw1.running: ipw1.ipw.place_widget() if ipw2.running: ipw2.ipw.place_widget() ipw2.ipw.plane_orientation = 'y_axes' if ipw3.running: ipw3.ipw.place_widget() ipw3.ipw.plane_orientation = 'z_axes' self.source.render() def _data_changed(self, value): if self.source is None: return self.source.scalar_data = value def _window_changed(self): m = self.get_mayavi() if m.engine.running: if len(self.data) == 0: # Happens since the window may be set on __init__ at # which time the data is not created. self._make_data() self._show_data() else: # Show the data once the mayavi engine has started. m.engine.on_trait_change(self._show_data, 'started')
# Enthought library imports. from pyface.action.api import Action, ActionItem, Group, \ MenuManager, MenuBarManager, ToolBarManager from pyface.util.id_helper import get_unique_id from traits.api import Bool, Callable, Enum, HasTraits, Instance, \ List, Property, Str, Trait, Tuple, Unicode # Trait definitions. SubSchema = Trait(None, Action, ActionItem, Group, MenuManager, Instance('pyface.tasks.action.schema.GroupSchema'), Instance('pyface.tasks.action.schema.MenuSchema'), Instance('pyface.tasks.action.schema.Schema')) class Schema(HasTraits): """ The abstract base class for all Tasks action schemas. """ # The schema's identifier (unique within its parent schema). id = Str def _id_default(self): return get_unique_id(self) # The list of sub-items in the schema. These items can be other # (non-top-level) schema or concrete instances from the Pyface API. items = List(SubSchema) def __init__(self, *items, **traits): """ Creates a new schema. """
class VTKFileReader(VTKXMLFileReader): """A VTK file reader. This does not handle the new XML file format but only the older format. The reader supports all the different types of data sets. This reader also supports a time series. """ # The version of this class. Used for persistence. __version__ = 0 # The VTK data file reader. reader = Instance(tvtk.DataSetReader, args=(), kw={'read_all_scalars':True, 'read_all_vectors': True, 'read_all_tensors': True, 'read_all_fields': True} ) # Information about what this object can produce. output_info = PipelineInfo(datasets=['any'], attribute_types=['any'], attributes=['any']) ###################################################################### # `FileDataSource` interface ###################################################################### def has_output_port(self): """ Return True as the reader has output port.""" return True def get_output_object(self): """ Return the reader output port.""" return self.reader.output_port ###################################################################### # Non-public interface ###################################################################### def _file_path_changed(self, fpath): value = fpath.get() if len(value) == 0: self.name = 'No VTK file' return else: self.reader.file_name = value self.update() # Setup the outputs by resetting self.outputs. Changing # the outputs automatically fires a pipeline_changed # event. try: n = self.reader.number_of_outputs except AttributeError: # for VTK >= 4.5 n = self.reader.number_of_output_ports outputs = [] for i in range(n): outputs.append(self.reader.get_output(i)) self.outputs = outputs # FIXME: Only the first output goes through the assign # attribute filter. aa = self._assign_attribute self.configure_input_data(aa, outputs[0]) self.update_data() aa.update() outputs[0] = aa.output self.outputs = outputs # FIXME: The output info is only based on the first output. self.output_info.datasets = [get_tvtk_dataset_name(outputs[0])] # Change our name on the tree view self.name = self._get_name() def _get_name(self): """ Gets the name to display on the tree view. """ fname = basename(self.file_path.get()) ret = "VTK file (%s)"%fname if len(self.file_list) > 1: ret += " (timeseries)" if '[Hidden]' in self.name: ret += ' [Hidden]' return ret
class TreeItem(HasTraits): """ A generic base-class for items in a tree data structure. """ #### 'TreeItem' interface ################################################# # Does this item allow children? allows_children = Bool(True) # The item's children. children = List(Instance('TreeItem')) # Arbitrary data associated with the item. data = Any # Does the item have any children? has_children = Property(Bool) # The item's parent. parent = Instance('TreeItem') ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns the informal string representation of the object. """ if self.data is None: s = '' else: s = str(self.data) return s ########################################################################### # 'TreeItem' interface. ########################################################################### #### Properties ########################################################### # has_children def _get_has_children(self): """ True iff the item has children. """ return len(self.children) != 0 #### Methods ############################################################## def append(self, child): """ Appends a child to this item. This removes the child from its current parent (if it has one). """ return self.insert(len(self.children), child) def insert(self, index, child): """ Inserts a child into this item at the specified index. This removes the child from its current parent (if it has one). """ if child.parent is not None: child.parent.remove(child) child.parent = self self.children.insert(index, child) return child def remove(self, child): """ Removes a child from this item. """ child.parent = None self.children.remove(child) return child def insert_before(self, before, child): """ Inserts a child into this item before the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(before) self.insert(index, child) return (index, child) def insert_after(self, after, child): """ Inserts a child into this item after the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(after) self.insert(index + 1, child) return (index, child)
class _ThemedCheckboxEditor(Editor): """ Traits UI themed checkbox editor. """ # The ThemedControl used for the checkbox: checkbox = Instance(ThemedControl) #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ # Create the checkbox and its control: item = self.item factory = self.factory label = self.string_value(factory.label or item.label) min_size = (0, 0) if factory.theme is not None: min_size = (80, 0) self.checkbox = checkbox = ThemedControl( **factory.get('image', 'position', 'spacing', 'theme')).set( text=label, controller=self, min_size=min_size) self.control = checkbox.create_control(parent) # Set the tooltip: self.set_tooltip() #--------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: #--------------------------------------------------------------------------- def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ if self.checkbox.state == 'hover': self._set_hover_theme() else: self._set_theme() #-- ThemedControl Event Handlers ------------------------------------------- def normal_motion(self, x, y, event): self._set_hover_theme('hover') self.control.CaptureMouse() def hover_left_down(self, x, y, event): self.control.ReleaseMouse() self._set_hover_theme('down', not self.value) def hover_motion(self, x, y, event): if not self.checkbox.in_control(x, y): self.control.ReleaseMouse() self._set_theme('normal') def down_left_up(self, x, y, event): if self.checkbox.in_control(x, y): self.value = not self.value self.normal_motion(x, y, event) else: self._set_theme('normal') def down_motion(self, x, y, event): if not self.checkbox.in_control(x, y): self._set_theme() else: self._set_hover_theme(value=not self.value) #-- Private Methods -------------------------------------------------------- def _set_theme(self, state=None, value=None): """ Sets the theme, image, offset and optional checkbox state to use for a specified checkbox state value. """ if value is None: value = self.value factory = self.factory theme, image = factory.theme, factory.image if value: theme, image = factory.on_theme, factory.on_image n = (1 * value) * (theme is not None) self.checkbox.set(offset=(n, n), theme=theme or factory.theme, image=image or factory.image, state=state or self.checkbox.state) def _set_hover_theme(self, state=None, value=None): """ Sets the theme, image, offset and optional checkbox state to use for a specified checkbox state value while in hover mode. """ if value is None: value = self.value factory = self.factory theme, image = factory.hover_off_theme, factory.hover_off_image if value: theme = factory.hover_on_theme or factory.on_theme image = factory.hover_on_image or factory.on_image n = (1 * value) * (theme is not None) self.checkbox.set(offset=(n, n), theme=theme or factory.theme, image=image or factory.image, state=state or self.checkbox.state)
class CandlePlot(BaseCandlePlot): """ A plot consisting of a filled bar with an optional centerline and stems extending to extrema. Usually used to represent some statistics on bins of data, with the centerline representing the mean, the bar extents representing +/- 1 standard dev or 10th/90th percentiles, and the stems extents representing the minimum and maximum samples. The values in the **index** datasource indicate the centers of the bins; the widths of the bins are *not* specified in data space, and are determined by the minimum space between adjacent index values. """ #------------------------------------------------------------------------ # Data-related traits #------------------------------------------------------------------------ # The minimum values at each index point. If None, then no stem and no # endcap line will be drawn below each bar. min_values = Instance(AbstractDataSource) # The "lower" extent of the "bar", i.e. the value closest to the # corresponding value in min_values at each index. bar_min = Instance(AbstractDataSource) # Values that appear inside the bar, between bar_min and bar_max. These # Are usually mean or median values, and are rendered with a solid line # of a different color than the bar fill color. This can be None. center_values = Instance(AbstractDataSource) # The "upper" extent of the "bar", i.e. the value closest to the # corresponding value in max_values at each index. bar_max = Instance(AbstractDataSource) # The maximum value at each index point. If None, then no stem and no # endcap line will be drawn above each bar. max_values = Instance(AbstractDataSource) value = Property def map_data(self, screen_pt, all_values=True): """ Maps a screen space point into the "index" space of the plot. Overrides the BaseXYPlot implementation, and always returns an array of (index, value) tuples. """ x, y = screen_pt if self.orientation == 'v': x, y = y, x return array((self.index_mapper.map_data(x), self.value_mapper.map_data(y))) def map_index(self, screen_pt, threshold=0.0, outside_returns_none=True, index_only = True): if not index_only: raise NotImplementedError("Candle Plots only support index_only map_index()") if len(screen_pt) == 0: return None # Find the closest index point using numpy index_data = self.index.get_data() if len(index_data) == 0: return None target_data = self.index_mapper.map_data(screen_pt[0]) index = searchsorted(index_data, [target_data])[0] if index == len(index_data): index -= 1 # Bracket index and map those points to screen space, then # compute the distance if index > 0: lower = index_data[index-1] upper = index_data[index] screen_low, screen_high = self.index_mapper.map_screen(array([lower, upper])) # Find the closest index low_dist = abs(screen_pt[0] - screen_low) high_dist = abs(screen_pt[0] - screen_high) if low_dist < high_dist: index = index - 1 dist = low_dist else: dist = high_dist # Determine if we need to check the threshold if threshold > 0 and dist >= threshold: return None else: return index else: screen = self.index_mapper.map_screen(index_data[0]) if threshold > 0 and abs(screen - screen_pt[0]) >= threshold: return None else: return index def _gather_points(self): index = self.index.get_data() mask = broaden(self.index_range.mask_data(index)) if not mask.any(): self._cached_data_pts = [] self._cache_valid = True return data_pts = [compress(mask, index)] for v in (self.min_values, self.bar_min, self.center_values, self.bar_max, self.max_values): if v is None or len(v.get_data()) == 0: data_pts.append(None) else: data_pts.append(compress(mask, v.get_data())) self._cached_data_pts = data_pts self._cache_valid = True def _draw_plot(self, gc, view_bounds=None, mode="normal"): self._gather_points() if len(self._cached_data_pts) == 0: return index = self.index_mapper.map_screen(self._cached_data_pts[0]) if len(index) == 0: return vals = [] for v in self._cached_data_pts[1:]: if v is None: vals.append(None) else: vals.append(self.value_mapper.map_screen(v)) # Compute lefts and rights from self.index, which represents bin # centers. if len(index) == 1: width = 5.0 else: width = (index[1:] - index[:-1]).min() / 2.5 left = index - width right = index + width with gc: gc.clip_to_rect(self.x, self.y, self.width, self.height) self._render(gc, left, right, *vals) def _get_value(self): if self.center_values is not None: return self.center_values elif self.bar_min is not None: return self.bar_min elif self.bar_max is not None: return self.bar_max
class ExperimentDialogHandler(Controller): # bits for model initialization import_op = Instance( 'cytoflowgui.workflow.operations.import_op.ImportWorkflowOp') # events add_tubes = Event remove_tubes = Event add_variable = Event import_csv = Event # traits to communicate with the TabularEditor selected_tubes = List default_view = View(HGroup( VGroup( Label("Variables"), Item('tube_traits', editor=VerticalListEditor(editor=InstanceEditor(), style='custom', mutable=False, deletable=True), show_label=False), HGroup( Item('handler.add_variable', editor=ButtonEditor(label="Add a variable"), show_label=False))), VGroup( Label("Tubes"), Item(name='tubes', id='table', editor=TableEditor(editable=True, sortable=True, auto_size=True, configurable=False, selection_mode='rows', selected='handler.selected_tubes', columns=[ ObjectColumn( name='index', read_only_cell_color='light grey', editable=False) ]), enabled_when="object.tubes", show_label=False), HGroup( Item('handler.add_tubes', editor=ButtonEditor(label="Add tubes..."), show_label=False), Item('handler.remove_tubes', editor=ButtonEditor(label="Remove tubes"), show_label=False, enabled_when='object.tubes'), Item('handler.import_csv', editor=ButtonEditor(label="Import from CSV..."), show_label=False)))), title='Experiment Setup', buttons=OKCancelButtons, resizable=True, width=0.3, height=0.3) # keep a ref to the table editor so we can add columns dynamically table_editor = Instance(TableEditorQt) updating = Bool(False) def init(self, info): # save a reference to the table editor self.table_editor = info.ui.get_editors('tubes')[0] # init the model self.model.init(self.import_op) return True def close(self, info, is_ok): """ Handles the user attempting to close a dialog-based user interface. This method is called when the user attempts to close a window, by clicking an **OK** or **Cancel** button, or clicking a Close control on the window). It is called before the window is actually destroyed. Override this method to perform any checks before closing a window. While Traits UI handles "OK" and "Cancel" events automatically, you can use the value of the *is_ok* parameter to implement additional behavior. Parameters ---------- info : UIInfo object The UIInfo object associated with the view is_ok : Boolean Indicates whether the user confirmed the changes (such as by clicking **OK**.) Returns ------- allow_close : bool A Boolean, indicating whether the window should be allowed to close. """ if is_ok: if not self.model.valid: error( None, "Each tube must have a unique set of experimental conditions", "Invalid experiment!") return False if not is_ok: # we don't need to "undo" anything, we're throwing this model away info.ui.history = None return True def closed(self, info, is_ok): for trait in self.model.tube_traits: if trait.type != 'metadata': for tube in self.model.tubes: tube.on_trait_change(self._try_multiedit, trait.name, remove=True) if is_ok: self.model.update_import_op(self.import_op) @on_trait_change('add_variable') def _on_add_variable(self): self.model.tube_traits.append(TubeTrait(model=self.model)) @on_trait_change('import_csv') def _on_import(self): """ Import format: CSV, first column is filename, path relative to CSV. others are conditions, type is autodetected. first row is header with names. """ file_dialog = FileDialog() file_dialog.wildcard = "CSV files (*.csv)|*.csv|" file_dialog.action = 'open' file_dialog.open() if file_dialog.return_code != PyfaceOK: return csv = pandas.read_csv(file_dialog.path) csv_folder = Path(file_dialog.path).parent if self.model.tubes or self.model.tube_traits: if confirm( parent=None, message="This will clear the current conditions and tubes! " "Are you sure you want to continue?", title="Clear tubes and conditions?") != YES: return for col in csv.columns[1:]: self.model.tube_traits.append( TubeTrait(model=self.model, name=util.sanitize_identifier(col), type='category')) for _, row in csv.iterrows(): filename = csv_folder / row[0] try: metadata, _ = parse_tube(str(filename), metadata_only=True) except Exception as e: warning( None, "Had trouble loading file {}: {}".format(filename, str(e))) continue metadata['CF_File'] = Path(filename).stem new_tube = Tube(file=str(filename), parent=self.model, metadata=sanitize_metadata(metadata)) self.model.tubes.append(new_tube) for col in csv.columns[1:]: new_tube.trait_set(**{util.sanitize_identifier(col): row[col]}) @on_trait_change('add_tubes') def _on_add_tubes(self): """ Handle "Add tubes..." button. Add tubes to the experiment. """ file_dialog = FileDialog() file_dialog.wildcard = "Flow cytometry files (*.fcs *.lmd)|*.fcs *.lmd|" file_dialog.action = 'open files' file_dialog.open() if file_dialog.return_code != PyfaceOK: return for path in file_dialog.paths: try: metadata, _ = parse_tube(path, metadata_only=True) except Exception as e: raise RuntimeError("FCS reader threw an error on tube {0}: {1}"\ .format(path, e.value)) # if we're the first tube loaded, create a dummy experiment # and setup default metadata columns if not self.model.dummy_experiment: self.model.dummy_experiment = \ ImportOp(tubes = [CytoflowTube(file = path)]).apply(metadata_only = True) # check the next tube against the dummy experiment try: check_tube(path, self.model.dummy_experiment) except util.CytoflowError as e: error(None, e.__str__(), "Error importing tube") return metadata['CF_File'] = Path(path).stem tube = Tube(file=path, parent=self.model, metadata=sanitize_metadata(metadata)) self.model.tubes.append(tube) for trait in self.model.tube_traits: if trait.type != 'metadata' and trait.name: tube.on_trait_change(self._try_multiedit, trait.name) @on_trait_change('remove_tubes') def _on_remove_tubes(self): conf = confirm( None, "Are you sure you want to remove the selected tube(s)?", "Remove tubes?") if conf == YES: for tube in self.selected_tubes: self.model.tubes.remove(tube) if not self.model.tubes: self.model.dummy_experiment = None @on_trait_change('model:tube_traits_items', post_init=True) def _tube_traits_changed(self, event): for trait in event.added: if not trait.name: continue if self.table_editor: self.table_editor.columns.append( ExperimentColumn(name=trait.name, editable=(trait.type != 'metadata'))) for tube in self.model.tubes: if trait.type != 'metadata' and trait.name: tube.on_trait_change(self._try_multiedit, trait.name) for trait in event.removed: if not trait.name: continue table_column = next( (x for x in self.table_editor.columns if x.name == trait.name)) self.table_editor.columns.remove(table_column) for tube in self.model.tubes: if trait.type != 'metadata' and trait.name: tube.on_trait_change(self._try_multiedit, trait.name, remove=True) @on_trait_change('model:tube_traits:name') def _tube_trait_name_changed(self, trait, _, old_name, new_name): if old_name: old_table_column = next( (x for x in self.table_editor.columns if x.name == old_name)) column_idx = self.table_editor.columns.index(old_table_column) else: column_idx = len(self.table_editor.columns) if new_name: self.table_editor.columns.insert( column_idx, ExperimentColumn(name=new_name, editable=(trait.type != 'metadata'))) if old_name: self.table_editor.columns.remove(old_table_column) for tube in self.model.tubes: if trait.type != 'metadata': if old_name: tube.on_trait_change(self._try_multiedit, old_name, remove=True) if new_name: tube.on_trait_change(self._try_multiedit, new_name) if old_name: tube.remove_trait(old_name) self.model.counter.clear() for tube in self.model.tubes: tube_hash = tube.conditions_hash() if tube_hash in self.model.counter: self.model.counter[tube_hash] += 1 else: self.model.counter[tube_hash] = 1 @on_trait_change('model:tube_traits:type') def _tube_trait_type_changed(self, trait, _, old_type, new_type): if not trait.name: return table_column = next( (x for x in self.table_editor.columns if x.name == trait.name)) table_column.editable = (new_type != 'metadata') for tube in self.model.tubes: if trait.name: if old_type != 'metadata': tube.on_trait_change(self._try_multiedit, trait.name, remove=True) if new_type != 'metadata': tube.on_trait_change(self._try_multiedit, trait.name) def _try_multiedit(self, obj, name, old, new): """ See if there are multiple elements selected when a tube's trait changes and if so, edit the same trait for all the selected tubes. """ if self.updating: return self.updating = True for tube in self.selected_tubes: if tube != obj: old_hash = tube.conditions_hash() self.model.counter[old_hash] -= 1 if self.model.counter[old_hash] == 0: del self.model.counter[old_hash] # update the underlying traits without notifying the editor # we do this all here for performance reasons tube.trait_setq(**{name: new}) tube.conditions[name] = new new_hash = tube.conditions_hash() if new_hash not in self.model.counter: self.model.counter[new_hash] = 1 else: self.model.counter[new_hash] += 1 # now refresh the editor all at once self.table_editor.refresh_editor() self.updating = False
class Tube(HasStrictTraits): """ The model for a tube in an experiment. I originally wanted to make the Tube in the ImportDialog and the Tube in the ImportOp the same, but that fell apart when I tried to implement serialization (dynamic traits don't survive pickling when sending tubes to the remote process) (well, their values do, but neither the trait type nor the metadata do.) Oh well. This model depends on duck-typing ("if it walks like a duck, and quacks like a duck..."). Because we want to use all of the TableEditor's nice features, each row needs to be an instance, and each column a Trait. So, each Tube instance represents a single tube, and each experimental condition (as well as the tube name, its file, and optional plate row and col) are traits. These traits are dynamically added to Tube INSTANCES (NOT THE TUBE CLASS.) Then, we add appropriate columns to the table editor to access these traits. We also derive traits from tubes' FCS metadata. One can make a new column from metadata, then convert it into a condition to use in the analysis. We also use the "transient" flag to specify traits that shouldn't be displayed in the editor. This matches well with the traits that every HasTraits-derived class has by default (all of which are transient.) """ # these are the traits that every tube has. every other trait is # dynamically created. # the tube index index = Property(Int) # the file name. file = Str(transient=True) # need a link to the model; needed for row coloring parent = Instance("ExperimentDialogModel", transient=True) # needed for fast hashing conditions = Dict # metadata from the FCS file metadata = Dict all_conditions_set = Property(Bool, depends_on="conditions") def conditions_hash(self): ret = int(0) for key, value in self.conditions.items(): if not ret: ret = hash((key, value)) else: ret = ret ^ hash((key, value)) return ret def _anytrait_changed(self, name, old, new): if self.parent is not None and name in self.parent.tube_traits_dict and self.parent.tube_traits_dict[ name].type != 'metadata': old_hash = self.conditions_hash() self.conditions[name] = new new_hash = self.conditions_hash() if old_hash in self.parent.counter: self.parent.counter[old_hash] -= 1 if self.parent.counter[old_hash] == 0: del self.parent.counter[old_hash] if new_hash not in self.parent.counter: self.parent.counter[new_hash] = 1 else: self.parent.counter[new_hash] += 1 @cached_property def _get_all_conditions_set(self): return len([ x for x in list(self.conditions.values()) if x is None or x == "" ]) == 0 # magic - gets the value of the 'index' property def _get_index(self): return self.parent.tubes.index(self)
class ExperimentDialogModel(HasStrictTraits): """ The model for the Experiment setup dialog. """ # the list of Tubes (rows in the table) tubes = List(Tube) # a list of the traits that have been added to Tube instances # (columns in the table) tube_traits = List(TubeTrait) tube_traits_dict = Dict # keeps track of whether a tube is unique or not counter = Dict(Int, Int) # are all the tubes unique and filled? valid = Property(List) # a dummy Experiment, with the first Tube and no events, so we can check # subsequent tubes for voltage etc. and fail early. dummy_experiment = Instance(Experiment) # traits to communicate with the traits_view fcs_metadata = Property(List, depends_on='tubes') def init(self, import_op): if 'CF_File' not in import_op.conditions: self.tube_traits.append( TubeTrait(model=self, type='metadata', name='CF_File')) for name, condition in import_op.conditions.items(): if condition == "category" or condition == "object": self.tube_traits.append( TubeTrait(model=self, name=name, type='category')) elif condition == "int" or condition == "float": self.tube_traits.append( TubeTrait(model=self, name=name, type='float')) elif condition == "bool": self.tube_traits.append( TubeTrait(model=self, name=name, type='bool')) self.dummy_experiment = None if import_op.tubes: try: self.dummy_experiment = import_op.apply(metadata_only=True, force=True) except Exception as e: warning( None, "Had trouble loading some of the experiment's FCS " "files. You will need to re-add them.\n\n{}".format( str(e))) return for op_tube in import_op.tubes: metadata = self.dummy_experiment.metadata['fcs_metadata'][ op_tube.file] tube = Tube(file=op_tube.file, parent=self, metadata=sanitize_metadata(metadata)) self.tubes.append(tube) # adds the dynamic traits to the tube tube.trait_set(**op_tube.conditions) for trait in self.tube_traits: if trait.type == 'metadata': tube.trait_set( **{trait.name: tube.metadata[trait.name]}) else: tube.conditions[trait.name] = tube.trait_get()[ trait.name] @on_trait_change('tubes_items') def _tubes_items(self, event): for tube in event.added: for trait in self.tube_traits: if not trait.name: continue tube.add_trait(trait.name, trait.trait) if trait.type == 'metadata': tube.trait_set(**{trait.name: tube.metadata[trait.name]}) else: tube.trait_set(**{trait.name: trait.trait.default_value}) tube.conditions[trait.name] = tube.trait_get()[trait.name] self.counter.clear() for tube in self.tubes: tube_hash = tube.conditions_hash() if tube_hash in self.counter: self.counter[tube_hash] += 1 else: self.counter[tube_hash] = 1 @on_trait_change('tube_traits_items') def _tube_traits_changed(self, event): for trait in event.added: if not trait.name: continue for tube in self.tubes: tube.add_trait(trait.name, trait.trait) if trait.type == 'metadata': tube.trait_set(**{trait.name: tube.metadata[trait.name]}) else: tube.trait_set(**{trait.name: trait.trait.default_value}) tube.conditions[trait.name] = tube.trait_get()[trait.name] self.tube_traits_dict[trait.name] = trait for trait in event.removed: if not trait.name: continue for tube in self.tubes: tube.remove_trait(trait.name) if trait.type != 'metadata': del tube.conditions[trait.name] del self.tube_traits_dict[trait.name] self.counter.clear() for tube in self.tubes: tube_hash = tube.conditions_hash() if tube_hash in self.counter: self.counter[tube_hash] += 1 else: self.counter[tube_hash] = 1 @on_trait_change('tube_traits:name') def _on_trait_name_change(self, trait, _, old_name, new_name): for tube in self.tubes: trait_value = None if old_name: # store the value trait_value = tube.trait_get()[old_name] if trait.type != 'metadata': del tube.conditions[old_name] # defer removing the old trait until the handler # tube.remove_trait(old_name) if new_name: if new_name in tube.metadata: trait_value = tube.metadata[new_name] elif trait_value is None: trait_value = trait.trait.default_value tube.add_trait(new_name, trait.trait) tube.trait_set(**{new_name: trait_value}) if trait.type != 'metadata': tube.conditions[new_name] = trait_value if old_name: del self.tube_traits_dict[old_name] if new_name: self.tube_traits_dict[new_name] = trait self.counter.clear() for tube in self.tubes: tube_hash = tube.conditions_hash() if tube_hash in self.counter: self.counter[tube_hash] += 1 else: self.counter[tube_hash] = 1 @on_trait_change('tube_traits:type') def _on_type_change(self, trait, name, old_type, new_type): if not trait.name: return for tube in self.tubes: trait_value = tube.trait_get()[trait.name] tube.remove_trait(trait.name) if old_type != 'metadata': del tube.conditions[trait.name] tube.add_trait(trait.name, trait.trait) try: tube.trait_set(**{trait.name: trait_value}) except TraitError: tube.trait_set(**{trait.name: trait.trait.default_value}) if new_type != 'metadata': tube.conditions[trait.name] = tube.trait_get()[trait.name] self.counter.clear() for tube in self.tubes: tube_hash = tube.conditions_hash() if tube_hash in self.counter: self.counter[tube_hash] += 1 else: self.counter[tube_hash] = 1 def update_import_op(self, import_op): if not self.tubes: return assert self.dummy_experiment is not None conditions = { trait.name: trait.type for trait in self.tube_traits if trait.type != 'metadata' } tubes = [] events = 0 for tube in self.tubes: op_tube = CytoflowTube(file=tube.file, conditions=tube.trait_get( list(conditions.keys()))) tubes.append(op_tube) events += tube.metadata['TOT'] import_op.ret_events = events import_op.conditions = conditions import_op.tubes = tubes import_op.original_channels = channels = self.dummy_experiment.channels all_present = len(import_op.channels_list) > 0 if len(import_op.channels_list) > 0: for c in import_op.channels_list: if c.name not in channels: all_present = False if not all_present: warning( None, "Some of the operation's channels weren't found in " "these FCS files. Resetting all channel names.", "Resetting channel names") if not all_present: import_op.reset_channels() def is_tube_unique(self, tube): tube_hash = tube.conditions_hash() if tube_hash in self.counter: return self.counter[tube.conditions_hash()] == 1 else: return False def _get_valid(self): return len(self.tubes) > 0 and \ len(set(self.counter)) == len(self.tubes) and \ all([x.all_conditions_set for x in self.tubes]) # magic: gets the list of FCS metadata for the trait list editor @cached_property def _get_fcs_metadata(self): meta = {} for tube in self.tubes: for name, val in tube.metadata.items(): if name not in meta: meta[name] = set() meta[name].add(val) ret = [x for x in meta.keys() if len(meta[x]) > 1] return sorted(ret)
class BaseMagnet(HasTraits): dac = Property(Float, depends_on='_dac') mass = Float _dac = Float dacmin = Float(0.0) dacmax = Float(10.0) massmin = Property(Float, depends_on='_massmin') massmax = Property(Float, depends_on='_massmax') _massmin = Float(0.0) _massmax = Float(200.0) settling_time = 0.5 detector = Instance('pychron.spectrometer.base_detector.BaseDetector') dac_changed = Event mftable = Instance('pychron.spectrometer.mftable.MagnetFieldTable', ()) confirmation_threshold_mass = Float use_deflection_correction = True _suppress_mass_update = False def set_dac(self, *args, **kw): raise NotImplementedError def set_mftable(self, name): self.mftable.set_path_name(name) def update_field_table(self, *args): self.mftable.update_field_table(*args) # =============================================================================== # persistence # =============================================================================== def load(self): pass def finish_loading(self): """ initialize the mftable read DAC from device :return: """ if self.spectrometer: molweights = self.spectrometer.molecular_weights name = self.spectrometer.name else: from pychron.spectrometer.molecular_weights import MOLECULAR_WEIGHTS as molweights name = '' # self.mftable.molweights = molweights self.mftable.initialize(molweights) self.mftable.spectrometer_name = name.lower() d = self.read_dac() if d is not None: self._dac = d # =============================================================================== # mapping # =============================================================================== def map_dac_to_mass(self, dac, detname): """ convert a DAC value (voltage) to mass for a given detector use the mftable :param dac: float, voltage (0-10V) :param detname: str, name of a detector, e.g H1 :return: float, mass """ from pychron.spectrometer.mftable import get_detector_name, mass_cal_func detname = get_detector_name(detname) d = self.mftable.get_table() _, xs, ys, p = d[detname] def func(x, *args): c = list(p) c[-1] -= dac return mass_cal_func(c, x) try: mass = optimize.brentq(func, 0, 200) return mass except ValueError, e: import traceback traceback.print_exc() self.debug( 'DAC does not map to an isotope. DAC={}, Detector={}'.format( dac, detname))
class Product(ChromatographyData): """Represents a product, which is made up of components, impurities, and amino acids. The product stores the expressions needed to compute the concentration of each component, which are used by :class:`SolutionWithProduct` for the actual computation, since the solution's overall concentration is needed. """ # ------------------------------------------------------------------------- # Product traits # ------------------------------------------------------------------------- #: User notes about the product's origin, purpose, ... description = Str #: The type of product product_type = Key() #: List of components this product is composed of product_components = List(Instance(ProductComponent)) #: List of component names. Built from the product_components list product_component_names = Property(List(Str), depends_on='product_components') #: List of assay names to compute the concentrations of each component product_component_assays = List(Str) #: Expressions to compute the concentration of each product_component #: given the product's concentration and assays results. This should be a #: Pandas Series indexed on the component names, and containing string #: expressions. Each string should be an eval-able expression involving the #: 'product_component_assays' as well as the total `product_concentration`. #: (e.g. 'product_concentration * CEX_Acidic_2 / 100') product_component_concentration_exps = Instance(pd.Series) #: Expressions to compute the assay of each product_component #: given the product's component concentration. Each string #: should be a valid Python expression that can use the names #: in the 'product_component_names' attribute #: as well as the name `product_concentration` product_component_assay_exps = Property( Instance(List(Str)), depends_on='product_component_concentration_exps' ) #: List of impurities in the product impurity_names = List(Str) #: List of the assay names for each impurity impurity_assays = List(Str) #: Expressions to compute the concentration of each impurity #: given the product's concentration and impurity assays. This should be a #: Pandas Series indexed on the component names, and containing string #: expressions. Each string should be an eval-able expression that can use #: the names in the 'impurity_assays' attribute as well as the name #: `product_concentration`. impurity_concentration_exps = Instance(pd.Series) #: Expressions to compute the assay of each product_component given the #: product's component concentration. Each string should be a valid Python #: expression that can use the names in the 'product_component_names' #: attribute as well as the name `product_concentration` impurity_assay_exps = Property( Instance(List(Str)), depends_on='impurity_concentration_exps' ) #: Iso-electric point of the product (in pH unit) pI = Instance(UnitScalar) # FIXME: Later we may want to consider making a AminoAcid class (issue #33) #: List of titrating amino acids in the product amino_acids = List(Str) # FIXME: Later we may want to consider making a AminoAcid class (issue #33) #: Number of each amino acid in the product amino_acid_numbers = List(Int) #: Number of components the product contains num_components = Property(Int, depends_on='product_components') # ------------------------------------------------------------------------- # ChromatographyData traits # ------------------------------------------------------------------------- #: The type of data being represented by this class. type_id = Constant(PRODUCT_TYPE) #: How to identify a particular product _unique_keys = Tuple(('name',)) # ------------------------------------------------------------------------- # Product Methods # ------------------------------------------------------------------------- def __init__(self, **traits): # Convert lists of str concentration expression to series if needed if "product_component_concentration_exps" in traits: values = traits["product_component_concentration_exps"] if isinstance(values, list): index = [comp.name for comp in traits["product_components"]] traits["product_component_concentration_exps"] = \ pd.Series(values, index=index) if "impurity_concentration_exps" in traits: values = traits["impurity_concentration_exps"] if isinstance(values, list): traits["impurity_concentration_exps"] = \ pd.Series(values, index=traits["impurity_names"]) super(Product, self).__init__(**traits) # If the component's target product wasn't set, it is set here. # Otherwise, its target product is checked: for comp in self.product_components: if comp.target_product == "": comp.target_product = self.name elif comp.target_product != self.name: msg = "Trying to build product {} with component {} " \ "targeting product {}." msg = msg.format(self.name, comp.name, comp.target_product) logger.exception(msg) raise ValueError(msg) if traits["name"] == BLANK_PRODUCT_NAME: return #: check for each set of attributes, the same number was given component_attrs = [self.product_components, self.product_component_concentration_exps] impurity_attrs = [self.impurity_names, self.impurity_assays, self.impurity_concentration_exps] amino_acid_attrs = [self.amino_acids, self.amino_acid_numbers] for group in [component_attrs, impurity_attrs, amino_acid_attrs]: self._check_lists_lengths(group) # Check each expression in product_component_concentration_exps and # impurity_concentration_exps self._check_expressions() self._sanitize_component_names() def get_component_with_name(self, name): """ Returns component with specified name. Raises ------ KeyError: If the name requested isn't a valid component name. """ all_names = [] for comp in self.product_components: all_names.append(comp.name) if comp.name == name: return comp msg = "No product component with name {} in product {}. Available " \ "components names are: {}".format(name, self.name, all_names) logger.exception(msg) raise KeyError(msg) # Private interface ------------------------------------------------------- def _sanitize_component_names(self): """ Change component/impurity names that would break the sympy solver. """ for comp in self.product_components: if comp.name in BAD_COMPONENT_NAMES: comp.name = BAD_COMPONENT_NAMES[comp.name] for i, impurity_name in enumerate(self.impurity_names): if impurity_name in BAD_COMPONENT_NAMES: self.impurity_names[i] = BAD_COMPONENT_NAMES[comp.name] def _check_lists_lengths(self, lists): """ Raises ValueError if lists not all same length """ if len(lists) == 0: return first_length = len(lists[0]) if any(len(l) != first_length for l in lists): msg = ("Attempting to create product {} with lists of different " "lengths ({}).".format(self.name, lists)) logger.exception(msg) raise ValueError(msg) def _check_expressions(self): """ Check that component concentration expressions to be eval-able. Raises ------ ValueError: If expressions inside the object's '*_exps' attrs are not valid python expressions involving the 'product_concentration' and the product's assays. """ for comp_group in COMPONENT_GROUPS: exps = getattr(self, comp_group + '_concentration_exps') if len(exps) == 0: continue valid_names = [PROD_CONCENTRATION_VARNAME] valid_names += getattr(self, comp_group + '_assays') namespace = {name: 1 for name in valid_names} for exp in exps: try: eval(exp, namespace) except NameError as e: msg = "Concentration expression {} is invalid. Does it " \ "only involve operations, and the known product " \ "concentration and assays ({})? (Original error " \ "was {}.)".format(exp, valid_names, e) logger.exception(msg) raise ValueError(msg) # Traits property getters/setters ----------------------------------------- def _get_assay_exps(self, component_group): """ Returns a list of expressions representing the inverse 'component_group'_concentration_exps (i.e. solved for 'component_group'_assays) """ expressions = getattr(self, component_group + "_concentration_exps") if expressions is None: return names = getattr(self, component_group + "_names") concentration_exps_lhs = [exp + ' - ' + comp for (exp, comp) in zip(list(expressions), names)] unknown_variables = [var(variable) for variable in getattr(self, component_group + "_assays")] try: temp_assay_exps = solve(concentration_exps_lhs, unknown_variables, warn=True) except TypeError as e: msg = ("Sympy's solve function failed with error {}. It could be " "due to a badly chosen component/impurity name that " "collides during solver execution. Expressions are {}.") msg = msg.format(e, concentration_exps_lhs) logger.exception(msg) raise ExpressionComputationError(msg) if isinstance(temp_assay_exps, dict): assay_exps = \ [str(temp_assay_exps[var(assay)]) for assay in getattr(self, component_group + "_assays")] else: assay_exps = [str(exp) for exp in temp_assay_exps] return assay_exps @cached_property def _get_product_component_assay_exps(self): return self._get_assay_exps(component_group="product_component") @cached_property def _get_impurity_assay_exps(self): return self._get_assay_exps(component_group="impurity") @cached_property def _get_num_components(self): return len(self.product_components) @cached_property def _get_product_component_names(self): return [comp.name for comp in self.product_components] # Traits initialization method -------------------------------------------- def _impurity_concentration_exps_default(self): return pd.Series([]) def _product_component_concentration_exps_default(self): return pd.Series([])
class PipelineEngine(Loggable): dvc = Instance('pychron.dvc.dvc.DVC') browser_model = Instance( 'pychron.envisage.browser.base_browser_model.BaseBrowserModel') interpreted_age_browser_model = Instance( 'pychron.envisage.browser.base_browser_model.BaseBrowserModel') pipeline = Instance(Pipeline, ()) selected = Instance(BaseNode, ()) dclicked = Event active_editor = Event active_inspector_item = Instance(BaseInspectorItem, ()) selected_editor = Any # unknowns = List # references = List run_needed = Event refresh_all_needed = Event update_needed = Event refresh_table_needed = Event # show_group_colors = Bool selected_pipeline_template = Str available_pipeline_templates = List selected_unknowns = List selected_references = List dclicked_unknowns = Event dclicked_references = Event recall_analyses_needed = Event reset_event = Event tag_event = Event invalid_event = Event recall_event = Event state = Instance(EngineState) def __init__(self, *args, **kw): super(PipelineEngine, self).__init__(*args, **kw) self._load_predefined_templates() def drop_factory(self, items): return self.dvc.make_analyses(items) def reset(self): # for ni in self.pipeline.nodes: # ni.visited = False self.pipeline.reset() self.update_needed = True def update_detectors(self): """ set valid detectors for FitICFactorNodes """ for p in self.pipeline.nodes: if isinstance(p, FitICFactorNode): udets = { iso.detector for ai in p.unknowns for iso in ai.isotopes.itervalues() } rdets = { iso.detector for ai in p.references for iso in ai.isotopes.itervalues() } p.set_detectors(list(udets.union(rdets))) def get_unknowns_node(self): nodes = self.get_nodes(UnknownNode) if nodes: return nodes[0] def get_nodes(self, klass): return [n for n in self.pipeline.nodes if n.__class__ == klass] def unknowns_clear_all_grouping(self): self._set_grouping(self.selected.unknowns, 0) def unknowns_clear_grouping(self): items = self.selected_unknowns if not items: items = self.selected.unknowns self._set_grouping(items, 0) def unknowns_group_by_selected(self): items = self.selected.unknowns max_gid = max([si.group_id for si in items]) + 1 self._set_grouping(self.selected_unknowns, max_gid) def _set_grouping(self, items, gid): for si in items: si.group_id = gid if hasattr(self.selected, 'editor') and self.selected.editor: self.selected.editor.refresh_needed = True self.refresh_table_needed = True def recall_unknowns(self): self.debug('recall unks') self.recall_analyses_needed = self.selected_unknowns def recall_references(self): self.debug('recall refs') self.recall_analyses_needed = self.selected_references def review_node(self, node): node.reset() # self.run_needed = True # if node.review(): # self.run_needed = True def configure(self, node): node.configure() osel = self.selected self.update_needed = True self.selected = osel def remove_node(self, node): self.pipeline.nodes.remove(node) # self.run_needed = True def set_template(self, name): self.debug('Set template "{}"'.format(name)) self._set_template(name) def get_experiment_ids(self): return self.pipeline.get_experiment_ids() def set_review_permanent(self, state): name = self.selected_pipeline_template path, is_user_path = self._get_template_path(name) if path: with open(path, 'r') as rfile: nodes = yaml.load(rfile) for i, ni in enumerate(nodes): klass = ni['klass'] if klass == 'ReviewNode': ni['enabled'] = state with open(path, 'w') as wfile: yaml.dump(nodes, wfile) if is_user_path: with open(path, 'r') as rfile: paths.update_manifest(name, rfile.read()) # debugging def select_default(self): node = self.pipeline.nodes[0] self.browser_model.select_project('J-Curve') self.browser_model.select_repository('Irradiation-NM-272') self.browser_model.select_sample(idx=0) records = self.browser_model.get_analysis_records() if records: analyses = self.dvc.make_analyses(records) # print len(records),len(analyses) node.unknowns.extend(analyses) node._manual_configured = True # self.refresh_analyses() def add_test_filter(self): node = self.pipeline.nodes[-1] newnode = FilterNode() filt = newnode.filters[0] filt.attribute = 'uage' filt.comparator = '<' filt.criterion = '10' self.pipeline.add_after(node, newnode) def clear(self): self.unknowns = [] self.references = [] for ni in self.pipeline.nodes: ni.clear_data() # self.pipeline.nodes = [] # self.selected_pipeline_template = '' # self._set_template(self.selected_pipeline_template) # ============================================================================================================ # nodes # ============================================================================================================ # data def add_data(self, node=None, run=False): """ add a default data node :return: """ self.debug('add data node') newnode = UnknownNode(dvc=self.dvc, browser_model=self.browser_model) node = self._get_last_node(node) self.pipeline.add_after(node, newnode) def add_references(self, node=None, run=False): newnode = ReferenceNode(name='references', dvc=self.dvc, browser_model=self.browser_model) node = self._get_last_node(node) self.pipeline.add_after(node, newnode) # preprocess def add_filter(self, node=None, run=True): newnode = FilterNode() self._add_node(node, newnode, run) def add_grouping(self, node=None, run=True): newnode = GroupingNode() self._add_node(node, newnode, run) # find def add_find_airs(self, node=None, run=True): self._add_find_node(node, run, 'air') def add_find_blanks(self, node=None, run=True): self._add_find_node(node, run, 'blank_unknown') # figures def add_spectrum(self, node=None, run=True): newnode = SpectrumNode() self._add_node(node, newnode, run) def add_ideogram(self, node=None, run=True): ideo_node = IdeogramNode() self._add_node(node, ideo_node, run) def add_series(self, node=None, run=True): series_node = SeriesNode() self._add_node(node, series_node, run) # fits def add_icfactor(self, node=None, run=True): new = FitICFactorNode() if new.configure(): node = self._get_last_node(node) self.pipeline.add_after(node, new) if new.use_save_node: self.add_icfactor_persist(new, run=False) # if run: # self.run_needed = True def add_blanks(self, node=None, run=True): new = FitBlanksNode() if new.configure(): node = self._get_last_node(node) self.pipeline.add_after(node, new) if new.use_save_node: self.add_blanks_persist(new, run=False) # if run: # self.run_needed = True def add_isotope_evolution(self, node=None, run=True): new = FitIsotopeEvolutionNode() if new.configure(): node = self._get_last_node(node) self.pipeline.add_after(node, new) if new.use_save_node: self.add_iso_evo_persist(new, run=False) # if run: # self.run_needed = new # save def add_icfactor_persist(self, node=None, run=True): new = ICFactorPersistNode(dvc=self.dvc) self._add_node(node, new, run) def add_blanks_persist(self, node=None, run=True): new = BlanksPersistNode(dvc=self.dvc) self._add_node(node, new, run) def add_iso_evo_persist(self, node=None, run=True): new = IsotopeEvolutionPersistNode(dvc=self.dvc) self._add_node(node, new, run) def add_pdf_figure(self, node=None, run=True): newnode = PDFFigureNode(root='/Users/ross/Sandbox') self._add_node(node, newnode, run=run) # ============================================================================================================ def save_pipeline_template(self, path): self.info('Saving pipeline to {}'.format(path)) with open(path, 'w') as wfile: obj = self.pipeline.to_template() yaml.dump(obj, wfile, default_flow_style=False) # def run_persist(self, state): # for node in self.pipeline.iternodes(): # if not isinstance(node, (FitNode, PersistNode)): # continue # # node.run(state) # if state.canceled: # self.debug('pipeline canceled by {}'.format(node)) # return True def run_from_pipeline(self): if not self.state: ret = self.run_pipeline() else: node = self.selected idx = self.pipeline.nodes.index(node) idx = max(0, idx - 1) node = self.pipeline.nodes[idx] ret = self.run_pipeline(run_from=node, state=self.state) return ret def resume_pipeline(self): return self.run_pipeline(state=self.state) def refresh_figure_editors(self): for ed in self.state.editors: if isinstance(ed, FigureEditor): ed.refresh_needed = True def rerun_with(self, unks, post_run=True): if not self.state: return state = self.state state.unknowns = unks ost = time.time() for idx, node in enumerate(self.pipeline.iternodes(None)): if node.enabled: with ActiveCTX(node): if not node.pre_run(state, configure=False): self.debug('Pre run failed {}'.format(node)) return True node.unknowns = [] st = time.time() try: node.run(state) node.visited = True self.selected = node except NoAnalysesError: self.information_dialog('No Analyses in Pipeline!') self.pipeline.reset() return True self.debug('{:02n}: {} Runtime: {:0.4f}'.format( idx, node, time.time() - st)) if state.veto: self.debug('pipeline vetoed by {}'.format(node)) return if state.canceled: self.debug('pipeline canceled by {}'.format(node)) return True else: self.debug('Skip node {:02n}: {}'.format(idx, node)) else: self.debug('pipeline run finished') self.debug('pipeline runtime {}'.format(time.time() - ost)) if post_run: self.post_run(state) return True def run_pipeline(self, run_from=None, state=None): if state is None: state = EngineState() self.state = state ost = time.time() start_node = run_from or state.veto self.debug('pipeline run started') if start_node: self.debug('starting at node {} {}'.format(start_node, run_from)) state.veto = None for node in self.pipeline.iternodes(start_node): node.visited = False for idx, node in enumerate(self.pipeline.iternodes(start_node)): if node.enabled: node.editor = None with ActiveCTX(node): if not node.pre_run(state): self.debug('Pre run failed {}'.format(node)) return True st = time.time() try: node.run(state) node.visited = True self.selected = node except NoAnalysesError: self.information_dialog('No Analyses in Pipeline!') self.pipeline.reset() return True self.debug('{:02n}: {} Runtime: {:0.4f}'.format( idx, node, time.time() - st)) if state.veto: self.debug('pipeline vetoed by {}'.format(node)) return if state.canceled: self.debug('pipeline canceled by {}'.format(node)) return True else: self.debug('Skip node {:02n}: {}'.format(idx, node)) else: self.debug('pipeline run finished') self.debug('pipeline runtime {}'.format(time.time() - ost)) self.post_run(state) # self.state = None return True run = run_pipeline def post_run(self, state): self.debug('pipeline post run started') for idx, node in enumerate(self.pipeline.nodes): action = 'skip' if node.enabled: action = 'post run' node.post_run(self, state) self.debug('{} node {:02n}: {}'.format(action, idx, node.name)) self.debug('pipeline post run finished') self.update_needed = True self.refresh_table_needed = True def select_node_by_editor(self, editor): for node in self.pipeline.nodes: if hasattr(node, 'editor'): if node.editor == editor: # print 'selecting',node self.selected = node # self.unknowns = editor.analyses # self.refresh_table_needed = True break # private def _set_template(self, name): self.reset_event = True path, _ = self._get_template_path(name) pt = PipelineTemplate(name, path) pt.render(self.application, self.pipeline, self.browser_model, self.interpreted_age_browser_model, self.dvc) self.update_detectors() if self.pipeline.nodes: self.selected = self.pipeline.nodes[0] def _get_template_path(self, name): pname = name.replace(' ', '_').lower() pname = add_extension(pname, '.yaml') path = os.path.join(paths.pipeline_template_dir, pname) user_path = False if not os.path.isfile(path): path = os.path.join(paths.user_pipeline_template_dir, pname) user_path = True if not os.path.isfile(path): self.warning( 'Invalid template name "{}". {} does not exist'.format( name, path)) return return path, user_path def _load_predefined_templates(self): self.debug('load predefined templates') templates = [] user_templates = [] for temp in list_directory2(paths.pipeline_template_dir, extension='.yaml', remove_extension=True): templates.append(temp) self.debug('loaded {} pychron templates'.format(len(templates))) for temp in list_directory2(paths.user_pipeline_template_dir, extension='.yaml', remove_extension=True): user_templates.append(temp) self.debug('loaded {} user templates'.format(len(user_templates))) def formatter(t): return ' '.join(map(str.capitalize, t.split('_'))) templates = map(formatter, templates) user_templates = map(formatter, user_templates) with open(paths.pipeline_template_file, 'r') as rfile: tnames = yaml.load(rfile) ns = [pt for pt in tnames if pt in templates] ns.extend(user_templates) self.available_pipeline_templates = ns def _add_find_node(self, node, run, analysis_type): newnode = FindReferencesNode(dvc=self.dvc, analysis_type=analysis_type) if newnode.configure(): node = self._get_last_node(node) self.pipeline.add_after(node, newnode) self.add_references(newnode, run=False) if run: self.run_needed = newnode def _add_node(self, node, new, run=True): if new.configure(): node = self._get_last_node(node) self.pipeline.add_after(node, new) # if run: # self.run_needed = new def _get_last_node(self, node=None): if node is None: if self.pipeline.nodes: idx = len(self.pipeline.nodes) - 1 node = self.pipeline.nodes[idx] return node # handlers def _dclicked_unknowns_changed(self): if self.selected_unknowns: self.recall_unknowns() def _dclicked_references_fired(self): if self.selected_references: self.recall_references() def _selected_pipeline_template_changed(self, new): if new: self.debug('Pipeline template {} selected'.format(new)) self._set_template(new) def _selected_changed(self, old, new): if old: old.on_trait_change(self._handle_tag, 'unknowns:tag_event,references:tag_event', remove=True) old.on_trait_change( self._handle_invalid, 'unknowns:invalid_event,references:invalid_event', remove=True) old.on_trait_change( self._handle_recall, 'unknowns:recall_event,references:recall_event', remove=True) old.on_trait_change(self._handle_len_unknowns, 'unknowns_items', remove=True) old.on_trait_change(self._handle_len_references, 'references_items', remove=True) old.on_trait_change(self._handle_status, 'unknowns:temp_status,references:temp_status', remove=True) if new: new.on_trait_change(self._handle_tag, 'unknowns:tag_event,references:tag_event') new.on_trait_change( self._handle_invalid, 'unknowns:invalid_event,references:invalid_event') new.on_trait_change( self._handle_recall, 'unknowns:recall_event,references:recall_event') new.on_trait_change(self._handle_status, 'unknowns:temp_status,references:temp_status') new.on_trait_change(self._handle_len_unknowns, 'unknowns_items') new.on_trait_change(self._handle_len_references, 'references_items') # new.on_trait_change(self._handle_unknowns, 'unknowns[]') # self.show_group_colors = False if isinstance(new, FigureNode): # self.show_group_colors = True if new.editor: editor = new.editor self.selected_editor = editor self.active_editor = editor # _suppress_handle_unknowns = False # def _handle_unknowns(self, obj, name, new): # if self._suppress_handle_unknowns: # return # # items = obj.unknowns # # for i, item in enumerate(items): # if not isinstance(item, DVCAnalysis): # self._suppress_handle_unknowns = True # nitem = self.dvc.make_analyses((item,))[0] # obj.unknowns.pop(i) # obj.unknowns.insert(i, nitem) # # self._suppress_handle_unknowns = False # print 'asdfasdfasfsafs', obj, new def refresh_unknowns(self, unks, refresh_editor=False): self.selected.unknowns = unks self.selected.editor.set_items(unks, refresh=refresh_editor) _len_unknowns_cnt = 0 _len_unknowns_removed = 0 _len_references_cnt = 0 _len_references_removed = 0 def _handle_len_unknowns(self, new): # self._handle_len('unknowns', lambda e: e.set_items(self.selected.unknowns)) def func(editor): vs = self.selected.unknowns editor.set_items(vs) self.state.unknowns = vs for node in self.pipeline.nodes: if isinstance(node, UnknownNode) and node is not self.selected: node.unknowns = vs self._handle_len('unknowns', func) def _handle_len_references(self, new): def func(editor): vs = self.selected.references editor.set_references(vs) self.state.references = vs for node in self.pipeline.nodes: if isinstance(node, ReferenceNode) and node is not self.selected: node.references = vs self._handle_len('references', func) def _handle_len(self, k, func): lr = '_len_{}_removed'.format(k) lc = '_len_{}_cnt'.format(k) editor = None if hasattr(self.selected, 'editor'): editor = self.selected.editor if editor: n = len(getattr(self, 'selected_{}'.format(k))) if not n: setattr(self, lc, getattr(self, lc) + 1) else: setattr(self, lc, 0) setattr(self, lr, n) if getattr(self, lc) >= getattr(self, lr) or n == 1: setattr(self, lc, 0) # self._len_references_cnt = 0 func(editor) # editor.set_references(self.selected.references) editor.refresh_needed = True def _handle_status(self, new): self.refresh_table_needed = True def _handle_recall(self, new): self.recall_event = new def _handle_tag(self, new): self.tag_event = new def _handle_invalid(self, new): self.invalid_event = new def _dclicked_changed(self, new): self.configure(new) # self.update_needed = True @on_trait_change( 'selected_editor:figure_model:panels:[figures:[inspector_event]]') def _handle_inspector_event(self, obj, name, old, new): self.active_inspector_item = new
class BaseXYPlot(AbstractPlotRenderer): """ Base class for simple X-vs-Y plots that consist of a single index data array and a single value data array. Subclasses handle the actual rendering, but this base class takes care of most of making sure events are wired up between mappers and data or screen space changes, etc. """ #------------------------------------------------------------------------ # Data-related traits #------------------------------------------------------------------------ #: The data source to use for the index coordinate. index = Instance(ArrayDataSource) #: The data source to use as value points. value = Instance(AbstractDataSource) #: Screen mapper for index data. index_mapper = Instance(AbstractMapper) #: Screen mapper for value data value_mapper = Instance(AbstractMapper) # Convenience properties that correspond to either index_mapper or # value_mapper, depending on the orientation of the plot. #: Corresponds to either **index_mapper** or **value_mapper**, depending on #: the orientation of the plot. x_mapper = Property #: Corresponds to either **value_mapper** or **index_mapper**, depending on #: the orientation of the plot. y_mapper = Property #: Convenience property for accessing the index data range. index_range = Property #: Convenience property for accessing the value data range. value_range = Property #: The type of hit-testing that is appropriate for this renderer. #: #: * 'line': Computes Euclidean distance to the line between the #: nearest adjacent points. #: * 'point': Checks for adjacency to a marker or point. hittest_type = Enum("point", "line") #------------------------------------------------------------------------ # Appearance-related traits #------------------------------------------------------------------------ #: The orientation of the index axis. orientation = Enum("h", "v") #: Overall alpha value of the image. Ranges from 0.0 for transparent to 1.0 alpha = Range(0.0, 1.0, 1.0) #------------------------------------------------------------------------ # Convenience readonly properties for common annotations #------------------------------------------------------------------------ #: Read-only property for horizontal grid. hgrid = Property #: Read-only property for vertical grid. vgrid = Property #: Read-only property for x-axis. x_axis = Property #: Read-only property for y-axis. y_axis = Property #: Read-only property for labels. labels = Property #------------------------------------------------------------------------ # Other public traits #------------------------------------------------------------------------ #: Does the plot use downsampling? #: This is not used right now. It needs an implementation of robust, fast #: downsampling, which does not exist yet. use_downsampling = Bool(False) #: Does the plot use a spatial subdivision structure for fast hit-testing? #: This makes data updates slower, but makes hit-tests extremely fast. use_subdivision = Bool(False) #: Overrides the default background color trait in PlotComponent. bgcolor = "transparent" # This just turns on a simple drawing of the X and Y axes... not a long # term solution, but good for testing. #: Defines the origin axis color, for testing. origin_axis_color = black_color_trait #: Defines a the origin axis width, for testing. origin_axis_width = Float(1.0) #: Defines the origin axis visibility, for testing. origin_axis_visible = Bool(False) #------------------------------------------------------------------------ # Private traits #------------------------------------------------------------------------ # Are the cache traits valid? If False, new ones need to be compute. _cache_valid = Bool(False) # Cached array of (x,y) data-space points; regardless of self.orientation, # these points are always stored as (index_pt, value_pt). _cached_data_pts = Array # Cached array of (x,y) screen-space points. _cached_screen_pts = Array # Does **_cached_screen_pts** contain the screen-space coordinates # of the points currently in **_cached_data_pts**? _screen_cache_valid = Bool(False) # Reference to a spatial subdivision acceleration structure. _subdivision = Any #------------------------------------------------------------------------ # Abstract methods that subclasses must implement #------------------------------------------------------------------------ def _render(self, gc, points): """ Abstract method for rendering points. Parameters ---------- gc : graphics context Target for drawing the points points : List of Nx2 arrays Screen-space points to render """ raise NotImplementedError def _gather_points(self): """ Abstract method to collect data points that are within the range of the plot, and cache them. """ raise NotImplementedError def _downsample(self): """ Abstract method that gives the renderer a chance to downsample in screen space. """ # By default, this just does a mapscreen and returns the result raise NotImplementedError #------------------------------------------------------------------------ # Concrete methods below #------------------------------------------------------------------------ def __init__(self, **kwtraits): # Handling the setting/initialization of these traits manually because # they should be initialized in a certain order. kwargs_tmp = {"trait_change_notify": False} for trait_name in ("index", "value", "index_mapper", "value_mapper"): if trait_name in kwtraits: kwargs_tmp[trait_name] = kwtraits.pop(trait_name) self.trait_set(**kwargs_tmp) AbstractPlotRenderer.__init__(self, **kwtraits) if self.index is not None: self.index.on_trait_change(self._either_data_changed, "data_changed") self.index.on_trait_change(self._either_metadata_changed, "metadata_changed") if self.index_mapper: self.index_mapper.on_trait_change(self._mapper_updated_handler, "updated") if self.value is not None: self.value.on_trait_change(self._either_data_changed, "data_changed") self.value.on_trait_change(self._either_metadata_changed, "metadata_changed") if self.value_mapper: self.value_mapper.on_trait_change(self._mapper_updated_handler, "updated") # If we are not resizable, we will not get a bounds update upon layout, # so we have to manually update our mappers if self.resizable == "": self._update_mappers() return def hittest(self, screen_pt, threshold=7.0, return_distance=False): """ Performs proximity testing between a given screen point and the plot. Parameters ---------- screen_pt : (x,y) A point to test. threshold : integer Optional maximum screen space distance (pixels) between *screen_pt* and the plot. return_distance : Boolean If True, also return the distance. Returns ------- If self.hittest_type is 'point', then this method returns the screen coordinates of the closest point on the plot as a tuple (x,y) If self.hittest_type is 'line', then this method returns the screen endpoints of the line segment closest to *screen_pt*, as ((x1,y1), (x2,y2)) If *screen_pt* does not fall within *threshold* of the plot, then this method returns None. If return_distance is True, return the (x, y, d), where d is the distance between the distance between the input point and the closest point (x, y), in screen coordinates. """ if self.hittest_type == "point": tmp = self.get_closest_point(screen_pt, threshold) elif self.hittest_type == "line": tmp = self.get_closest_line(screen_pt, threshold) else: raise ValueError("Unknown hittest type '%s'" % self.hittest_type) if tmp is not None: if return_distance: return tmp else: return tmp[:-1] else: return None def get_closest_point(self, screen_pt, threshold=7.0): """ Tests for proximity in screen-space. This method checks only data points, not the line segments connecting them; to do the latter use get_closest_line() instead. Parameters ---------- screen_pt : (x,y) A point to test. threshold : integer Optional maximum screen space distance (pixels) between *screen_pt* and the plot. If 0.0, then no threshold tests are performed, and the nearest point is returned. Returns ------- (x, y, distance) of a datapoint nearest to *screen_pt*. If no data points are within *threshold* of *screen_pt*, returns None. """ ndx = self.map_index(screen_pt, threshold) if ndx is not None: x = self.x_mapper.map_screen(self.index.get_data()[ndx]) y = self.y_mapper.map_screen(self.value.get_data()[ndx]) return (x, y, sqrt((x-screen_pt[0])**2 + (y-screen_pt[1])**2)) else: return None def get_closest_line(self, screen_pt, threshold=7.0): """ Tests for proximity in screen-space against lines connecting the points in this plot's dataset. Parameters ---------- screen_pt : (x,y) A point to test. threshold : integer Optional maximum screen space distance (pixels) between the line and the plot. If 0.0, then the method returns the closest line regardless of distance from the plot. Returns ------- (x1, y1, x2, y2, dist) of the endpoints of the line segment closest to *screen_pt*. The *dist* element is the perpendicular distance from *screen_pt* to the line. If there is only a single point in the renderer's data, then the method returns the same point twice. If no data points are within *threshold* of *screen_pt*, returns None. """ ndx = self.map_index(screen_pt, threshold=0.0) if ndx is None: return None index_data = self.index.get_data() value_data = self.value.get_data() x = self.x_mapper.map_screen(index_data[ndx]) y = self.y_mapper.map_screen(value_data[ndx]) # We need to find another index so we have two points; in the # even that we only have 1 point, just return that point. datalen = len(index_data) if datalen == 1: dist = (x, y, sqrt((x-screen_pt[0])**2 + (y-screen_pt[1])**2)) if (threshold == 0.0) or (dist <= threshold): return (x, y, x, y, dist) else: return None else: if (ndx == 0) or (screen_pt[0] >= x): ndx2 = ndx + 1 elif (ndx == datalen - 1) or (screen_pt[0] <= x): ndx2 = ndx - 1 x2 = self.x_mapper.map_screen(index_data[ndx2]) y2 = self.y_mapper.map_screen(value_data[ndx2]) dist = point_line_distance(screen_pt, (x,y), (x2,y2)) if (threshold == 0.0) or (dist <= threshold): return (x, y, x2, y2, dist) else: return None #------------------------------------------------------------------------ # AbstractPlotRenderer interface #------------------------------------------------------------------------ def map_screen(self, data_array): """ Maps an array of data points into screen space and returns it as an array. Implements the AbstractPlotRenderer interface. """ # data_array is Nx2 array if len(data_array) == 0: return [] x_ary, y_ary = transpose(data_array) sx = self.index_mapper.map_screen(x_ary) sy = self.value_mapper.map_screen(y_ary) if self.orientation == "h": return transpose(array((sx,sy))) else: return transpose(array((sy,sx))) def map_data(self, screen_pt, all_values=False): """ Maps a screen space point into the "index" space of the plot. Implements the AbstractPlotRenderer interface. If *all_values* is True, returns an array of (index, value) tuples; otherwise, it returns only the index values. """ x, y = screen_pt if self.orientation == 'v': x, y = y, x if all_values: return array((self.index_mapper.map_data(x), self.value_mapper.map_data(y))) else: return self.index_mapper.map_data(x) def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index array(s). Implements the AbstractPlotRenderer interface. Parameters ---------- screen_pt : Screen space point threshold : float Maximum distance from screen space point to plot data point. A value of 0.0 means no threshold (any distance will do). outside_returns_none : bool If True, a screen space point outside the data range returns None. Otherwise, it returns either 0 (outside the lower range) or the last index (outside the upper range) index_only : bool If True, the threshold is measured on the distance between the index values, otherwise as Euclidean distance between the (x,y) coordinates. """ data_pt = self.map_data(screen_pt) if ((data_pt < self.index_mapper.range.low) or (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None try: # find the closest point to data_pt in index_data ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError: # if reverse_map raises this exception, it means that data_pt is # outside the range of values in index_data. if outside_returns_none: return None else: if data_pt < index_data[0]: return 0 else: return len(index_data) - 1 if threshold == 0.0: # Don't do any threshold testing return ndx x = index_data[ndx] y = value_data[ndx] if isnan(x) or isnan(y): return None # transform x,y in a 1x2 array, which is the preferred format of # map_screen. this makes it robust against differences in # the map_screen methods of logmapper and linearmapper # when passed a scalar xy = array([[x,y]]) sx, sy = self.map_screen(xy).T if index_only and (threshold == 0.0 or screen_pt[0]-sx < threshold): return ndx elif ((screen_pt[0]-sx)**2 + (screen_pt[1]-sy)**2 < threshold*threshold): return ndx else: return None def get_screen_points(self): """Returns the currently visible screen-space points. Intended for use with overlays. """ self._gather_points() if self.use_downsampling: # The BaseXYPlot implementation of _downsample doesn't actually # do any downsampling. return self._downsample() else: return self.map_screen(self._cached_data_pts) #------------------------------------------------------------------------ # PlotComponent interface #------------------------------------------------------------------------ def _draw_plot(self, gc, view_bounds=None, mode="normal"): """ Draws the 'plot' layer. """ self._draw_component(gc, view_bounds, mode) return def _draw_component(self, gc, view_bounds=None, mode="normal"): # This method should be folded into self._draw_plot(), but is here for # backwards compatibilty with non-draw-order stuff. pts = self.get_screen_points() self._render(gc, pts) return def _draw_default_axes(self, gc): if not self.origin_axis_visible: return with gc: gc.set_stroke_color(self.origin_axis_color_) gc.set_line_width(self.origin_axis_width) gc.set_line_dash(None) for range in (self.index_mapper.range, self.value_mapper.range): if (range.low < 0) and (range.high > 0): if range == self.index_mapper.range: dual = self.value_mapper.range data_pts = array([[0.0,dual.low], [0.0, dual.high]]) else: dual = self.index_mapper.range data_pts = array([[dual.low,0.0], [dual.high,0.0]]) start,end = self.map_screen(data_pts) start = around(start) end = around(end) gc.move_to(int(start[0]), int(start[1])) gc.line_to(int(end[0]), int(end[1])) gc.stroke_path() return def _post_load(self): super(BaseXYPlot, self)._post_load() self._update_mappers() self.invalidate_draw() self._cache_valid = False self._screen_cache_valid = False return def _update_subdivision(self): return #------------------------------------------------------------------------ # Properties #------------------------------------------------------------------------ def _get_index_range(self): return self.index_mapper.range def _set_index_range(self, val): self.index_mapper.range = val def _get_value_range(self): return self.value_mapper.range def _set_value_range(self, val): self.value_mapper.range = val def _get_x_mapper(self): if self.orientation == "h": return self.index_mapper else: return self.value_mapper def _get_y_mapper(self): if self.orientation == "h": return self.value_mapper else: return self.index_mapper def _get_hgrid(self): for obj in self.underlays+self.overlays: if isinstance(obj, PlotGrid) and obj.orientation=="horizontal": return obj else: return None def _get_vgrid(self): for obj in self.underlays+self.overlays: if isinstance(obj, PlotGrid) and obj.orientation=="vertical": return obj else: return None def _get_x_axis(self): for obj in self.underlays+self.overlays: if isinstance(obj, PlotAxis) and obj.orientation in ("bottom", "top"): return obj else: return None def _get_y_axis(self): for obj in self.underlays+self.overlays: if isinstance(obj, PlotAxis) and obj.orientation in ("left", "right"): return obj else: return None def _get_labels(self): labels = [] for obj in self.underlays+self.overlays: if isinstance(obj, PlotLabel): labels.append(obj) return labels #------------------------------------------------------------------------ # Event handlers #------------------------------------------------------------------------ def _update_mappers(self): x_mapper = self.index_mapper y_mapper = self.value_mapper if self.orientation == "v": x_mapper, y_mapper = y_mapper, x_mapper x = self.x x2 = self.x2 y = self.y y2 = self.y2 if "left" in self.origin: x_mapper.screen_bounds = (x, x2) else: x_mapper.screen_bounds = (x2, x) if "bottom" in self.origin: y_mapper.screen_bounds = (y, y2) else: y_mapper.screen_bounds = (y2, y) self.invalidate_draw() self._cache_valid = False self._screen_cache_valid = False def _bounds_changed(self, old, new): super(BaseXYPlot, self)._bounds_changed(old, new) self._update_mappers() def _bounds_items_changed(self, event): super(BaseXYPlot, self)._bounds_items_changed(event) self._update_mappers() def _position_changed(self): self._update_mappers() def _position_items_changed(self): self._update_mappers() def _orientation_changed(self): self._update_mappers() def _index_changed(self, old, new): if old is not None: old.on_trait_change(self._either_data_changed, "data_changed", remove=True) old.on_trait_change(self._either_metadata_changed, "metadata_changed", remove=True) if new is not None: new.on_trait_change(self._either_data_changed, "data_changed") new.on_trait_change(self._either_metadata_changed, "metadata_changed") self._either_data_changed() return def _either_data_changed(self): self.invalidate_draw() self._cache_valid = False self._screen_cache_valid = False self.request_redraw() return def _either_metadata_changed(self): # By default, don't respond to metadata change events. pass def _value_changed(self, old, new): if old is not None: old.on_trait_change(self._either_data_changed, "data_changed", remove=True) old.on_trait_change(self._either_metadata_changed, "metadata_changed", remove=True) if new is not None: new.on_trait_change(self._either_data_changed, "data_changed") new.on_trait_change(self._either_metadata_changed, "metadata_changed") self._either_data_changed() return def _origin_changed(self, old, new): # origin switch from left to right or vice versa? if old.split()[1] != new.split()[1]: xm = self.x_mapper xm.low_pos, xm.high_pos = xm.high_pos, xm.low_pos # origin switch from top to bottom or vice versa? if old.split()[0] != new.split()[0]: ym = self.y_mapper ym.low_pos, ym.high_pos = ym.high_pos, ym.low_pos self.invalidate_draw() self._screen_cache_valid = False return def _index_mapper_changed(self, old, new): self._either_mapper_changed(self, "index_mapper", old, new) if self.orientation == "h": self.trait_property_changed("x_mapper", old, new) else: self.trait_property_changed("y_mapper", old, new) return def _value_mapper_changed(self, old, new): self._either_mapper_changed(self, "value_mapper", old, new) if self.orientation == "h": self.trait_property_changed("y_mapper", old, new) else: self.trait_property_changed("x_mapper", old, new) return def _either_mapper_changed(self, obj, name, old, new): if old is not None: old.on_trait_change(self._mapper_updated_handler, "updated", remove=True) if new is not None: new.on_trait_change(self._mapper_updated_handler, "updated") self.invalidate_draw() self._screen_cache_valid = False return def _mapper_updated_handler(self): self._cache_valid = False self._screen_cache_valid = False self.invalidate_draw() self.request_redraw() return def _visible_changed(self, old, new): if new: self._layout_needed = True def _bgcolor_changed(self): self.invalidate_draw() def _use_subdivision_changed(self, old, new): if new: self._set_up_subdivision() return #------------------------------------------------------------------------ # Persistence #------------------------------------------------------------------------ def __getstate__(self): state = super(BaseXYPlot,self).__getstate__() for key in ['_cache_valid', '_cached_data_pts', '_screen_cache_valid', '_cached_screen_pts']: if key in state: del state[key] return state def __setstate__(self, state): super(BaseXYPlot, self).__setstate__(state) if self.index is not None: self.index.on_trait_change(self._either_data_changed, "data_changed") if self.value is not None: self.value.on_trait_change(self._either_data_changed, "data_changed") self.invalidate_draw() self._cache_valid = False self._screen_cache_valid = False self._update_mappers() return
class Editor(HasPrivateTraits): """ Represents an editing control for an object trait in a Traits-based user interface. """ #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The UI (user interface) this editor is part of: ui = Instance('traitsui.ui.UI') # Full name of the object the editor is editing (e.g. 'object.link1.link2'): object_name = Str('object') # The object this editor is editing (e.g. object.link1.link2): object = Instance(HasTraits) # The name of the trait this editor is editing (e.g. 'value'): name = ReadOnly # The context object the editor is editing (e.g. object): context_object = Property # The extended name of the object trait being edited. That is, # 'object_name.name' minus the context object name at the beginning. For # example: 'link1.link2.value': extended_name = Property # Original value of object.name (e.g. object.link1.link2.value): old_value = Any # Text description of the object trait being edited: description = ReadOnly # The Item object used to create this editor: item = Instance(Item, ()) # The GUI widget defined by this editor: control = Any # The GUI label (if any) defined by this editor: label_control = Any # Is the underlying GUI widget enabled? enabled = Bool(True) # Is the underlying GUI widget visible? visible = Bool(True) # Is the underlying GUI widget scrollable? scrollable = Bool(False) # The EditorFactory used to create this editor: factory = factory_trait # Is the editor updating the object.name value? updating = Bool(False) # Current value for object.name: value = Property # Current value of object trait as a string: str_value = Property # The trait the editor is editing (not its value, but the trait itself): value_trait = Property # The current editor invalid state status: invalid = Bool(False) #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__(self, parent, **traits): """ Initializes the editor object. """ HasPrivateTraits.__init__(self, **traits) try: self.old_value = getattr(self.object, self.name) except AttributeError: ctrait = self.object.base_trait(self.name) if ctrait.type == 'event' or self.name == 'spring': # Getting the attribute will fail for 'Event' traits: self.old_value = Undefined else: raise # Synchronize the application invalid state status with the editor's: self.sync_value(self.factory.invalid, 'invalid', 'from') #--------------------------------------------------------------------------- # Finishes editor set-up: #--------------------------------------------------------------------------- def prepare(self, parent): """ Finishes setting up the editor. """ name = self.extended_name if name != 'None': self.context_object.on_trait_change(self._update_editor, name, dispatch='ui') self.init(parent) self._sync_values() self.update_editor() #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ raise NotImplementedError #--------------------------------------------------------------------------- # Assigns focus to the editor's underlying toolkit widget: #--------------------------------------------------------------------------- def set_focus(self): """ Assigns focus to the editor's underlying toolkit widget. """ raise NotImplementedError #--------------------------------------------------------------------------- # Disposes of the contents of an editor: #--------------------------------------------------------------------------- def dispose(self): """ Disposes of the contents of an editor. """ if self.ui is None: return name = self.extended_name if name != 'None': self.context_object.on_trait_change(self._update_editor, name, remove=True) if self._user_from is not None: for name, handler in self._user_from: self.on_trait_change(handler, name, remove=True) if self._user_to is not None: for object, name, handler in self._user_to: object.on_trait_change(handler, name, remove=True) # Break linkages to references we no longer need: self.object = self.ui = self.item = self.factory = self.control = \ self.label_control = self.old_value = self._context_object = None #--------------------------------------------------------------------------- # Returns the context object the editor is using (Property implementation): #--------------------------------------------------------------------------- @cached_property def _get_context_object(self): """ Returns the context object the editor is using (Property implementation). """ object_name = self.object_name context_key = object_name.split('.', 1)[0] if (object_name != '') and (context_key in self.ui.context): return self.ui.context[context_key] # This handles the case of a 'ListItemProxy', which is not in the # ui.context, but is the editor 'object': return self.object #--------------------------------------------------------------------------- # Returns the extended trait name being edited (Property implementation): #--------------------------------------------------------------------------- @cached_property def _get_extended_name(self): """ Returns the extended trait name being edited (Property implementation). """ return ('%s.%s' % (self.object_name, self.name)).split('.', 1)[1] #--------------------------------------------------------------------------- # Returns the trait the editor is editing (Property implementation): #--------------------------------------------------------------------------- def _get_value_trait(self): """ Returns the trait the editor is editing (Property implementation). """ return self.object.trait(self.name) #--------------------------------------------------------------------------- # Gets/Sets the associated object trait's value: #--------------------------------------------------------------------------- def _get_value(self): return getattr(self.object, self.name, Undefined) def _set_value(self, value): if self.ui and self.name != 'None': self.ui.do_undoable(self.__set_value, value) def __set_value(self, value): self._no_update = True try: try: handler = self.ui.handler obj_name = self.object_name name = self.name method = (getattr(handler, '%s_%s_setattr' % (obj_name, name), None) or getattr(handler, '%s_setattr' % name, None) or getattr(handler, 'setattr')) method(self.ui.info, self.object, name, value) except TraitError as excp: self.error(excp) raise finally: self._no_update = False #--------------------------------------------------------------------------- # Returns the text representation of a specified object trait value: #--------------------------------------------------------------------------- def string_value(self, value, format_func=None): """ Returns the text representation of a specified object trait value. If the **format_func** attribute is set on the editor factory, then this method calls that function to do the formatting. If the **format_str** attribute is set on the editor factory, then this method uses that string for formatting. If neither attribute is set, then this method just calls the built-in unicode() function. """ factory = self.factory #print "editor", value.decode(encoding='UTF-16',errors='strict') if factory.format_func is not None: return factory.format_func(value) if factory.format_str != '': return factory.format_str % value if format_func is not None: return format_func(value) """if value.startswith(b'\xFF\xD8')and value.endswith(b'\xFF\xD9'): from base64 import b64encode mime='image/jpeg' fmt = 'data:{mime};base64,{data}' b64 = b64encode(value).decode('ascii') return fmt.format(mime=mime, data=b64)""" return unicode(value) #--------------------------------------------------------------------------- # Returns the text representation of the object trait: #--------------------------------------------------------------------------- def _get_str_value(self): """ Returns the text representation of the object trait. """ return self.string_value(getattr(self.object, self.name, Undefined)) #--------------------------------------------------------------------------- # Returns the text representation of a specified value: #--------------------------------------------------------------------------- def _str(self, value): """ Returns the text representation of a specified value. """ # In Unicode! return unicode(value) #--------------------------------------------------------------------------- # Handles an error that occurs while setting the object's trait value: # # (Should normally be overridden in a subclass) #--------------------------------------------------------------------------- def error(self, excp): """ Handles an error that occurs while setting the object's trait value. """ pass #--------------------------------------------------------------------------- # Performs updates when the object trait changes: #--------------------------------------------------------------------------- def _update_editor(self, object, name, old_value, new_value): """ Performs updates when the object trait changes. """ # If background threads have modified the trait the editor is bound to, # their trait notifications are queued to the UI thread. It is possible # that by the time the UI thread dispatches these events, the UI the # editor is part of has already been closed. So we need to check if we # are still bound to a live UI, and if not, exit immediately: if self.ui is None: return # If the notification is for an object different than the one actually # being edited, it is due to editing an item of the form: # object.link1.link2.name, where one of the 'link' objects may have # been modified. In this case, we need to rebind the current object # being edited: if object is not self.object: self.object = eval(self.object_name, globals(), self.ui.context) # If the editor has gone away for some reason, disconnect and exit: if self.control is None: self.context_object.on_trait_change(self._update_editor, self.extended_name, remove=True) return # Log the change that was made (as long as it is not for an event): if object.base_trait(name).type != 'event': self.log_change(self.get_undo_item, object, name, old_value, new_value) # If the change was not caused by the editor itself: if not self._no_update: # Update the editor control to reflect the current object state: self.update_editor() #--------------------------------------------------------------------------- # Logs a change made in the editor: #--------------------------------------------------------------------------- def log_change(self, undo_factory, *undo_args): """ Logs a change made in the editor. """ # Indicate that the contents of the user interface have been changed: ui = self.ui ui.modified = True # Create an undo history entry if we are maintaining a history: undoable = ui._undoable if undoable >= 0: history = ui.history if history is not None: item = undo_factory(*undo_args) if item is not None: if undoable == history.now: # Create a new undo transaction: history.add(item) else: # Extend the most recent undo transaction: history.extend(item) #--------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: # # (Should normally be overridden in a subclass) #--------------------------------------------------------------------------- def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ pass #--------------------------------------------------------------------------- # Creates an undo history entry: # # (Can be overridden in a subclass for special value types) #--------------------------------------------------------------------------- def get_undo_item(self, object, name, old_value, new_value): """ Creates an undo history entry. """ return UndoItem(object=object, name=name, old_value=old_value, new_value=new_value) #--------------------------------------------------------------------------- # Returns a tuple of the form ( context_object, name[.name...], callable ) # for a specified extended name of the form: name or # context_object_name.name[.name...]: #--------------------------------------------------------------------------- def parse_extended_name(self, name): """ Returns a tuple of the form ( context_object, 'name[.name...], callable ) for a specified extended name of the form: 'name' or 'context_object_name.name[.name...]'. """ col = name.find('.') if col < 0: object = self.context_object else: object, name = self.ui.context[name[:col]], name[col + 1:] return (object, name, eval("lambda obj=object: obj." + name)) #--------------------------------------------------------------------------- # Initializes and synchronizes (as needed) editor traits with the value of # corresponding factory traits: #--------------------------------------------------------------------------- def _sync_values(self): """ Initializes and synchronizes (as needed) editor traits with the value of corresponding factory traits. """ factory = self.factory for name, trait in factory.traits(sync_value=not_none): value = getattr(factory, name) if isinstance(value, ContextValue): self_trait = self.trait(name) self.sync_value(value.name, name, self_trait.sync_value or trait.sync_value, self_trait.is_list is True) elif value is not Undefined: setattr(self, name, value) #--------------------------------------------------------------------------- # Sets/Unsets synchronization between an editor trait and a user object # trait: #--------------------------------------------------------------------------- def sync_value(self, user_name, editor_name, mode='both', is_list=False): """ Sets or unsets synchronization between an editor trait and a user object trait. """ if user_name != '': key = '%s:%s' % (user_name, editor_name) if self._no_trait_update is None: self._no_trait_update = {} user_ref = 'user_object' col = user_name.find('.') if col < 0: user_object = self.context_object xuser_name = user_name else: user_object = self.ui.context[user_name[:col]] user_name = xuser_name = user_name[col + 1:] col = user_name.rfind('.') if col >= 0: user_ref += ('.' + user_name[:col]) user_name = user_name[col + 1:] user_value = compile('%s.%s' % (user_ref, user_name), '<string>', 'eval') user_ref = compile(user_ref, '<string>', 'eval') if mode in ('from', 'both'): def user_trait_modified(new): # Need this to include 'user_object' in closure: user_object if key not in self._no_trait_update: self._no_trait_update[key] = None try: setattr(self, editor_name, new) except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] user_object.on_trait_change(user_trait_modified, xuser_name) if self._user_to is None: self._user_to = [] self._user_to.append( (user_object, xuser_name, user_trait_modified)) if is_list: def user_list_modified(event): if isinstance(event, TraitListEvent): if key not in self._no_trait_update: self._no_trait_update[key] = None n = event.index try: getattr(self, editor_name )[n:n + len(event.removed)] = event.added except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] user_object.on_trait_change(user_list_modified, xuser_name + '_items') self._user_to.append((user_object, xuser_name + '_items', user_list_modified)) try: setattr(self, editor_name, eval(user_value)) except: from traitsui.api import raise_to_debug raise_to_debug() if mode in ('to', 'both'): def editor_trait_modified(new): # Need this to include 'user_object' in closure: user_object if key not in self._no_trait_update: self._no_trait_update[key] = None try: setattr(eval(user_ref), user_name, new) except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] self.on_trait_change(editor_trait_modified, editor_name) if self._user_from is None: self._user_from = [] self._user_from.append((editor_name, editor_trait_modified)) if is_list: def editor_list_modified(event): # Need this to include 'user_object' in closure: user_object if key not in self._no_trait_update: self._no_trait_update[key] = None n = event.index try: eval(user_value )[n:n + len(event.removed)] = event.added except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] self.on_trait_change(editor_list_modified, editor_name + '_items') self._user_from.append( (editor_name + '_items', editor_list_modified)) if mode == 'to': try: setattr(eval(user_ref), user_name, getattr(self, editor_name)) except: from traitsui.api import raise_to_debug raise_to_debug() #-- UI preference save/restore interface ----------------------------------- #--------------------------------------------------------------------------- # Restores any saved user preference information associated with the # editor: #--------------------------------------------------------------------------- def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ pass #--------------------------------------------------------------------------- # Returns any user preference information associated with the editor: #--------------------------------------------------------------------------- def save_prefs(self): """ Returns any user preference information associated with the editor. """ return None
class SourceMixer( SamplesGenerator ): """ Mixes the signals from several sources. """ #: List of :class:`~acoular.sources.SamplesGenerator` objects #: to be mixed. sources = List( Instance(SamplesGenerator, ()) ) #: Sampling frequency of the signal. sample_freq = Trait( SamplesGenerator().sample_freq ) #: Number of channels. numchannels = Trait( SamplesGenerator().numchannels ) #: Number of samples. numsamples = Trait( SamplesGenerator().numsamples ) # internal identifier ldigest = Property( depends_on = ['sources.digest', ]) # internal identifier digest = Property( depends_on = ['ldigest', '__class__']) traits_view = View( Item('sources', style='custom') ) @cached_property def _get_ldigest( self ): res = '' for s in self.sources: res += s.digest return res @cached_property def _get_digest( self ): return digest(self) @on_trait_change('sources') def validate_sources( self ): """ Validates if sources fit together. """ if self.sources: self.sample_freq = self.sources[0].sample_freq self.numchannels = self.sources[0].numchannels self.numsamples = self.sources[0].numsamples for s in self.sources[1:]: if self.sample_freq != s.sample_freq: raise ValueError("Sample frequency of %s does not fit" % s) if self.numchannels != s.numchannels: raise ValueError("Channel count of %s does not fit" % s) if self.numsamples != s.numsamples: raise ValueError("Number of samples of %s does not fit" % s) def result(self, num): """ Python generator that yields the output block-wise. The outputs from the sources in the list are being added. Parameters ---------- num : integer This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block). Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ gens = [i.result(num) for i in self.sources[1:]] for temp in self.sources[0].result(num): sh = temp.shape[0] for g in gens: temp1 = next(g) if temp.shape[0] > temp1.shape[0]: temp = temp[:temp1.shape[0]] temp += temp1[:temp.shape[0]] yield temp if sh > temp.shape[0]: break
class TableModel(GridModel): """ Model for table data. """ # ------------------------------------------------------------------------- # Trait definitions: # ------------------------------------------------------------------------- #: The editor that created this model editor = Instance(Editor) #: The current filter filter = Instance(TableFilter, allow_none=True) #: Current filter summary message filter_summary = Str("All items") #: Display the table items in reverse order? reverse = Bool(False) #: Event fired when the table has been sorted sorted = Event #: The current 'auto_add' row auto_add_row = Any def __init__(self, **traits): """ Initializes the object. """ super(TableModel, self).__init__(**traits) # Attach trait handlers to the list object: editor = self.editor object = editor.context_object name = " " + editor.extended_name # Set up listeners for any of the model data changing: object.on_trait_change(self._on_data_changed, name, dispatch="ui") object.on_trait_change( self.fire_content_changed, name + ".-", dispatch="ui" ) # Set up listeners for any column definitions changing: editor.on_trait_change(self.update_columns, "columns", dispatch="ui") editor.on_trait_change( self.update_columns, "columns_items", dispatch="ui" ) # Initialize the current filter from the editor's default filter: self.filter = editor.filter # If we are using 'auto_add' mode, create the first 'auto_add' row: if editor.auto_add: self.auto_add_row = row = editor.create_new_row() if row is not None: row.on_trait_change(self.on_auto_add_row, dispatch="ui") # -- TableModel Interface ------------------------------------------------- def dispose(self): """ Disposes of the model when it is no longer needed. """ editor = self.editor object = editor.context_object name = " " + editor.extended_name # Remove listeners for any of the model data changing: object.on_trait_change(self._on_data_changed, name, remove=True) object.on_trait_change( self.fire_content_changed, name + ".-", remove=True ) # Remove listeners for any column definitions changing: editor.on_trait_change(self.update_columns, "columns", remove=True) editor.on_trait_change( self.update_columns, "columns_items", remove=True ) # Make sure we have removed listeners from the current filter also: if self.filter is not None: self.filter.on_trait_change(self._filter_modified, remove=True) # Clean-up any links that should be broken: self.editor = None def get_filtered_items(self): """ Returns all model items matching the current filter. """ return self.__filtered_items() def get_filtered_item(self, index=0): """ Returns a single specified item from those items matching the current filter. """ try: return self.__filtered_items()[index] except: logger.error( "TableModel error: Request for invalid row %d out of " "%d" % (index, len(self.__filtered_items())) ) return None def raw_index_of(self, row): """ Returns the raw, unfiltered index corresponding to a specified filtered index. """ if self._filtered_cache is None: return row return self.editor.filtered_indices[row] def insert_filtered_item_after(self, index, item): """ Inserts an object after a specified filtered index. """ mapped_index = 0 n = len(self.editor.filtered_indices) if index >= n: if (index != 0) or (n != 0): raise IndexError elif index >= 0: mapped_index = self.editor.filtered_indices[index] + 1 self.__items().insert(mapped_index, item) sorted = self._sort_model() if sorted: mapped_index = self.__items().index(item) self._filtered_cache = None return (mapped_index, sorted) def delete_filtered_item_at(self, index): """ Deletes the object at the specified filtered index. """ if index >= len(self.editor.filtered_indices): raise IndexError mapped_index = self.editor.filtered_indices[index] items = self.__items() object = items[mapped_index] del items[mapped_index] self._filtered_cache = None return (mapped_index, object) def update_columns(self): """ Updates the table view when columns have been changed. """ self._columns = None self.fire_structure_changed() self.editor.refresh() def no_column_sort(self): """ Resets any sorting being performed on the underlying model. """ self._sorter = self._filtered_cache = None self.column_sorted = GridSortEvent(index=-1) # self.fire_structure_changed() # -- Event Handlers ------------------------------------------------------- @on_trait_change("filter.+") def _filter_modified(self): """ Handles the contents of the filter being changed. """ self._filtered_cache = None self.fire_structure_changed() self.editor.filter_modified() def _click_changed(self, event): """ Handles the grid firing a 'click' event. """ row, col = event # Fire the same event on the editor after mapping it to a model object # and column name: object = self.get_filtered_item(row) column = self.__get_column(col) self.editor.click = (object, column) # Check to see if the column has a view to display: view = column.get_view(object) if view is not None: column.get_object(object).edit_traits( view=view, parent=self._bounds_for(row, col) ) # Invoke the column's click handler: column.on_click(object) def _dclick_changed(self, event): """ Handles the grid firing a 'dclick' event. """ row, col = event # Fire the same event on the editor after mapping it to a model object # and column name: object = self.get_filtered_item(row) column = self.__get_column(col) self.editor.dclick = (object, column) # Invoke the column's double-click handler: column.on_dclick(object) def on_auto_add_row(self): """ Handles the user modifying the current 'auto_add' mode row. """ object = self.auto_add_row object.on_trait_change(self.on_auto_add_row, remove=True) self.auto_add_row = row = self.editor.create_new_row() if row is not None: row.on_trait_change(self.on_auto_add_row, dispatch="ui") do_later( self.editor.add_row, object, len(self.get_filtered_items()) - 2 ) # -- GridModel Interface -------------------------------------------------- def get_column_count(self): """ Returns the number of columns for this table. """ return len(self.__get_columns()) def get_column_name(self, index): """ Returns the label of the column specified by the (zero-based) index. """ return self.__get_column(index).get_label() def get_column_size(self, index): """ Returns the size in pixels of the column indexed by *index*. A value of -1 or None means to use the default. """ return self.__get_column(index).get_width() def get_cols_drag_value(self, cols): """ Returns the value to use when the specified columns are dragged or copied and pasted. The parameter *cols* is a list of column indexes. """ return [self.__get_data_column(col) for col in cols] def get_cols_selection_value(self, cols): """ Returns a list of TraitGridSelection objects containing the objects corresponding to the grid rows and the traits corresponding to the specified columns. """ values = [] for obj in self.__items(False): values.extend( [ TraitGridSelection( obj=obj, name=self.__get_column_name(col) ) for col in cols ] ) return values def sort_by_column(self, col, reverse=False): """ Sorts the model data by the column indexed by *col*. """ # Make sure we allow sorts by column: factory = self.editor.factory if not factory.sortable: return # Flush the object cache: self._filtered_cache = None # Cache the sorting information for later: self._sorter = self.__get_column(col).key self._reverse = reverse # If model sorting is requested, do it now: self._sort_model() # Indicate the we have been sorted: self.sorted = True self.column_sorted = GridSortEvent(index=col, reversed=reverse) def is_column_read_only(self, index): """ Returns True if the column specified by the zero-based *index* is read-only. """ return not self.__get_column(index).editable def get_row_count(self): """ Return the number of rows for this table. """ return len(self.__filtered_items()) def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) *index*. """ return "<undefined>" def get_rows_drag_value(self, rows): """ Returns the value to use when the specified rows are dragged or copied and pasted. The parameter *rows* is a list of row indexes. If there is only one row listed, then return the corresponding trait object. If more than one row is listed, then return a list of objects. """ items = self.__filtered_items() return [items[row] for row in rows] def get_rows_selection_value(self, rows): """ Returns a list of TraitGridSelection objects containing the object corresponding to the selected rows. """ items = self.__filtered_items() return [TraitGridSelection(obj=items[row]) for row in rows] def is_row_read_only(self, index): """ Returns True if the row specified by the zero-based *index* is read-only. """ return False def get_cell_editor(self, row, col): """ Returns the editor for the specified cell. """ if self.editor is None: return None column = self.__get_column(col) object = self.get_filtered_item(row) editor = column.get_editor(object) if editor is None: return None editor._ui = self.editor.ui target, name = column.target_name(object) return TraitGridCellAdapter( editor, target, name, "", context=self.editor.ui.context, style=column.get_style(object), width=column.get_edit_width(object), height=column.get_edit_height(object), ) def get_cell_renderer(self, row, col): """ Returns the renderer for the specified cell. """ return self.__get_column(col).get_renderer(self.get_filtered_item(row)) def get_cell_drag_value(self, row, col): """ Returns the value to use when the specified cell is dragged or copied and pasted. """ return self.__get_column(col).get_drag_value( self.get_filtered_item(row) ) def get_cell_selection_value(self, row, col): """ Returns a TraitGridSelection object specifying the data stored in the table at (*row*, *col*). """ return TraitGridSelection( obj=self.get_filtered_item(row), name=self.__get_column_name(col) ) def resolve_selection(self, selection_list): """ Returns a list of (row, col) grid-cell coordinates that correspond to the objects in *selection_list*. For each coordinate, if the row is -1, it indicates that the entire column is selected. Likewise coordinates with a column of -1 indicate an entire row that is selected. For the TableModel, the objects in *selection_list* must be TraitGridSelection objects. """ items = self.__filtered_items() cells = [] for selection in selection_list: row = -1 if selection.obj is not None: try: row = items.index(selection.obj) except ValueError: continue column = -1 if selection.name != "": column = self._get_column_index_by_trait(selection.name) if column is None: continue cells.append((row, column)) return cells def get_cell_context_menu(self, row, col): """ Returns a Menu object that generates the appropriate context menu for this cell. """ column = self.__get_column(col) menu = column.get_menu(self.get_filtered_item(row)) editor = self.editor if menu is None: menu = editor.factory.menu if menu is None: menu_name = editor.factory.menu_name if menu_name: menu = getattr(self.editor.object, menu_name, None) if menu is not None: editor.prepare_menu(row, column) return (menu, editor) return None def get_value(self, row, col): """ Returns the value stored in the table at (*row*, *col*). """ object = self.get_filtered_item(row) if object is self.auto_add_row: return "" value = self.__get_column(col).get_value(object) formats = self.__get_column_formats(col) if (value is not None) and (formats is not None): format = formats.get(type(value)) if format is not None: try: if callable(format): value = format(value) else: value = format % value except: pass return value def is_valid_cell_value(self, row, col, value): """ Tests whether *value* is valid for the cell at (*row*, *col*). Returns True if value is acceptable, and False otherwise. """ return self.__get_column(col).is_droppable( self.get_filtered_item(row), value ) def is_cell_empty(self, row, col): """ Returns True if the cell at (*row*, *col*) has a None value, and False otherwise. """ return self.get_value(row, col) is None def is_cell_read_only(self, row, col): """ Returns True if the cell at (*row*, *col*) is read-only, and False otherwise. """ return not self.__get_column(col).is_editable( self.get_filtered_item(row) ) def get_cell_bg_color(self, row, col): """ Returns a wxColour object specifying the background color of the specified cell. """ return self.__get_column(col).get_cell_color( self.get_filtered_item(row) ) def get_cell_text_color(self, row, col): """ Returns a wxColour object specifying the text color of the specified cell. """ column = self.__get_column(col) item = self.get_filtered_item(row) return column.get_text_color(item) def get_cell_font(self, row, col): """ Returns a wxFont object specifying the font of the specified cell. """ return self.__get_column(col).get_text_font( self.get_filtered_item(row) ) def get_cell_halignment(self, row, col): """ Returns a string specifying the horizontal alignment of the specified cell. Returns 'left' for left alignment, 'right' for right alignment, or 'center' for center alignment. """ return self.__get_column(col).get_horizontal_alignment( self.get_filtered_item(row) ) def get_cell_valignment(self, row, col): """ Returns a string specifying the vertical alignment of the specified cell. Returns 'top' for top alignment, 'bottom' for bottom alignment, or 'center' for center alignment. """ return self.__get_column(col).get_vertical_alignment( self.get_filtered_item(row) ) def _insert_rows(self, pos, num_rows): """ Inserts *num_rows* at *pos*; fires an event only if a factory method for new rows is defined or the model is not empty. Otherwise, it returns 0. """ count = 0 factory = self.editor.factory.row_factory if factory is None: items = self.__items(False) if len(items) > 0: factory = items[0].__class__ if factory is not None: new_data = [ x for x in [factory() for i in range(num_rows)] if x is not None ] if len(new_data) > 0: count = self._insert_rows_into_model(pos, new_data) self.rows_added = ("added", pos, new_data) return count def _delete_rows(self, pos, num_rows): """ Removes rows *pos* through *pos* + *num_rows* from the model. """ row_count = self.get_rows_count() if (pos + num_rows) > row_count: num_rows = row_count - pos return self._delete_rows_from_model(pos, num_rows) def _set_value(self, row, col, value): """ Sets the value of the cell at (*row*, *col*) to *value*. Raises a ValueError if the value is vetoed or the cell at the specified position does not exist. """ new_rows = 0 column = self.__get_column(col) obj = None try: obj = self.get_filtered_item(row) except: # Add a new row: new_rows = self._insert_rows(self.get_row_count(), 1) if new_rows > 0: # Now set the value on the new object: try: obj = self.get_filtered_item(self.get_row_count() - 1) except: # fixme: what do we do in this case? veto the set somehow? # raise an exception? pass if obj is not None: self._set_data_on_row(obj, column, value) return new_rows def _move_column(self, frm, to): """ Moves a specified **frm** column to before the specified **to** column. Returns **True** if successful; **False** otherwise. """ to_column = None if to < len(self.__get_columns()): to_column = self.__get_column(to) return self.editor.move_column(self.__get_column(frm), to_column) def _set_data_on_row(self, row, column, value): """ Sets the cell specified by (*row*, *col*) to *value, which can be either a member of the row object, or a no-argument method on that object. """ column.set_value(row, value) def _insert_rows_into_model(self, pos, new_data): """ Inserts the given new rows into the model. """ raw_pos = self.raw_index_of(pos) self.__items()[raw_pos:raw_pos] = new_data def _delete_rows_from_model(self, pos, num_rows): """ Deletes the specified rows from the model. """ raw_rows = sorted( [self.raw_index_of(i) for i in range(pos, pos + num_rows)] ) raw_rows.reverse() items = self.__items() for row in raw_rows: del items[row] return num_rows def _on_data_changed(self): """ Forces the grid to refresh when the underlying list changes. """ # Invalidate the current cache (if any): self._filtered_cache = None self.fire_structure_changed() def _mouse_cell_changed(self, new): """ Handles the user mousing over a specified cell. """ row, col = new column = self.__get_column(col) object = self.get_filtered_item(row) # Update the tooltip if necessary: tooltip = column.get_tooltip(object) if tooltip != self._tooltip: self._tooltip = tooltip self.editor.grid._grid_window.SetToolTip(wx.ToolTip(tooltip)) if column.is_auto_editable(object): x, y, dx, dy = self._bounds_for(row, col) if column.is_editable(object): view = View( Item( name=column.name, editor=column.get_editor(object), style=column.get_style(object), show_label=False, padding=-4, ), kind="info", width=dx, height=dy, ) else: view = column.get_view(object) if view is None: return column.get_object(object).edit_traits( view=view, parent=(x, y, dx, dy) ) def _bounds_for(self, row, col): """ Returns the coordinates and size of the specified cell in the form: ( x, y, dx, dy ). """ grid = self.editor.grid coords = wxg.GridCellCoords(row, col) x, y, dx, dy = grid._grid.BlockToDeviceRect(coords, coords) x, y = grid._grid_window.ClientToScreenXY(x, y) return (x, y, dx, dy) def _sort_model(self): """ Sorts the underlying model if that is what the user requested. """ editor = self.editor sorted = editor.factory.sort_model and (self._sorter is not None) if sorted: items = self.__items(False)[:] items.sort(key=self._sorter) if self.reverse ^ self._reverse: items.reverse() editor.value = items return sorted def __items(self, ordered=True): """ Returns the raw list of model objects. """ result = self.editor.value if not isinstance(result, SequenceTypes): return [result] if ordered and self.reverse: return ReversedList(result) return result def __filtered_items(self): """ Returns the list of all model objects that pass the current filter. """ fc = self._filtered_cache if fc is None: items = self.__items() filter = self.filter if filter is None: nitems = [nitem for nitem in enumerate(items)] self.filter_summary = "All %s items" % len(nitems) else: if not callable(filter): filter = filter.filter nitems = [ nitem for nitem in enumerate(items) if filter(nitem[1]) ] self.filter_summary = "%s of %s items" % ( len(nitems), len(items), ) sorter = self._sorter if sorter is not None: nitems.sort(key=lambda x: sorter(x[1])) if self._reverse: nitems.reverse() self.editor.filtered_indices = [x[0] for x in nitems] self._filtered_cache = fc = [x[1] for x in nitems] if self.auto_add_row is not None: self._filtered_cache.append(self.auto_add_row) return fc def __get_data_column(self, col): """ Returns a list of model data from the column indexed by *col*. """ column = self.__get_column(col) return [column.get_value(item) for item in self.__filtered_items()] def __get_columns(self): columns = self._columns if columns is None: self._columns = columns = [ c for c in self.editor.columns if c.visible ] return columns def __get_column(self, col): try: return self.__get_columns()[col] except: return self.__get_columns()[0] def __get_column_name(self, col): return self.__get_column(col).name def __get_column_formats(self, col): return None # Not used/implemented currently def _get_column_index_by_trait(self, name): for i, col in enumerate(self.__get_columns()): if name == col.name: return i
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 TestCase(Mayavi): """ This class is to be subclassed when you write a test. """ # Interact with the user after test is done? Normally tests just # exit after completion, this prevents that. interact = Bool(False) # Always use offscreen rendering to generate images -- even if # `self.compare_image` was called in the test.. offscreen = Bool(False) # Use the standalone mode. This mode does not use the envisage Mayavi # application. standalone = Bool(True) app_window = Instance('pyface.api.ApplicationWindow') gui = Instance('pyface.gui.GUI') # An exception info if an exception was raised by a test. exception_info = Any ###################################################################### # `Mayavi` interface. ###################################################################### def main(self, argv=None, plugins=None): """Overridden main method that sets the argv to sys.argv[1:] by default. Call this to run the test. """ if argv is None: argv = sys.argv[1:] if not is_running_with_nose(): self.parse_command_line(argv) if self.standalone: self.run_standalone() else: # Call the superclass main method. super(TestCase, self).main(argv, plugins) def setup_logger(self): """Overridden logger setup.""" if self.standalone: path = os.path.join(ETSConfig.application_data, 'mayavi_e3', 'mayavi-test.log') path = os.path.abspath(path) log_path = os.path.dirname(path) if not os.path.exists(log_path): os.makedirs(log_path) else: path = 'mayavi-test.log' setup_logger(logger, path, mode=self.log_mode) def run_standalone(self): from mayavi.core.engine import Engine from mayavi.plugins.script import Script from pyface.api import ApplicationWindow, GUI self.setup_logger() if self.offscreen: engine = Engine(scene_factory=off_screen_viewer) else: engine = Engine() engine.start() self.exception_info = None self.script = Script(engine=engine) self.gui = g = GUI() self.app_window = a = ApplicationWindow() a.open() a.show(False) g.invoke_later(self.run) g.start_event_loop() if self.exception_info is not None: type, value, tb = self.exception_info raise type, value, tb def run(self): """This starts everything up and runs the test. Call main to run the test.""" # Calls the users test code. try: self.do() except Exception, e: type, value, tb = sys.exc_info() if is_running_with_nose(): self.exception_info = type, value, tb else: # To mimic behavior of unittest. sys.stderr.write('\nfailures=1\n') info = traceback.extract_tb(tb) filename, lineno, function, text = info[-1] # last line only exc_msg = "%s\nIn %s:%d\n%s: %s (in %s)" %\ ('Exception', filename, lineno, type.__name__, str(value), function) sys.stderr.write(exc_msg + '\n') # Log the message. logger.exception(exc_msg) if not self.interact: sys.exit(1) finally:
class Pipeline(HasTraits): """ Function used to build pipelines for helper functions """ #doc = '' _source_function = Callable() _pipeline = List() # Traits here only for documentation purposes figure = Instance('mayavi.core.scene.Scene', help='Figure to populate.') def __call__(self, *args, **kwargs): """ Calls the logics of the factory, but only after disabling rendering, if needed. """ # First retrieve the scene, if any. if 'figure' in kwargs: figure = kwargs['figure'] assert isinstance(figure, (Scene, None)) scene = figure.scene else: scene = tools.gcf().scene if scene is not None: self._do_redraw = not scene.disable_render scene.disable_render = True # Then call the real logic output = self.__call_internal__(*args, **kwargs) # And re-enable the rendering, if needed. if scene is not None: scene.disable_render = not self._do_redraw return output def __call_internal__(self, *args, **kwargs): """ Builds the source and runs through the pipeline, returning the last object created by the pipeline.""" self.store_kwargs(kwargs) self.source = self._source_function(*args, **kwargs) # Copy the pipeline so as not to modify it for the next call self.pipeline = self._pipeline[:] return self.build_pipeline() def store_kwargs(self, kwargs): """ Merges the given keyword argument, with traits default and store the resulting dictionary in self.kwargs.""" kwargs = kwargs.copy() all_traits = self.get_all_traits() if not set(kwargs.keys()).issubset(list(all_traits.keys())): raise ValueError("Invalid keyword arguments : %s" % \ ', '.join( str(k) for k in set(kwargs.keys()).difference(list(all_traits.keys())))) traits = self.get(self.class_trait_names()) [traits.pop(key) for key in list(traits.keys()) if key[0] == '_'] traits.update(kwargs) self.kwargs = traits def build_pipeline(self): """ Runs through the pipeline, applying pipe after pipe. """ object = self.source for pipe in self.pipeline: keywords = set(pipe.class_trait_names()) keywords.remove('trait_added') keywords.remove('trait_modified') this_kwargs = {} for key, value in self.kwargs.items(): if key in keywords: this_kwargs[key] = value object = pipe(object, **this_kwargs)._target return object def get_all_traits(self): """ Returns all the traits of class, and the classes in the pipeline. """ traits = {} for pipe in self._pipeline: traits.update(pipe.class_traits()) traits.update(self.class_traits()) traits.pop('trait_added') traits.pop('trait_modified') return traits
class BleedthroughLinearDiagnostic(HasStrictTraits): """ Plots a scatterplot of each channel vs every other channel and the bleedthrough line Attributes ---------- op : Instance(BleedthroughPiecewiseOp) The operation whose parameters we're viewing. If you made the instance with :meth:`BleedthroughPLinearOp.default_view`, this is set for you already. subset : str If set, only plot this subset of the underlying data. """ # traits id = Constant("edu.mit.synbio.cytoflow.view.autofluorescencediagnosticview") friendly_id = Constant("Autofluorescence Diagnostic") subset = Str # TODO - why can't I use BleedthroughPiecewiseOp here? op = Instance(IOperation) def plot(self, experiment = None, **kwargs): """ Plot a diagnostic of the bleedthrough model computation. """ if experiment is None: raise util.CytoflowViewError('experiment', "No experiment specified") if not self.op.controls: raise util.CytoflowViewError('op', "No controls specified") if not self.op.spillover: raise util.CytoflowViewError('op', "No spillover matrix specified") kwargs.setdefault('histtype', 'stepfilled') kwargs.setdefault('alpha', 0.5) kwargs.setdefault('antialiased', True) plt.figure() # the completely arbitrary ordering of the channels channels = list(set([x for (x, _) in list(self.op.spillover.keys())])) num_channels = len(channels) for from_idx, from_channel in enumerate(channels): for to_idx, to_channel in enumerate(channels): if from_idx == to_idx: continue check_tube(self.op.controls[from_channel], experiment) tube_exp = ImportOp(tubes = [Tube(file = self.op.controls[from_channel])], channels = {experiment.metadata[c]["fcs_name"] : c for c in experiment.channels}, name_metadata = experiment.metadata['name_metadata']).apply() # apply previous operations for op in experiment.history: tube_exp = op.apply(tube_exp) # subset it if self.subset: try: tube_exp = tube_exp.query(self.subset) except Exception as e: raise util.CytoflowViewError('subset', "Subset string '{0}' isn't valid" .format(self.subset)) from e if len(tube_exp.data) == 0: raise util.CytoflowViewError('subset', "Subset string '{0}' returned no events" .format(self.subset)) tube_data = tube_exp.data # for ReadTheDocs, which doesn't have swig import sys if sys.modules['cytoflow.utility.logicle_ext.Logicle'].__name__ != 'cytoflow.utility.logicle_ext.Logicle': scale_name = 'log' else: scale_name = 'logicle' xscale = util.scale_factory(scale_name, tube_exp, channel = from_channel) yscale = util.scale_factory(scale_name, tube_exp, channel = to_channel) plt.subplot(num_channels, num_channels, from_idx + (to_idx * num_channels) + 1) plt.xscale(scale_name, **xscale.mpl_params) plt.yscale(scale_name, **yscale.mpl_params) plt.xlabel(from_channel) plt.ylabel(to_channel) plt.scatter(tube_data[from_channel], tube_data[to_channel], alpha = 0.1, s = 1, marker = 'o') xs = np.logspace(-1, math.log(tube_data[from_channel].max(), 10)) ys = xs * self.op.spillover[(from_channel, to_channel)] plt.plot(xs, ys, 'g-', lw=3) plt.tight_layout(pad = 0.8)
class VideoServer(Loggable): video = Instance(Video) port = Int(1084) quality = Int(75) _started = False use_color = True start_button = Button start_label = Property(depends_on='_started') _started = Bool(False) def _get_start_label(self): return 'Start' if not self._started else 'Stop' def _start_button_fired(self): if self._started: self.stop() else: self.start() def traits_view(self): v = View( Item('start_button', editor=ButtonEditor(label_value='start_label'))) return v def _video_default(self): return Video(swap_rb=True) def stop(self): # if self._started: self.info('stopping video server') self._stop_signal.set() self._started = False def start(self): self.info('starting video server') self._new_frame_ready = Event() self._stop_signal = Event() self.video.open(user='******') bt = Thread(name='broadcast', target=self._broadcast) bt.start() self.info('video server started') self._started = True def _broadcast(self): # new_frame = self._new_frame_ready self.info('video broadcast thread started') context = zmq.Context() # sock = context.socket(zmq.PUB) sock = context.socket(zmq.REP) sock.bind('tcp://*:{}'.format(self.port)) poll = zmq.Poller() poll.register(sock, zmq.POLLIN) self.request_reply(sock, poll) # if use_color: # kw = dict(swap_rb=True) # depth = 3 # else: # kw = dict(gray=True) # depth = 1 # pt = time.time() def request_reply(self, sock, poll): stop = self._stop_signal video = self.video fps = 10 import Image from cStringIO import StringIO quality = self.quality while not stop.isSet(): socks = dict(poll.poll(100)) if socks.get(sock) == zmq.POLLIN: resp = sock.recv() if resp == 'FPS': buf = str(fps) elif resp.startswith('QUALITY'): quality = int(resp[7:]) buf = '' else: f = video.get_frame() # new_frame.clear() im = Image.fromarray(array(f)) s = StringIO() im.save(s, 'JPEG', quality=quality) buf = s.getvalue() sock.send(buf) def publisher(self, sock): stop = self._stop_signal video = self.video use_color = self.use_color fps = 10 import Image from cStringIO import StringIO while not stop.isSet(): f = video.get_frame(gray=False) # new_frame.clear() im = Image.fromarray(array(f)) s = StringIO() im.save(s, 'JPEG') sock.send(str(fps)) sock.send(s.getvalue()) time.sleep(1.0 / fps)
class TaskWindowBackend(MTaskWindowBackend): """ The toolkit-specific implementation of a TaskWindowBackend. See the ITaskWindowBackend interface for API documentation. """ #### Private interface #################################################### _main_window_layout = Instance(MainWindowLayout) ########################################################################### # 'ITaskWindowBackend' interface. ########################################################################### def create_contents(self, parent): """ Create and return the TaskWindow's contents. """ QtGui.QApplication.instance().focusChanged.connect( self._focus_changed_signal) return QtGui.QStackedWidget(parent) def destroy(self): """ Destroy the backend. """ QtGui.QApplication.instance().focusChanged.disconnect( self._focus_changed_signal) def hide_task(self, state): """ Assuming the specified TaskState is active, hide its controls. """ # Save the task's layout in case it is shown again later. self.window._active_state.layout = self.get_layout() # Now hide its controls. self.control.centralWidget().removeWidget(state.central_pane.control) for dock_pane in state.dock_panes: # Warning: The layout behavior is subtly different (and wrong!) if # the order of these two statement is switched. dock_pane.control.hide() self.control.removeDockWidget(dock_pane.control) def show_task(self, state): """ Assumming no task is currently active, show the controls of the specified TaskState. """ # Show the central pane. self.control.centralWidget().addWidget(state.central_pane.control) # Show the dock panes. self._layout_state(state) # OSX-specific: if there is only a single tool bar, it doesn't matter if # the user can drag it around or not. Therefore, we can combine it with # the title bar, which is idiomatic on the Mac. self.control.setUnifiedTitleAndToolBarOnMac( len(state.tool_bar_managers) <= 1) #### Methods for saving and restoring the layout ########################## def get_layout(self): """ Returns a TaskLayout for the current state of the window. """ # Extract the layout from the main window. layout = TaskLayout(id=self.window._active_state.task.id) self._main_window_layout.state = self.window._active_state self._main_window_layout.get_layout(layout) # Extract the window's corner configuration. for name, corner in CORNER_MAP.iteritems(): area = INVERSE_AREA_MAP[int(self.control.corner(corner))] setattr(layout, name + '_corner', area) return layout def set_layout(self, layout): """ Applies a TaskLayout (which should be suitable for the active task) to the window. """ self.window._active_state.layout = layout self._layout_state(self.window._active_state) ########################################################################### # Private interface. ########################################################################### def _layout_state(self, state): """ Layout the dock panes in the specified TaskState using its TaskLayout. """ # Assign the window's corners to the appropriate dock areas. for name, corner in CORNER_MAP.iteritems(): area = getattr(state.layout, name + '_corner') self.control.setCorner(corner, AREA_MAP[area]) # Add all panes in the TaskLayout. self._main_window_layout.state = state self._main_window_layout.set_layout(state.layout) # Add all panes not assigned an area by the TaskLayout. for dock_pane in state.dock_panes: if dock_pane.control not in self._main_window_layout.consumed: self.control.addDockWidget(AREA_MAP[dock_pane.dock_area], dock_pane.control) # By default, these panes are not visible. However, if a pane # has been explicitly set as visible, honor that setting. if dock_pane.visible: dock_pane.control.show() #### Trait initializers ################################################### def __main_window_layout_default(self): return TaskWindowLayout(control=self.control) #### Signal handlers ###################################################### def _focus_changed_signal(self, old, new): if self.window.active_task: panes = [self.window.central_pane] + self.window.dock_panes for pane in panes: if new and pane.control.isAncestorOf(new): pane.has_focus = True elif old and pane.control.isAncestorOf(old): pane.has_focus = False
class DSCUSBUI(DSCUSB, HasTraits): """An enhanced version of :class:`~.dscusb.DSCUSB`. It defines a traits based user interface. For function definition see :class:`~.dscusb.DSCUSB` .. note:: HasTraits define set and get functions, so these are overridden by DSCUSB's set and get functions. Examples -------- For simple DSCUSB redout display do: >>> dsc = DSCUSBUI() >>> dsc.configure_traits() # doctest: +SKIP %s """ #: DSCUSB settable parameters are defined in this class settings = Instance(DSCUSBSettings, ()) #: station number STN = Range(0, 999, 1, desc='station number') #: serialUI instance serial = Instance(SerialUI, (), transient=True) _initialized = Bool(False, desc='device status') #: a string representing serial number of the device serial_number = Str(' Unknown ', desc='device serial number') #: a stringe representing firmware version of the device firmware_version = Str(' -.-- ', desc='firmware version') #: here the measured data is written for display output = Str(desc='measured output') #: here the measured temperature is written for display temp = Str('', desc='measured temperature') #: defines output mode SYS: Force [mN], MVV: Strain [mV/V], CELL: Cell output output_mode = Trait('Force [mN]', { 'Force [mN]': 'SYS', 'Strain [mV/V]': 'MVV', 'Cell output': 'CELL' }, desc='different output modes. ') #: STAT flag is written here, for status messages stat = Int(desc='STAT flags.') #: status messages are written here status_message = Str #: a bool that determines if there is a problem with output data output_alarm = Bool(False) #: a bool that determines if there is a problem with temperature temp_alarm = Bool(False) timer = Any #: interval for data readout interval = Range(low=READOUT_INTERVAL_MIN, high=READOUT_INTERVAL_MAX, value=READOUT_INTERVAL, desc='data acquisition interval') timer_fast = Any readout = List([]) offset = Float(desc='force measurement offset') port_button = Button('Port', desc='port settings') settings_button = Button('Settings', desc='settings') init_button = Button('On/Off', desc='initialize action') set_offset_button = Button('Set', desc='set offset action') # zero_button = Button('Set zero', desc = 'set force to zero') view = dscusbui_view def _serial_default(self): return DSCSerial(timeout=0.06, baudrate=BAUDRATE_DEFAULT) def __initialized_changed(self, value): if value == True: self.serial_number, self.firmware_version = self.get_info() self._stat_changed(self.stat) #update message self.start_readout(self.interval) else: self.stop_readout() self._reset_output_data() self.status_message = '' def _interval_changed(self, value): if self._initialized: self.stop_readout() self.start_readout(value) def _timer_function(self): try: self.stat = self.get_stat() if self.readout != []: value, self.readout = np.median(self.readout), [] self.output = '%.4f' % value temp = self.get_temp() self.temp = '%.2f' % temp return value, temp except: self.close() raise def _timer_fast_function(self): try: value = self.get(self.output_mode_) if self.output_mode_ == 'SYS': value -= self.offset self.readout.append(value) except: self.close() raise def start_readout(self, interval=1.): """Starts readout process. GUI must be running for this to work... """ try: self.timer.Start(interval * 1000) self.timer_fast.Start(self._interval_min * 1000) except AttributeError: #self._timer_function() self.timer = Timer(interval * 1000, self._timer_function) self.timer_fast = Timer(self._interval_min * 1000, self._timer_fast_function) def stop_readout(self): """Stops readout process """ self.timer.Stop() self.timer_fast.Stop() def _reset_output_data(self): self.status_message = '' self.output = '' self.temp = '' self.serial_number, self.firmware_version = ('Unknown', '-.--') def _stat_changed(self, stat): stat = stat_to_dict(stat) self.output_alarm = stat['ECOMUR'] or stat['ECOMOR'] or \ stat['SYSUR'] or stat['SYSOR'] or \ stat['CRAWUR'] or stat['CRAWOR'] or \ stat['SCALON'] or stat['LCINTEG'] self.temp_alarm = stat['TEMPUR'] or stat['TEMPOR'] self.status_message = get_status_message(stat) @display_on_exception(logger, 'Could not initialize') def _init_button_fired(self): if self._initialized: self.close() else: self.init() @display_on_exception(logger, 'Could not change settings') def _settings_button_fired(self): self.stop_readout() d = self.settings.get() d.pop('_poweruser') for key in d: value = self.get(key) setattr(self.settings, key, value) d[key] = getattr(self.settings, key) self.settings.edit_traits(kind='modal') reset = False STN = self.STN for key in d: value = getattr(self.settings, key) if d[key] != value: if key == 'BAUD': self.set_baudrate(BAUDRATES[baudrate]) else: self.set(key, value) if key in RESET_SETTINGS: reset = True if key == 'STN': if STN != value: STN = value if reset == True: self._initialized = False self.reset() self.STN = STN self.start_readout(self.interval) @display_on_exception(logger, 'Could not change port settings') def _port_button_fired(self): self.close() self.serial.edit_traits(kind='livemodal') @display_on_exception(logger, 'Could not change offset') def _set_offset_button_fired(self): self.calibrate()
class FMPModel(BaseModule): """基于特征因子模拟组合的绩效分析模型""" #Portfolio = Enum(None, arg_type="SingleOption", label="策略组合", order=0) #BenchmarkPortfolio = Enum("无", arg_type="SingleOption", label="基准组合", order=1) AttributeFactors = ListStr(arg_type="MultiOption", label="特征因子", order=2, option_range=()) #IndustryFactor = Enum("无", arg_type="SingleOption", label="行业因子", order=3) #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=4) RiskTable = Instance(RiskTable, arg_type="RiskTable", label="风险表", order=5) CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=6) def __init__(self, factor_table, name="因子模拟组合绩效分析模型", sys_args={}, **kwargs): self._FactorTable = factor_table return super().__init__(name=name, sys_args=sys_args, config_file=None, **kwargs) def __QS_initArgs__(self): DefaultNumFactorList, DefaultStrFactorList = getFactorList( dict(self._FactorTable.getFactorMetaData(key="DataType"))) self.add_trait( "Portfolio", Enum(*DefaultNumFactorList, arg_type="SingleOption", label="策略组合", order=0)) self.add_trait( "BenchmarkPortfolio", Enum(*(["无"] + DefaultNumFactorList), arg_type="SingleOption", label="基准组合", order=1)) self.add_trait( "AttributeFactors", ListStr(arg_type="MultiOption", label="特征因子", order=2, option_range=tuple(DefaultNumFactorList))) self.AttributeFactors.append(DefaultNumFactorList[-1]) self.add_trait( "IndustryFactor", Enum(*(["无"] + DefaultStrFactorList), arg_type="SingleOption", label="行业因子", order=3)) self.add_trait( "PriceFactor", Enum(*DefaultNumFactorList, arg_type="SingleOption", label="价格因子", order=4)) self.PriceFactor = searchNameInStrList(DefaultNumFactorList, ['价', 'Price', 'price']) def _normalizePortfolio(self, portfolio): NegMask = (portfolio < 0) TotalNegWeight = portfolio[NegMask].sum() if TotalNegWeight != 0: portfolio[NegMask] = portfolio[NegMask] / TotalNegWeight PosMask = (portfolio > 0) TotalPosWeight = portfolio[PosMask].sum() if TotalPosWeight != 0: portfolio[PosMask] = portfolio[PosMask] / TotalPosWeight portfolio[ NegMask] = portfolio[NegMask] * TotalNegWeight / TotalPosWeight return portfolio def __QS_start__(self, mdl, dts, **kwargs): if self._isStarted: return () super().__QS_start__(mdl=mdl, dts=dts, **kwargs) self.RiskTable.start(dts=dts) self._Output = {} self._Output["因子暴露"] = pd.DataFrame(columns=self.AttributeFactors) self._Output["风险调整的因子暴露"] = pd.DataFrame(columns=self.AttributeFactors) self._Output["风险贡献"] = pd.DataFrame(columns=self.AttributeFactors + ["Alpha"]) self._Output["收益贡献"] = pd.DataFrame(columns=self.AttributeFactors + ["Alpha"]) self._Output["因子收益"] = pd.DataFrame(columns=self.AttributeFactors) self._CurCalcInd = 0 self._IDs = self._FactorTable.getID() return (self._FactorTable, ) def __QS_move__(self, idt, **kwargs): if self._iDT == idt: return 0 PreDT = None if self.CalcDTs: if idt not in self.CalcDTs[self._CurCalcInd:]: return 0 self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index( idt) + self._CurCalcInd if self._CurCalcInd > 0: PreDT = self.CalcDTs[self._CurCalcInd - 1] else: self._CurCalcInd = self._Model.DateTimeIndex if self._CurCalcInd > 0: PreDT = self._Model.DateTimeSeries[self._CurCalcInd - 1] if PreDT is None: return 0 Portfolio = self._FactorTable.readData(factor_names=[self.Portfolio], dts=[PreDT], ids=self._IDs).iloc[0, 0] Portfolio = self._normalizePortfolio(Portfolio[pd.notnull(Portfolio) & (Portfolio != 0)]) if self.BenchmarkPortfolio != "无": BenchmarkPortfolio = self._FactorTable.readData( factor_names=[self.BenchmarkPortfolio], dts=[PreDT], ids=self._IDs).iloc[0, 0] BenchmarkPortfolio = self._normalizePortfolio( BenchmarkPortfolio[pd.notnull(BenchmarkPortfolio) & (BenchmarkPortfolio != 0)]) IDs = Portfolio.index.union(BenchmarkPortfolio.index) if Portfolio.shape[0] > 0: Portfolio = Portfolio.loc[IDs] Portfolio.fillna(0.0, inplace=True) else: Portfolio = pd.Series(0.0, index=IDs) if BenchmarkPortfolio.shape[0] > 0: BenchmarkPortfolio = BenchmarkPortfolio.loc[IDs] BenchmarkPortfolio.fillna(0.0, inplace=True) else: BenchmarkPortfolio = pd.Series(0.0, index=IDs) Portfolio = Portfolio - BenchmarkPortfolio # 计算因子模拟组合 self.RiskTable.move(PreDT, **kwargs) CovMatrix = dropRiskMatrixNA( self.RiskTable.readCov(dts=[PreDT], ids=Portfolio.index.tolist()).iloc[0]) FactorExpose = self._FactorTable.readData( factor_names=list(self.AttributeFactors), ids=IDs, dts=[PreDT]).iloc[:, 0].dropna(axis=0) IDs = FactorExpose.index.intersection(CovMatrix.index).tolist() CovMatrix, FactorExpose = CovMatrix.loc[IDs, IDs], FactorExpose.loc[IDs, :] if self.IndustryFactor != "无": IndustryData = self._FactorTable.readData( factor_names=[self.IndustryFactor], ids=IDs, dts=[PreDT]).iloc[0, 0, :] DummyData = DummyVarTo01Var(IndustryData, ignore_nonstring=True) FactorExpose = pd.merge(FactorExpose, DummyData, left_index=True, right_index=True) CovMatrixInv = np.linalg.inv(CovMatrix.values) FMPHolding = np.dot( np.dot( np.linalg.inv( np.dot(np.dot(FactorExpose.values.T, CovMatrixInv), FactorExpose.values)), FactorExpose.values.T), CovMatrixInv) # 计算持仓对因子模拟组合的投资组合 Portfolio = self._normalizePortfolio(Portfolio.loc[IDs]) Beta = np.dot( np.dot( np.dot( np.linalg.inv( np.dot(np.dot(FMPHolding, CovMatrix.values), FMPHolding.T)), FMPHolding), CovMatrix.values), Portfolio.values) Price = self._FactorTable.readData(factor_names=[self.PriceFactor], dts=[PreDT, idt], ids=IDs).iloc[0] Return = Price.iloc[1] / Price.iloc[0] - 1 # 计算各统计指标 if FactorExpose.shape[1] > self._Output["因子暴露"].shape[1]: FactorNames = FactorExpose.columns.tolist() self._Output["因子暴露"] = self._Output["因子暴露"].loc[:, FactorNames] self._Output["风险调整的因子暴露"] = self._Output[ "风险调整的因子暴露"].loc[:, FactorNames] self._Output["风险贡献"] = self._Output["风险贡献"].loc[:, FactorNames + ["Alpha"]] self._Output["收益贡献"] = self._Output["收益贡献"].loc[:, FactorNames + ["Alpha"]] self._Output["因子收益"] = self._Output["因子收益"].loc[:, FactorNames] self._Output["因子暴露"].loc[PreDT, FactorExpose.columns] = Beta self._Output["风险调整的因子暴露"].loc[PreDT, FactorExpose.columns] = np.sqrt( np.diag(np.dot(np.dot(FMPHolding, CovMatrix.values), FMPHolding.T))) * Beta RiskContribution = np.dot(np.dot( FMPHolding, CovMatrix.values), Portfolio.values) / np.sqrt( np.dot(np.dot(Portfolio.values, CovMatrix.values), Portfolio.values)) * Beta self._Output["风险贡献"].loc[idt, FactorExpose.columns] = RiskContribution self._Output["风险贡献"].loc[idt, "Alpha"] = np.sqrt( np.dot(np.dot(Portfolio.values, CovMatrix), Portfolio.values)) - np.nansum(RiskContribution) self._Output["因子收益"].loc[idt, FactorExpose.columns] = np.nansum( Return.values * FMPHolding, axis=1) self._Output["收益贡献"].loc[idt, FactorExpose.columns] = self._Output[ "因子收益"].loc[idt, FactorExpose.columns] * self._Output["因子暴露"].loc[ PreDT, FactorExpose.columns] self._Output["收益贡献"].loc[idt, "Alpha"] = (Portfolio * Return).sum( ) - self._Output["收益贡献"].loc[idt, FactorExpose.columns].sum() return 0 def __QS_end__(self): if not self._isStarted: return 0 self.RiskTable.end() self._Output["风险贡献占比"] = self._Output["风险贡献"].divide( self._Output["风险贡献"].sum(axis=1), axis=0) self._Output["历史均值"] = pd.DataFrame( columns=["因子暴露", "风险调整的因子暴露", "风险贡献", "风险贡献占比", "收益贡献"], index=self._Output["收益贡献"].columns) self._Output["历史均值"]["因子暴露"] = self._Output["因子暴露"].mean(axis=0) self._Output["历史均值"]["风险调整的因子暴露"] = self._Output["风险调整的因子暴露"].mean( axis=0) self._Output["历史均值"]["风险贡献"] = self._Output["风险贡献"].mean(axis=0) self._Output["历史均值"]["风险贡献占比"] = self._Output["风险贡献占比"].mean(axis=0) self._Output["历史均值"]["收益贡献"] = self._Output["收益贡献"].mean(axis=0) self._IDs = None return 0 def genMatplotlibFig(self, file_path=None): nRow, nCol = 2, 3 Fig = plt.figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow)) AxesGrid = gridspec.GridSpec(nRow, nCol) xData = np.arange(1, self._Output["历史均值"].shape[0]) xTickLabels = [str(iInd) for iInd in self._Output["历史均值"].index] PercentageFormatter = FuncFormatter(_QS_formatMatplotlibPercentage) FloatFormatter = FuncFormatter(lambda x, pos: '%.2f' % (x, )) _QS_plotStatistics(plt.subplot(AxesGrid[0, 0]), xData[:-1], xTickLabels[:-1], self._Output["历史均值"]["因子暴露"].iloc[:-1], FloatFormatter) _QS_plotStatistics(plt.subplot(AxesGrid[0, 1]), xData[:-1], xTickLabels[:-1], self._Output["历史均值"]["风险调整的因子暴露"].iloc[:-1], FloatFormatter) _QS_plotStatistics(plt.subplot(AxesGrid[0, 2]), xData[:-1], xTickLabels[:-1], self._Output["历史均值"]["因子收益"].iloc[:-1], PercentageFormatter) _QS_plotStatistics(plt.subplot(AxesGrid[1, 0]), xData, xTickLabels, self._Output["历史均值"]["收益贡献"], PercentageFormatter) _QS_plotStatistics(plt.subplot(AxesGrid[1, 1]), xData, xTickLabels, self._Output["历史均值"]["风险贡献"], FloatFormatter) _QS_plotStatistics(plt.subplot(AxesGrid[1, 2]), xData, xTickLabels, self._Output["历史均值"]["风险贡献占比"], PercentageFormatter) if file_path is not None: Fig.savefig(file_path, dpi=150, bbox_inches='tight') return Fig def _repr_html_(self): if len(self.ArgNames) > 0: HTML = "参数设置: " HTML += '<ul align="left">' for iArgName in self.ArgNames: if iArgName == "风险表": HTML += "<li>" + iArgName + ": " + self.RiskTable.Name + "</li>" elif iArgName != "计算时点": HTML += "<li>" + iArgName + ": " + str( self.Args[iArgName]) + "</li>" elif self.Args[iArgName]: HTML += "<li>" + iArgName + ": 自定义时点</li>" else: HTML += "<li>" + iArgName + ": 所有时点</li>" HTML += "</ul>" else: HTML = "" Formatters = [lambda x: '{0:.4f}'.format(x)] * 2 + [ _QS_formatPandasPercentage ] * 2 + [lambda x: '{0:.4f}'.format(x), _QS_formatPandasPercentage] iHTML = self._Output["历史均值"].to_html(formatters=Formatters) Pos = iHTML.find(">") HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:] Fig = self.genMatplotlibFig() # figure 保存为二进制文件 Buffer = BytesIO() plt.savefig(Buffer, bbox_inches='tight') PlotData = Buffer.getvalue() # 图像数据转化为 HTML 格式 ImgStr = "data:image/png;base64," + base64.b64encode(PlotData).decode() HTML += ('<img src="%s">' % ImgStr) return HTML
class VarTree(Variable): """ A Variable for a :class:`VariableTree` of a particular type. """ def __init__(self, default_value, allow_none=True, **metadata): from openmdao.main.vartree import VariableTree # Break import loop on VariableTree if isinstance(default_value, VariableTree): klass = default_value.__class__ if 'iotype' in metadata: default_value._iotype = metadata['iotype'] else: metadata['iotype'] = default_value.iotype else: raise TypeError('default_value must be an instance of VariableTree' ' or subclass') metadata.setdefault('copy', 'deep') self._allow_none = allow_none self.klass = klass self._instance = Instance(klass=klass, allow_none=False, factory=None, args=None, kw=None, **metadata) self._instance.default_value = default_value super(VarTree, self).__init__(default_value, **metadata) def validate(self, obj, name, value): """ Validates that a specified value is valid for this trait. """ if value is None: if self._allow_none: return value self.validate_failed(obj, name, value) try: value = self._instance.validate(obj, name, value) except Exception: obj.raise_exception('%r must be an instance of %s.%s' % (name, self._instance.klass.__module__, self._instance.klass.__name__), TypeError) return value def post_setattr(self, obj, name, value): """ VariableTrees must know their place within the hierarchy, so set their parent here. This keeps side effects out of validate(). """ if value.parent is not obj: value.parent = obj value._iotype = self.iotype def get_attribute(self, name, value, trait, meta): """Return the attribute dictionary for this variable. This dict is used by the GUI to populate the edit UI. Slots also return an attribute dictionary for the slot pane. name: str Name of variable value: object The value of the variable trait: CTrait The variable's trait meta: dict Dictionary of metadata for this variable """ io_attr = {} io_attr['name'] = name io_attr['type'] = trait.trait_type.klass.__name__ io_attr['ttype'] = 'vartree' for field in meta: if field not in gui_excludes: io_attr[field] = meta[field] return io_attr, None
class WXWindow(TreeNodeObject): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- window = Instance(wx.Window) name = Property position = Property size = Property sizer = Property min_size = Property best_size = Property items = Property(List) result = Property(Code) evaluate = Str #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View(VSplit(VGroup('position~', 'size~', 'sizer~', 'min_size~', 'best_size~', label='Information', dock='horizontal'), Item('items', label='Sizer Children', show_label=False, editor=sizer_item_table_editor, dock='horizontal'), VGroup('evaluate', 'result#~', label='Evaluate', dock='horizontal'), id='splitter'), id='etsdevtools.developer.tools.ui_debugger.item', kind='subpanel') #--------------------------------------------------------------------------- # Handles 'evaluate' being changed: #--------------------------------------------------------------------------- def _evaluate_changed(self): self.trait_property_changed('result', None, None) #--------------------------------------------------------------------------- # Implementation of the various trait properties: #--------------------------------------------------------------------------- def _get_name(self): w = self.window try: label = w.GetLabel() except: try: label = w.GetValue() except: try: label = w.GetTitle() except: label = '' if label != '': label = ' (%s)' % label return self.window.__class__.__name__ + label def _get_size(self): dx, dy = self.window.GetSizeTuple() return str((dx, dy)) def _get_position(self): x, y = self.window.GetPositionTuple() return str((x, y)) def _get_sizer(self): sizer = self.window.GetSizer() if sizer is None: return '' dx, dy = sizer.CalcMin() return '%s( %d, %d )' % (sizer.__class__.__name__, dx, dy) def _get_min_size(self): dx, dy = self.window.GetMinSize() return str((dx, dy)) def _get_best_size(self): dx, dy = self.window.GetEffectiveMinSize() return str((dx, dy)) def _get_result(self): try: result = eval(self.evaluate, { '_': self.window, '__': self.window.GetSizer() }) if isinstance(result, (list, tuple)): return '\n'.join( ['[%d]: %s' % (i, str(x)) for i, x in enumerate(result)]) return str(result) except: return '???' def _get_items(self): sizer = self.window.GetSizer() if sizer is None: return [] items = [] try: for i in range(10000): item = sizer.GetItem(i) if item is None: break items.append(WXSizerItem(item=item)) except: pass return items #--------------------------------------------------------------------------- # Returns whether chidren of this object are allowed or not: #--------------------------------------------------------------------------- def tno_allows_children(self, node): """ Returns whether chidren of this object are allowed or not. """ return True #--------------------------------------------------------------------------- # Returns whether or not the object has children: #--------------------------------------------------------------------------- def tno_has_children(self, node=None): """ Returns whether or not the object has children. """ return (len(self.window.GetChildren()) > 0) #--------------------------------------------------------------------------- # Gets the object's children: #--------------------------------------------------------------------------- def tno_get_children(self, node): """ Gets the object's children. """ return [ WXWindow(window=window) for window in self.window.GetChildren() ] #--------------------------------------------------------------------------- # Returns the 'draggable' version of a specified object: #--------------------------------------------------------------------------- def tno_get_drag_object(self, node): """ Returns the 'draggable' version of a specified object. """ return self.window
class Graph(Viewable, ContextMenuMixin): """ """ name = Str plotcontainer = Instance(BasePlotContainer) container_dict = Dict plots = List(Plot) selected_plotid = Property(depends_on='selected_plot') selected_plot = Instance(Plot) window_title = '' window_width = 600 window_height = 500 window_x = 500 window_y = 250 width = 300 height = 300 resizable = True line_inspectors_write_metadata = False _title = Str _title_font = None _title_size = None _convert_index = None status_text = Str view_identifier = None close_func = Callable x_limits_changed = Event def __init__(self, *args, **kw): """ """ super(Graph, self).__init__(*args, **kw) self.clear() pc = self.plotcontainer if self.use_context_menu: menu = ContextualMenuTool(parent=self, component=pc) pc.tools.append(menu) def set_time_xaxis(self, plotid=None): if plotid is None: plotid = len(self.plots) - 1 p = self.plots[plotid] p.x_axis.tick_generator = ScalesTickGenerator( scale=CalendarScaleSystem()) def add_point_inspector(self, scatter, convert_index=None): point_inspector = PointInspector(scatter, convert_index=convert_index) pinspector_overlay = PointInspectorOverlay(component=scatter, tool=point_inspector) # scatter.overlays.append(pinspector_overlay) scatter.tools.append(point_inspector) def closed(self, *args): if self.close_func: self.close_func() def update_group_attribute(self, plot, attr, value, dataid=0): pass def get_plotid_by_ytitle(self, *args, **kw): plot = self.get_plot_by_ytitle(*args, **kw) if plot is not None: return self.plots.index(plot) def get_plot_by_ytitle(self, txt, startswith=False): """ iso: str return None or Plot with y_axis.title equal to iso if startswith is True title only has to start with iso """ if startswith: is_equal = lambda x: x.startswith(txt) else: is_equal = lambda x: x.__eq__(txt) plot = None for po in self.plots: if is_equal(po.y_axis.title): plot = po break return plot def get_num_plots(self): """ """ return len(self.plots) def get_num_series(self, plotid): """ """ return len(self.series[plotid]) def get_data(self, plotid=0, series=0, axis=0): """ """ s = self.series[plotid][series] p = self.plots[plotid] return p.data.get_data(s[axis]) def get_aux_data(self, plotid=0, series=1): plot = self.plots[plotid] si = plot.plots['aux{:03d}'.format(series)][0] oi = si.index.get_data() ov = si.value.get_data() return oi, ov def save_png(self, path=None): """ """ self._save_(type_='pic', path=path) def save_pdf(self, path=None): """ """ self._save_(type_='pdf', path=path) def save(self, path=None): """ """ self._save_(path=path) # def export_raw_data(self, path=None, header=None, plotid=0): # """ # """ # if path is None: # path = self._path_factory() # if path is not None: # self._export_raw_data(path, header, plotid) def export_data(self, path=None, plotid=None): """ """ if path is None: path = self._path_factory() if path is not None: path = add_extension(path, '.csv') self._export_data(path, plotid) def read_xy(self, p, header=False, series=0, plotid=0): """ """ x = [] y = [] with open(p, 'r') as f: reader = csv.reader(f) if header: reader.next() for line in reader: if line[0].startswith('#'): continue if len(line) == 2: x.append(float(line[0])) y.append(float(line[1])) self.set_data(x, plotid, series) self.set_data(y, plotid, series, axis=1) # def close(self): # ''' # close the window # ''' # if self.ui is not None: # do_after_timer(1, self.ui.dispose) # self.ui = None def remove_rulers(self, plotid=0): plot = self.plots[plotid] for o in plot.overlays: if isinstance(o, GuideOverlay): plot.overlays.remove(o) def clear_plots(self): x = range(len(self.plots)) self.xdataname_generators = [ self._name_generator_factory('x') for _ in x ] self.ydataname_generators = [ self._name_generator_factory('y') for _ in x ] self.yerdataname_generators = [ self._name_generator_factory('yer') for _ in x ] self.color_generators = [color_generator() for _ in x] # for po in x: self.series = [[] for _ in x] self.data_len = [[] for _ in x] self.data_limits = [[] for _ in x] # print '====== {}'.format(self) # print 'len plots {}'.format(len(self.plots)) for pi in self.plots: # print 'len pi.renderers {}'.format(len(pi.plots.keys())) for k, pp in pi.plots.items(): for renderer in pp: try: pi.remove(renderer) except RuntimeError: print 'failed removing {}'.format(renderer) pi.plots.pop(k) # print 'len psss.renderers {}'.format(len(pi.plots.keys())) self.clear_data() def clear(self, clear_container=True): """ """ self.clear_plots() self.plots = [] self.xdataname_generators = [self._name_generator_factory('x')] self.ydataname_generators = [self._name_generator_factory('y')] self.yerdataname_generators = [self._name_generator_factory('yer')] self.color_generators = [color_generator()] self.series = [] self.data_len = [] self.data_limits = [] if clear_container: self.plotcontainer = pc = self.container_factory() if self.use_context_menu: menu = ContextualMenuTool(parent=self, component=pc) pc.tools.append(menu) self.selected_plot = None def set_axis_label_color(self, *args, **kw): """ """ kw['attr'] = 'title' self._set_axis_color(*args, **kw) def set_axis_tick_color(self, *args, **kw): """ """ attrs = ['tick_label', 'tick'] if 'attrs' in kw: attrs = kw['attrs'] for a in attrs: kw['attr'] = a self._set_axis_color(*args, **kw) def set_aux_data(self, x, y, plotid=0, series=1): p = self.plots[plotid].plots['aux{:03d}'.format(series)][0] p.index.set_data(x) p.value.set_data(y) def clear_data(self, plotid=None, **kw): if plotid is None: for i, p in enumerate(self.plots): for s in self.series[i]: for k in s: p.data.set_data(k, []) else: self.set_data([], **kw) def set_data(self, d, plotid=0, series=0, axis=0): """ """ if isinstance(series, int): n = self.series[plotid][series] series = n[axis] self.plots[plotid].data.set_data(series, d) def set_axis_traits(self, plotid=0, axis='x', **kw): """ """ plot = self.plots[plotid] attr = getattr(plot, '{}_axis'.format(axis)) attr.trait_set(**kw) def set_grid_traits(self, plotid=0, grid='x', **kw): """ """ plot = self.plots[plotid] attr = getattr(plot, '{}_grid'.format(grid)) attr.trait_set(**kw) def set_series_traits(self, d, plotid=0, series=0): """ """ plot = self.plots[plotid].plots['plot%i' % series][0] plot.trait_set(**d) self.plotcontainer.request_redraw() def get_series_color(self, plotid=0, series=0): if isinstance(series, int): series = 'plot{:03d}'.format(series) p = self.plots[plotid].plots[series][0] return p.color # def set_series_type(self, t, plotid = 0, series = 0): # ''' # ''' # p = self.plots[plotid] # s = 'plot%i' % series # # ss = p.plots[s][0] # x = ss.index.get_data() # y = ss.value.get_data() # # p.delplot(s) # # series,plot=self.new_series(x = x, y = y, color = ss.color, plotid = plotid, type = t, add = True) # self.plotcontainer.invalidate_and_redraw() # return series # #self.refresh_editor() def get_series_label(self, plotid=0, series=0): """ """ r = '' legend = self.plots[plotid].legend if isinstance(series, str): if series in legend.labels: return series return try: r = legend.labels[series] except IndexError: pass return r def set_series_label(self, label, plotid=0, series=None): """ A chaco update requires that the legends labels match the keys in the plot dict save the plots from the dict resave them with label as the key """ legend = self.plots[plotid].legend if series is None: n = len(self.plots[plotid].plots.keys()) series = n - 1 if isinstance(series, int): series = 'plot{}'.format(series) try: legend.labels[series] = label except Exception, e: legend.labels.append(label) try: plots = self.plots[plotid].plots[series] except KeyError: print self.plots[plotid].plots.keys() raise self.plots[plotid].plots[label] = plots self.plots[plotid].plots.pop(series)
class Tree(Widget): """ A tree control with a model/ui architecture. """ # The default tree style. STYLE = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN #### 'Tree' interface ##################################################### # The tree's filters (empty if no filtering is required). filters = List(Filter) # Mode for lines connecting tree nodes which emphasize hierarchy: # 'appearance' - only on when lines look good, # 'on' - always on, 'off' - always off # NOTE: on and off are ignored in favor of show_lines for now lines_mode = Enum('appearance', 'on', 'off') # The model that provides the data for the tree. model = Instance(TreeModel, ()) # The root of the tree (this is for convenience, it just delegates to # the tree's model). root = Property(Any) # The objects currently selected in the tree. selection = List # Selection mode. selection_mode = Enum('single', 'extended') # Should an image be shown for each node? show_images = Bool(True) # Should lines be drawn between levels in the tree. show_lines = Bool(True) # Should the root of the tree be shown? show_root = Bool(True) # The tree's sorter (None if no sorting is required). sorter = Instance(Sorter) #### Events #### # A right-click occurred on the control (not a node!). control_right_clicked = Event #(Point) # A key was pressed while the tree has focus. key_pressed = Event(KeyPressedEvent) # A node has been activated (ie. double-clicked). node_activated = Event #(Any) # A drag operation was started on a node. node_begin_drag = Event #(Any) # A (non-leaf) node has been collapsed. node_collapsed = Event #(Any) # A (non-leaf) node has been expanded. node_expanded = Event #(Any) # A left-click occurred on a node. # # Tuple(node, point). node_left_clicked = Event #(Tuple) # A right-click occurred on a node. # # Tuple(node, point) node_right_clicked = Event #(Tuple) #### Private interface #################################################### # A name to distinguish the tree for debugging! # # fixme: This turns out to be kinda useful... Should 'Widget' have a name # trait? _name = Str('Anonymous tree') # An optional callback to detect the end of a label edit. This is # useful because the callback will be invoked even if the node label was # not actually changed. _label_edit_callback = Trait(None, Callable, None) # Flag for allowing selection events to be ignored _ignore_selection_events = Bool(False) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, image_size=(16, 16), **traits): """ Creates a new tree. 'parent' is the toolkit-specific control that is the tree's parent. 'image_size' is a tuple in the form (int width, int height) that specifies the size of the images (if required) displayed in the tree. """ # Base class constructors. super(Tree, self).__init__(**traits) # Get our wx Id. wxid = wx.NewId() # Create the toolkit-specific control. self.control = tree = _Tree(self, parent, wxid, style=self._get_style()) # Wire up the wx tree events. tree.Bind(wx.EVT_CHAR, self._on_char) tree.Bind(wx.EVT_LEFT_DOWN, self._on_left_down) # fixme: This is not technically correct as context menus etc should # appear on a right up (or right click). Unfortunately, if we # change this to 'EVT_RIGHT_UP' wx does not fire the event unless the # right mouse button is double clicked 8^() Sad, but true! tree.Bind(wx.EVT_RIGHT_DOWN, self._on_right_down) # fixme: This is not technically correct as we would really like to use # 'EVT_TREE_ITEM_ACTIVATED'. Unfortunately, (in 2.6 at least), it # throws an exception when the 'Enter' key is pressed as the wx tree # item Id in the event seems to be invalid. It also seems to cause # any child frames that my be created in response to the event to # appear *behind* the parent window, which is, errrr, not great ;^) tree.Bind(wx.EVT_LEFT_DCLICK, self._on_tree_item_activated) #wx.EVT_TREE_ITEM_ACTIVATED(tree, wxid, self._on_tree_item_activated) tree.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self._on_tree_item_collapsing) tree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self._on_tree_item_collapsed) tree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding) tree.Bind(wx.EVT_TREE_ITEM_EXPANDED, self._on_tree_item_expanded) tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self._on_tree_begin_label_edit) tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self._on_tree_end_label_edit) tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self._on_tree_begin_drag) tree.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_sel_changed) tree.Bind(wx.EVT_TREE_DELETE_ITEM, self._on_tree_delete_item) # Enable the tree as a drag and drop target. self.control.SetDropTarget(PythonDropTarget(self)) # The image list is a wxPython-ism that caches all images used in the # control. self._image_list = ImageList(image_size[0], image_size[1]) if self.show_images: tree.AssignImageList(self._image_list) # Mapping from node to wx tree item Ids. self._node_to_id_map = {} # Add the root node. if self.root is not None: self._add_root_node(self.root) # Listen for changes to the model. self._add_model_listeners(self.model) return ########################################################################### # 'Tree' interface. ########################################################################### #### Properties ########################################################### def _get_root(self): """ Returns the root node of the tree. """ return self.model.root def _set_root(self, root): """ Sets the root node of the tree. """ self.model.root = root return #### Methods ############################################################## def collapse(self, node): """ Collapses the specified node. """ wxid = self._get_wxid(node) if wxid is not None: self.control.Collapse(wxid) return def edit_label(self, node, callback=None): """ Edits the label of the specified node. If a callback is specified it will be called when the label edit completes WHETHER OR NOT the label was actually changed. The callback must take exactly 3 arguments:- (tree, node, label) """ wxid = self._get_wxid(node) if wxid is not None: self._label_edit_callback = callback self.control.EditLabel(wxid) return def expand(self, node): """ Expands the specified node. """ wxid = self._get_wxid(node) if wxid is not None: self.control.Expand(wxid) return def expand_all(self): """ Expands every node in the tree. """ if self.show_root: self._expand_item(self._get_wxid(self.root)) else: for child in self._get_children(self.root): self._expand_item(self._get_wxid(child)) return def get_parent(self, node): """ Returns the parent of a node. This will only work iff the node has been displayed in the tree. If it hasn't then None is returned. """ # Has the node actually appeared in the tree yet? wxid = self._get_wxid(node) if wxid is not None: pid = self.control.GetItemParent(wxid) # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. populated, parent = self.control.GetItemData(pid) else: parent = None return parent def is_expanded(self, node): """ Returns True if the node is expanded, otherwise False. """ wxid = self._get_wxid(node) if wxid is not None: # If the root node is hidden then it is always expanded! if node is self.root and not self.show_root: is_expanded = True else: is_expanded = self.control.IsExpanded(wxid) else: is_expanded = False return is_expanded def is_selected(self, node): """ Returns True if the node is selected, otherwise False. """ wxid = self._get_wxid(node) if wxid is not None: is_selected = self.control.IsSelected(wxid) else: is_selected = False return is_selected def refresh(self, node): """ Refresh the tree starting from the specified node. Call this when the structure of the content has changed DRAMATICALLY. """ # Has the node actually appeared in the tree yet? pid = self._get_wxid(node) if pid is not None: # Delete all of the node's children and re-add them. self.control.DeleteChildren(pid) self.control.SetItemData(pid, (False, node)) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(pid, has_children) # fixme: At least on Windows, wx does not fire an expanding # event for a hidden root node, so we have to populate the node # manually. if node is self.root and not self.show_root: # Add the child nodes. for child in self._get_children(node): self._add_node(pid, child) else: # Expand it. if self.control.IsExpanded(pid): self.control.Collapse(pid) self.control.Expand(pid) return def select(self, node): """ Selects the specified node. """ wxid = self._get_wxid(node) if wxid is not None: self.control.SelectItem(wxid) return def set_selection(self, list): """ Selects the specified list of nodes. """ logger.debug('Setting selection to [%s] within Tree [%s]', list, self) # Update the control to reflect the target list by unselecting # everything and then selecting each item in the list. During this # process, we want to avoid changing our own selection. self._ignore_selection_events = True self.control.UnselectAll() for node in list: try: self.select(node) except: logger.exception('Unable to select node [%s]', node) self._ignore_selection_events = False # Update our selection to reflect the final selection state. self.selection = self._get_selection() ########################################################################### # 'PythonDropTarget' interface. ########################################################################### def on_drag_over(self, x, y, obj, default_drag_result): """ Called when a node is dragged over the tree. """ result = wx.DragNone # Find the node that we are dragging over... node = self._get_drag_drop_node(x, y) if node is not None: # Ask the model if the node allows the object to be dropped onto # it. if self.model.can_drop(node, obj): result = default_drag_result return result def on_drop(self, x, y, obj, default_drag_result): """ Called when a node is dropped on the tree. """ # Find the node that we are dragging over... node = self._get_drag_drop_node(x, y) if node is not None: self.model.drop(node, obj) return default_drag_result ########################################################################### # Private interface. ########################################################################### def _get_wxid(self, node): """ Returns the wxid for the specified node. Returns None if the node has not yet appeared in the tree. """ # The model must generate a unique key for each node (unique within the # model). key = self.model.get_key(node) return self._node_to_id_map.get(key, None) def _set_wxid(self, node, wxid): """ Sets the wxid for the specified node. """ # The model must generate a unique key for each node (unique within the # model). key = self.model.get_key(node) self._node_to_id_map[key] = wxid return def _remove_wxid(self, node): """ Removes the wxid for the specified node. """ # The model must generate a unique key for each node (unique within the # model). key = self.model.get_key(node) try: del self._node_to_id_map[key] except KeyError: # fixme: No, really, this is a serious one... How do we get in this # situation. It came up when using the canvas stuff... logger.warn('removing node: %s' % str(node)) return def _get_style(self): """ Returns the wx style flags for creating the tree control. """ # Start with the default flags. style = self.STYLE # Turn lines off for appearance on *nix. # ...for now, show_lines determines if lines are on or off, but # eventually lines_mode may eliminate the need for show_lines if self.lines_mode == 'appearance' and os.name == 'posix': self.show_lines = False if not self.show_lines: style = style | wx.TR_NO_LINES if not self.show_root: # fixme: It looks a little weird, but it we don't have the # 'lines at root' style then wx won't draw the expand/collapse # image on non-leaf nodes at the root level 8^() style = style | wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT if self.selection_mode != 'single': style = style | wx.TR_MULTIPLE | wx.TR_EXTENDED return style def _add_model_listeners(self, model): """ Adds listeners for model changes. """ # Listen for changes to the model. model.on_trait_change(self._on_root_changed, 'root') model.on_trait_change(self._on_nodes_changed, 'nodes_changed') model.on_trait_change(self._on_nodes_inserted, 'nodes_inserted') model.on_trait_change(self._on_nodes_removed, 'nodes_removed') model.on_trait_change(self._on_nodes_replaced, 'nodes_replaced') model.on_trait_change(self._on_structure_changed, 'structure_changed') return def _remove_model_listeners(self, model): """ Removes listeners for model changes. """ # Unhook the model event listeners. model.on_trait_change(self._on_root_changed, 'root', remove=True) model.on_trait_change(self._on_nodes_changed, 'nodes_changed', remove=True) model.on_trait_change(self._on_nodes_inserted, 'nodes_inserted', remove=True) model.on_trait_change(self._on_nodes_removed, 'nodes_removed', remove=True) model.on_trait_change(self._on_nodes_replaced, 'nodes_replaced', remove=True) model.on_trait_change(self._on_structure_changed, 'structure_changed', remove=True) return def _add_root_node(self, node): """ Adds the root node. """ # Get the tree item image index and the label text. image_index = self._get_image_index(node) text = self._get_text(node) # Add the node. wxid = self.control.AddRoot(text, image_index, image_index) # This gives the model a chance to wire up trait handlers etc. self.model.add_listener(node) # If the root node is hidden, get its children. if not self.show_root: # Add the child nodes. for child in self._get_children(node): self._add_node(wxid, child) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data (which in our case is an arbitrary # Python object provided by the tree model). if self.show_root: self.control.SetItemData(wxid, (not self.show_root, node)) # Make sure that we can find the node's Id. self._set_wxid(node, wxid) # Automatically expand the root. if self.show_root: self.control.Expand(wxid) return def _add_node(self, pid, node): """ Adds 'node' as a child of the node identified by 'pid'. If 'pid' is None then we are adding the root node. """ # Get the tree item image index and the label text. image_index = self._get_image_index(node) text = self._get_text(node) # Add the node. wxid = self.control.AppendItem(pid, text, image_index, image_index) # This gives the model a chance to wire up trait handlers etc. self.model.add_listener(node) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data (which in our case is an arbitrary # Python object provided by the tree model). self.control.SetItemData(wxid, (False, node)) # Make sure that we can find the node's Id. self._set_wxid(node, wxid) return def _insert_node(self, pid, node, index): """ Inserts 'node' as a child of the node identified by 'pid'. If 'pid' is None then we are adding the root node. """ # Get the tree item image index and the label text. image_index = self._get_image_index(node) text = self._get_text(node) # Add the node. wxid = self.control.Sizer.InsertBefore(pid, index, text, image_index, image_index) # This gives the model a chance to wire up trait handlers etc. self.model.add_listener(node) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data (which in our case is an arbitrary # Python object provided by the tree model). self.control.SetItemData(wxid, (False, node)) # Make sure that we can find the node's Id. self._set_wxid(node, wxid) return def _remove_node(self, wxid, node): """ Removes a node from the tree. """ # This gives the model a chance to remove trait handlers etc. self.model.remove_listener(node) # Remove the reference to the item's data. self._remove_wxid(node) self.control.SetItemData(wxid, None) return def _update_node(self, wxid, node): """ Updates the image and text of the specified node. """ # Get the tree item image index. image_index = self._get_image_index(node) self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Normal) self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Selected) # Get the tree item text. text = self._get_text(node) self.control.SetItemText(wxid, text) return def _has_children(self, node): """ Returns True if a node has children. """ # fixme: To be correct we *should* apply filtering here, but that # seems to blow a hole throught models that have some efficient # mechanism for determining whether or not they have children. There # is also a precedent for doing it this way in Windoze, where a node # gets marked as though it can be expanded, even thought when the # expansion occurs, no children are present! return self.model.has_children(node) def _get_children(self, node): """ Get the children of a node. """ children = self.model.get_children(node) # Filtering.... filtered_children = [] for child in children: for filter in self.filters: if not filter.select(self, node, child): break else: filtered_children.append(child) # Sorting... if self.sorter is not None: self.sorter.sort(self, node, filtered_children) return filtered_children def _get_image_index(self, node): """ Returns the tree item image index for a node. """ expanded = self.is_expanded(node) selected = self.is_selected(node) # Get the image used to represent the node. image = self.model.get_image(node, selected, expanded) if image is not None: image_index = self._image_list.GetIndex(image) else: image_index = -1 return image_index def _get_drag_drop_node(self, x, y): """ Returns the node that is being dragged/dropped on. Returns None if the cursor is not over the icon or label of a node. """ data, wxid, flags, point = self._hit_test((x, y)) if data is not None: populated, node = data else: node = None return node def _get_text(self, node): """ Returns the tree item text for a node. """ text = self.model.get_text(node) if text is None: text = '' return text def _unpack_event(self, event, wxid=None): """ Unpacks the event to see whether a tree item was involved. """ try: point = event.GetPosition() except: point = event.GetPoint() return self._hit_test(point, wxid) def _hit_test(self, point, wxid=None): """ Determines whether a point is within a node's label or icon. """ flags = wx.TREE_HITTEST_ONITEMLABEL if (wxid is None) or (not wxid.IsOk()): wxid, flags = self.control.HitTest(point) # Warning: On GTK we have to check the flags before we call 'GetPyData' # because if we call it when the hit test returns 'nowhere' it will # barf (on Windows it simply returns 'None' 8^() if flags & wx.TREE_HITTEST_NOWHERE: data = None elif flags & wx.TREE_HITTEST_ONITEMICON \ or flags & wx.TREE_HITTEST_ONITEMLABEL: data = self.control.GetItemData(wxid) # fixme: Not sure why 'TREE_HITTEST_NOWHERE' doesn't catch everything! else: data = None return data, wxid, flags, point def _get_selection(self): """ Returns a list of the selected nodes """ selection = [] for wxid in self.control.GetSelections(): data = self.control.GetItemData(wxid) if data is not None: populated, node = data selection.append(self.model.get_selection_value(node)) return selection def _expand_item(self, wxid): """ Recursively expand a tree item. """ self.control.Expand(wxid) cid, cookie = self.control.GetFirstChild(wxid) while cid.IsOk(): self._expand_item(cid) cid, cookie = self.control.GetNextChild(wxid, cookie) return #### Trait event handlers ################################################# def _on_root_changed(self, root): """ Called when the root of the model has changed. """ # Delete everything... if self.control is not None: self.control.DeleteAllItems() self._node_to_id_map = {} # ... and then add the root item back in. if root is not None: self._add_root_node(root) return def _on_nodes_changed(self, event): """ Called when nodes have been changed. """ self._update_node(self._get_wxid(event.node), event.node) for child in event.children: cid = self._get_wxid(child) if cid is not None: self._update_node(cid, child) return def _on_nodes_inserted(self, event): """ Called when nodes have been inserted. """ parent = event.node children = event.children index = event.index # Has the node actually appeared in the tree yet? pid = self._get_wxid(parent) if pid is not None: # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. if self.show_root or parent is not self.root: populated, node = self.control.GetItemData(pid) else: populated = True # If the node is not yet populated then just get the children and # add them. if not populated: for child in self._get_children(parent): self._add_node(pid, child) # Otherwise, insert them. else: # An index of -1 means append! if index == -1: index = self.control.GetChildrenCount(pid, False) for child in children: self._insert_node(pid, child, index) index += 1 # The element is now populated! if self.show_root or parent is not self.root: self.control.SetItemData(pid, (True, parent)) # Does the node have any children now? has_children = self.control.GetChildrenCount(pid) > 0 self.control.SetItemHasChildren(pid, has_children) # If the node is not expanded then expand it. if not self.is_expanded(parent): self.expand(parent) return def _on_nodes_removed(self, event): """ Called when nodes have been removed. """ parent = event.node children = event.children # Has the node actually appeared in the tree yet? pid = self._get_wxid(parent) if pid is not None: for child in event.children: cid = self._get_wxid(child) if cid is not None: self.control.Delete(cid) # Does the node have any children left? has_children = self.control.GetChildrenCount(pid) > 0 self.control.SetItemHasChildren(pid, has_children) return def _on_nodes_replaced(self, event): """ Called when nodes have been replaced. """ for old_child, new_child in zip(event.old_children, event.children): cid = self._get_wxid(old_child) if cid is not None: # Remove listeners from the old node. self.model.remove_listener(old_child) # Delete all of the node's children. self.control.DeleteChildren(cid) # Update the visual appearance of the node. self._update_node(cid, new_child) # Update the node data. # # The item data is a tuple. The first element indicates # whether or not we have already populated the item with its # children. The second element is the actual item data (which # in our case is an arbitrary Python object provided by the # tree model). self.control.SetItemData(cid, (False, new_child)) # Remove the old node from the node to Id map. self._remove_wxid(old_child) # Add the new node to the node to Id map. self._set_wxid(new_child, cid) # Add listeners to the new node. self.model.add_listener(new_child) # Does the new node have any children? has_children = self._has_children(new_child) self.control.SetItemHasChildren(cid, has_children) # Update the tree's selection (in case the old node that was replaced # was selected, the selection should now include the new node). self.selection = self._get_selection() return def _on_structure_changed(self, event): """ Called when the structure of a node has changed drastically. """ self.refresh(event.node) return #### wx event handlers #################################################### def _on_char(self, event): """ Called when a key is pressed when the tree has focus. """ self.key_pressed = KeyPressedEvent(alt_down=event.AltDown(), control_down=event.ControlDown(), shift_down=event.ShiftDown(), key_code=event.KeyCode) event.Skip() return def _on_left_down(self, event): """ Called when the left mouse button is clicked on the tree. """ data, id, flags, point = self._unpack_event(event) # Save point for tree_begin_drag method to workaround a bug in ?? when # wx.TreeEvent.GetPoint returns only (0,0). This happens under linux # when using wx-2.4.2.4, for instance. self._point_left_clicked = point # Did the left click occur on a tree item? if data is not None: populated, node = data # Trait event notification. self.node_left_clicked = node, point # Give other event handlers a chance. event.Skip() return def _on_right_down(self, event): """ Called when the right mouse button is clicked on the tree. """ data, id, flags, point = self._unpack_event(event) # Did the right click occur on a tree item? if data is not None: populated, node = data # Trait event notification. self.node_right_clicked = node, point # Otherwise notify that the control itself was clicked else: self.control_right_clicked = point # Give other event handlers a chance. event.Skip() return def _on_tree_item_activated(self, event): """ Called when a tree item is activated (i.e., double clicked). """ # fixme: See the comment where the events are wired up for more # information. ## # Which item was activated? ## wxid = event.GetItem() # Which item was activated. point = event.GetPosition() wxid, flags = self.control.HitTest(point) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Trait event notiification. self.node_activated = node return def _on_tree_item_collapsing(self, event): """ Called when a tree item is about to collapse. """ # Which item is collapsing? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Give the model a chance to veto the collapse. if not self.model.is_collapsible(node): event.Veto() return def _on_tree_item_collapsed(self, event): """ Called when a tree item has been collapsed. """ # Which item was collapsed? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Make sure that the item's 'closed' icon is displayed etc. self._update_node(wxid, node) # Trait event notification. self.node_collapsed = node return def _on_tree_item_expanding(self, event): """ Called when a tree item is about to expand. """ # Which item is expanding? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Give the model a chance to veto the expansion. if self.model.is_expandable(node): # Lazily populate the item's children. if not populated: # Add the child nodes. for child in self._get_children(node): self._add_node(wxid, child) # The element is now populated! self.control.SetItemData(wxid, (True, node)) else: event.Veto() return def _on_tree_item_expanded(self, event): """ Called when a tree item has been expanded. """ # Which item was expanded? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Make sure that the node's 'open' icon is displayed etc. self._update_node(wxid, node) # Trait event notification. self.node_expanded = node return def _on_tree_begin_label_edit(self, event): """ Called when the user has started editing an item's label. """ wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Give the model a chance to veto the edit. if not self.model.is_editable(node): event.Veto() return def _on_tree_end_label_edit(self, event): """ Called when the user has finished editing am item's label. """ wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetItemData(wxid) # Give the model a chance to veto the edit. label = event.GetLabel() # Making sure the new label is not an empty string if label is not None and len(label) > 0 and \ self.model.can_set_text(node, label): def end_label_edit(): """ Called to complete the label edit. """ # Set the node's text. self.model.set_text(node, label) # If a label edit callback was specified (in the call to # 'edit_label'), then call it). if self._label_edit_callback is not None: self._label_edit_callback(self, node, label) return # We use a deffered call here, because a name change can trigger # the structure of a node to change, and hence the actual tree # nodes might get moved/deleted before the label edit operation has # completed. When this happens wx gets very confused! By using # 'invoke_later' we allow the label edit to complete. GUI.invoke_later(end_label_edit) else: event.Veto() # If a label edit callback was specified (in the call to # 'edit_label'), then call it). if self._label_edit_callback is not None: self._label_edit_callback(self, node, label) return def _on_tree_begin_drag(self, event): """ Called when a drag operation is starting on a tree item. """ # Get the node, its id and the point where the event occurred. data, wxid, flags, point = self._unpack_event(event, event.GetItem()) if point == (0, 0): # Apply workaround for GTK. point = self.point_left_clicked wxid, flags = self.HitTest(point) data = self.control.GetItemData(wxid) if data is not None: populated, node = data # Give the model a chance to veto the drag. if self.model.is_draggable(node): # We ask the model for the actual value to drag. drag_value = self.model.get_drag_value(node) # fixme: This is a terrible hack to get the binding x passed # during a drag operation. Bindings should probably *always* # be dragged and our drag and drop mechanism should allow # extendable ways to extract the actual data. from pyface.wx.drag_and_drop import clipboard clipboard.node = [node] # Make sure that the tree selection is updated before we start # the drag. If we don't do this then if the first thing a # user does is drag a tree item (i.e., without a separate click # to select it first) then the selection appears empty. self.selection = self._get_selection() # Start the drag. PythonDropSource(self.control, drag_value, self) # Trait event notification. self.node_begin_drag = node else: event.Veto() return # fixme: This is part of the drag and drop hack... def on_dropped(self): """ Callback invoked when a drag/drop operation has completed. """ from pyface.wx.drag_and_drop import clipboard clipboard.node = None return def _on_tree_sel_changed(self, event): """ Called when the selection is changed. """ # Update our record of the selection to whatever was selected in the # tree UNLESS we are ignoring selection events. if not self._ignore_selection_events: # Trait notification. self.selection = self._get_selection() return def _on_tree_delete_item(self, event): """ Called when a tree item is being been deleted. """ # Which item is being deleted? wxid = event.GetItem() # Check if GetPyData() returned a valid to tuple to unpack # ...if so, remove the node from the tree, otherwise just return # # fixme: Whoever addeed this code (and the comment above) didn't say # when this was occurring. This is method is called in response to a wx # event to delete an item and hence the item data should never be None # surely?!? Was it happening just on one platform?!? try: data = self.control.GetItemData(wxid) except: data = None pass if data is not None: # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. populated, node = data # Remove the node. self._remove_node(wxid, node) return
class HistoryControl(HasPrivateTraits): # The UI control: control = Instance(wx.Window) # The current value of the control: value = Str # Should 'value' be updated on every keystroke? auto_set = Bool(False) # The current history of the control: history = List(Str) # The maximum number of history entries allowed: entries = Int(10) # Is the current value valid? error = Bool(False) #-- Public Methods --------------------------------------------------------- def create_control(self, parent): """ Creates the control. """ self.control = control = wx.ComboBox(parent, -1, self.value, wx.Point(0, 0), wx.Size(-1, -1), self.history, style=wx.CB_DROPDOWN) wx.EVT_COMBOBOX(parent, control.GetId(), self._update_value) wx.EVT_KILL_FOCUS(control, self._kill_focus) wx.EVT_TEXT_ENTER(parent, control.GetId(), self._update_text_value) if self.auto_set: wx.EVT_TEXT(parent, control.GetId(), self._update_value_only) return control def dispose(self): """ Disposes of the control at the end of its life cycle. """ control, self.control = self.control, None parent = control.GetParent() wx.EVT_COMBOBOX(parent, control.GetId(), None) wx.EVT_TEXT_ENTER(parent, control.GetId(), None) wx.EVT_KILL_FOCUS(control, None) def set_value(self, value): """ Sets the specified value and adds it to the history. """ self._update(value) #-- Traits Event Handlers -------------------------------------------------- def _value_changed(self, value): """ Handles the 'value' trait being changed. """ if not self._no_update: control = self.control if control is not None: control.SetValue(value) self._restore = False def _history_changed(self): """ Handles the 'history' being changed. """ if not self._no_update: if self._first_time is None: self._first_time = False if (self.value == '') and (len(self.history) > 0): self.value = self.history[0] self._load_history(select=False) def _error_changed(self, error): """ Handles the 'error' trait being changed. """ if error: self.control.SetBackgroundColour(ErrorColor) else: self.control.SetBackgroundColour(OKColor) self.control.Refresh() #-- Wx Event Handlers ------------------------------------------------------ def _update_value(self, event): """ Handles the user selecting something from the drop-down list of the combobox. """ self._update(event.GetString()) def _update_value_only(self, event): """ Handles the user typing into the text field in 'auto_set' mode. """ self._no_update = True self.value = event.GetString() self._no_update = False def _update_text_value(self, event, select=True): """ Handles the user typing something into the text field of the combobox. """ if not self._no_update: self._update(self.control.GetValue(), select) def _kill_focus(self, event): """ Handles the combobox losing focus. """ self._update_text_value(event, False) event.Skip() #-- Private Methods -------------------------------------------------------- def _update(self, value, select=True): """ Updates the value and history list based on a specified value. """ self._no_update = True if value.strip() != '': history = self.history if (len(history) == 0) or (value != history[0]): if value in history: history.remove(value) history.insert(0, value) del history[self.entries:] self._load_history(value, select) self.value = value self._no_update = False def _load_history(self, restore=None, select=True): """ Loads the current history list into the control. """ control = self.control control.Freeze() if restore is None: restore = control.GetValue() control.Clear() for value in self.history: control.Append(value) self._restore = True do_later(self._thaw_value, restore, select) def _thaw_value(self, restore, select): """ Restores the value of the combobox control. """ control = self.control if control is not None: if self._restore: control.SetValue(restore) if select: control.SetMark(0, len(restore)) control.Thaw()
class TasksApplicationAction(GUIApplicationAction): #: The Tasks application the action applies to. application = Instance(TasksApplication)
class Slot(Variable): """A trait for an object of a particular type or implementing a particular interface. Both Traits Interfaces and zope.interface.Interfaces are supported. """ def __init__(self, klass=object, allow_none=True, factory=None, args=None, kw=None, **metadata): default_value = None try: iszopeiface = issubclass(klass, zope.interface.Interface) except TypeError: iszopeiface = False if not isclass(klass): default_value = klass klass = klass.__class__ metadata.setdefault('copy', 'deep') self._allow_none = allow_none self.klass = klass if has_interface(klass, IContainer) or \ (isclass(klass) and IContainer.implementedBy(klass)): self._is_container = True else: self._is_container = False if iszopeiface: self._instance = None self.factory = factory self.args = args self.kw = kw else: self._instance = Instance(klass=klass, allow_none=allow_none, factory=factory, args=args, kw=kw, **metadata) if default_value: self._instance.default_value = default_value else: default_value = self._instance.default_value if klass.__name__ == 'VariableTree': raise TypeError('Slotting of VariableTrees is not supported,' ' please use VarTree instead') super(Slot, self).__init__(default_value, **metadata) def validate(self, obj, name, value): ''' wrapper around Enthought validate method''' if value is None: if self._allow_none: return value self.validate_failed(obj, name, value) if self._instance is None: # our iface is a zope.interface if not self.klass.providedBy(value): self._iface_error(obj, name, self.klass.__name__) else: try: value = self._instance.validate(obj, name, value) except Exception: if issubclass(self._instance.klass, Interface): self._iface_error(obj, name, self._instance.klass.__name__) else: obj.raise_exception("%s must be an instance of class '%s'" % (name, self._instance.klass.__name__), TypeError) return value def post_setattr(self, obj, name, value): '''Containers must know their place within the hierarchy, so set their parent here. This keeps side effects out of validate()''' if self._is_container and value is not None: if value.parent is not obj: value.parent = obj def _iface_error(self, obj, name, iface_name): obj.raise_exception("%s must provide interface '%s'" % (name, iface_name), TypeError) def get_attribute(self, name, value, trait, meta): """Return the attribute dictionary for this variable. This dict is used by the GUI to populate the edit UI. Slots also return an attribute dictionary for the slot pane. name: str Name of variable value: object The value of the variable trait: CTrait The variable's trait meta: dict Dictionary of metadata for this variable """ io_attr = {} io_attr['name'] = name io_attr['type'] = trait.trait_type.klass.__name__ io_attr['ttype'] = 'slot' slot_attr = {} slot_attr['name'] = name if value is None: slot_attr['filled'] = None elif value is []: slot_attr['filled'] = [] else: slot_attr['filled'] = type(value).__name__ slot_attr['klass'] = io_attr['type'] slot_attr['containertype'] = 'singleton' for field in meta: if field not in gui_excludes: slot_attr[field] = meta[field] io_attr[field] = meta[field] return io_attr, slot_attr