Exemple #1
0
class TransientPlotOverlay(BasePlotContainer, AbstractOverlay):
    """ Allows an arbitrary plot component to be overlaid on top of another one.
    """

    # The PlotComponent to draw as an overlay
    overlay_component = Instance(Component)

    # Where this overlay should draw relative to our .component
    align = Enum("right", "left", "top", "bottom")

    # The amount of space between the overlaying component and the underlying
    # one.  This is either horizontal or vertical (depending on the value of
    # self.align), but is not both.
    margin = Float(10)

    # An offset to apply in X and Y
    offset = Trait(None, None, Tuple)

    # Override default values of some inherited traits
    unified_draw = True
    resizable = ""

    def _bounds_default(self):
        return [450, 250]

    def _clear_bounds(self, gc, view_bounds):
        if view_bounds is None:
            view_bounds = (0, 0, self.width, self.height)
        gc.clip_to_rect(*view_bounds)
        gc.set_fill_color((1.0, 1.0, 1.0, 1.0))
        gc.begin_path()
        gc.rect(*view_bounds)
        gc.fill_path()

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        self._do_layout()
        with gc:
            self._clear_bounds(gc, view_bounds)
            self.overlay_component._draw(gc, view_bounds, mode)

    # TODO: Implement this more intelligently than the one in BasePlotContainer
    #def get_preferred_size(self):
    #    pass

    def _do_layout(self):
        component = self.component
        bounds = self.outer_bounds

        if self.align in ("right", "left"):
            y = component.outer_y - (bounds[1] - component.outer_height) / 2
            if self.align == "right":
                x = component.outer_x2 + self.margin
            else:
                x = component.outer_x - bounds[0] - self.margin

        else:  # "top", "bottom"
            x = component.outer_x - (bounds[0] - component.outer_width) / 2
            if self.align == "top":
                y = component.outer_y2 + self.margin
            else:
                y = component.outer_y - bounds[1] - self.margin

        if self.offset is not None:
            x += self.offset[0]
            y += self.offset[1]

        overlay_component = self.overlay_component
        overlay_component.outer_bounds = self.outer_bounds
        overlay_component.outer_position = [x, y]
        overlay_component._layout_needed = True
        overlay_component.do_layout()

    def dispatch(self, event, suffix):
        if self.visible and self.overlay_component.is_in(event.x, event.y):
            return self.overlay_component.dispatch(event, suffix)
class UIEditor(Editor):
    """ An editor that creates an embedded Traits UI.
    """

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

    # The Traits UI created by the editor
    editor_ui = Instance(UI)

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

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        self.editor_ui = self.init_ui(parent).set(parent=self.ui)
        self.control = self.editor_ui.control

    #---------------------------------------------------------------------------
    #  Creates the traits UI for the editor (can be overridden by a subclass):
    #---------------------------------------------------------------------------

    def init_ui(self, parent):
        """ Creates the traits UI for the editor.
        """
        return self.value.edit_traits(
            view=self.trait_view(),
            context={'object': self.value,
                     'editor': self},
            parent=parent)

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

    def update_editor(self):
        """ Updates the editor when the object trait changes external to the
            editor.
        """
        # Do nothing, since the embedded traits UI should handle the updates
        # itself, without our meddling:
        pass

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

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        # Make sure the embedded traits UI is disposed of properly:
        if self.editor_ui is not None:
            self.editor_ui.dispose()

        super(UIEditor, self).dispose()

    #---------------------------------------------------------------------------
    #  Returns the editor's control for indicating error status:
    #---------------------------------------------------------------------------

    def get_error_control(self):
        """ Returns the editor's control for indicating error status.
        """
        return self.editor_ui.get_error_controls()

#-- 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.
        """
        self.editor_ui.set_prefs(prefs)

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

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        return self.editor_ui.get_prefs()
Exemple #3
0
class Scene(TVTKScene, Widget):
    """A VTK interactor scene widget for pyface and wxPython.

    This widget uses a RenderWindowInteractor and therefore supports
    interaction with VTK widgets.  The widget uses TVTK.  In addition
    to the features that the base TVTKScene provides this widget
    supports:

    - saving the rendered scene to the clipboard.

    - picking data on screen.  Press 'p' or 'P' when the mouse is over
      a point that you need to pick.

    - The widget also uses a light manager to manage the lighting of
      the scene.  Press 'l' or 'L' to activate a GUI configuration
      dialog for the lights.

    - Pressing the left, right, up and down arrow let you rotate the
      camera in those directions.  When shift-arrow is pressed then
      the camera is panned.  Pressing the '+' (or '=')  and '-' keys
      let you zoom in and out.

    - Pressing the 'f' key will set the camera focal point to the
      current point.

    - full screen rendering via the full_screen button on the UI.

    """

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

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

    # Turn on full-screen rendering.
    full_screen = Button('Full Screen')

    # The picker handles pick events.
    picker = Instance(picker.Picker)

    ########################################

    # Render_window's view.
    _stereo_view = Group(
        Item(name='stereo_render'),
        Item(name='stereo_type'),
        show_border=True,
        label='Stereo rendering',
    )

    # The default view of this object.
    default_view = View(Group(
        Group(
            Item(name='background'),
            Item(name='foreground'),
            Item(name='parallel_projection'),
            Item(name='disable_render'),
            Item(name='off_screen_rendering'),
            Item(name='jpeg_quality'),
            Item(name='jpeg_progressive'),
            Item(name='magnification'),
            Item(name='anti_aliasing_frames'),
            Item(name='full_screen', show_label=False),
        ),
        Group(
            Item(name='render_window',
                 style='custom',
                 visible_when='object.stereo',
                 editor=InstanceEditor(view=View(_stereo_view)),
                 show_label=False), ),
        label='Scene'),
                        Group(Item(name='light_manager',
                                   style='custom',
                                   show_label=False),
                              label='Lights'),
                        Group(Item(name='movie_maker',
                                   style='custom',
                                   show_label=False),
                              label='Movie'),
                        buttons=['OK', 'Cancel'])

    ########################################
    # Private traits.

    _vtk_control = Instance(wxVTKRenderWindowInteractor)
    _fullscreen = Any
    _interacting = Bool

    ###########################################################################
    # 'object' interface.
    ###########################################################################
    def __init__(self, parent=None, **traits):
        """ Initializes the object. """

        # Base class constructor.
        super(Scene, self).__init__(parent, **traits)

        # Setup the default picker.
        self.picker = picker.Picker(self)

    def __get_pure_state__(self):
        """Allows us to pickle the scene."""
        # The control attribute is not picklable since it is a VTK
        # object so we remove it.
        d = super(Scene, self).__get_pure_state__()
        for x in ['_vtk_control', '_fullscreen', '_interacting']:
            d.pop(x, None)
        return d

    ###########################################################################
    # 'Scene' interface.
    ###########################################################################
    def render(self):
        """ Force the scene to be rendered. Nothing is done if the
        `disable_render` trait is set to True."""
        if not self.disable_render:
            self._vtk_control.Render()

    def get_size(self):
        """Return size of the render window."""
        return self._vtk_control.GetSize()

    def set_size(self, size):
        """Set the size of the window."""
        self._vtk_control.SetSize(size)

    def hide_cursor(self):
        """Hide the cursor."""
        self._vtk_control.HideCursor()

    def show_cursor(self):
        """Show the cursor."""
        self._vtk_control.ShowCursor()

    ###########################################################################
    # 'TVTKScene' interface.
    ###########################################################################
    def save_to_clipboard(self):
        """Saves a bitmap of the scene to the clipboard."""
        handler, name = tempfile.mkstemp()
        self.save_bmp(name)
        bmp = wx.Bitmap(name, wx.BITMAP_TYPE_BMP)
        bmpdo = wx.BitmapDataObject(bmp)
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmpdo)
        wx.TheClipboard.Close()
        os.close(handler)
        os.unlink(name)

    ###########################################################################
    # `wxVTKRenderWindowInteractor` interface.
    ###########################################################################
    def OnKeyDown(self, event):
        """This method is overridden to prevent the 's'/'w'/'e'/'q'
        keys from doing the default thing which is generally useless.
        It also handles the 'p' and 'l' keys so the picker and light
        manager are called.
        """
        keycode = event.GetKeyCode()
        modifiers = event.HasModifiers()
        camera = self.camera
        if keycode < 256:
            key = chr(keycode)
            if key == '-':
                camera.zoom(0.8)
                self.render()
                self._record_methods('camera.zoom(0.8)\nrender()')
                return
            if key in ['=', '+']:
                camera.zoom(1.25)
                self.render()
                self._record_methods('camera.zoom(1.25)\nrender()')
                return
            if key.lower() in ['q', 'e'] or keycode == wx.WXK_ESCAPE:
                self._disable_fullscreen()
            if key.lower() in ['w']:
                event.Skip()
                return
            if key.lower() in ['r']:
                self._record_methods('reset_zoom()')
            # Handle picking.
            if key.lower() in ['p']:
                # In wxPython-2.6, there appears to be a bug in
                # EVT_CHAR so that event.GetX() and event.GetY() are
                # not correct.  Therefore the picker is called on
                # KeyUp.
                event.Skip()
                return
            # Camera focal point.
            if key.lower() in ['f']:
                event.Skip()
                return
            # Light configuration.
            if key.lower() in ['l'] and not modifiers:
                self.light_manager.configure()
                return
            if key.lower() in ['s'] and not modifiers:
                parent = self._vtk_control.GetParent()
                fname = popup_save(parent)
                if len(fname) != 0:
                    self.save(fname)
                return

        shift = event.ShiftDown()
        if keycode == wx.WXK_LEFT:
            if shift:
                camera.yaw(-5)
                self._record_methods('camera.yaw(-5)')
            else:
                camera.azimuth(5)
                self._record_methods('camera.azimuth(5)')
            self.render()
            self._record_methods('render()')
            return
        elif keycode == wx.WXK_RIGHT:
            if shift:
                camera.yaw(5)
                self._record_methods('camera.yaw(5)')
            else:
                camera.azimuth(-5)
                self._record_methods('camera.azimuth(-5)')
            self.render()
            self._record_methods('render()')
            return
        elif keycode == wx.WXK_UP:
            if shift:
                camera.pitch(-5)
                self._record_methods('camera.pitch(-5)')
            else:
                camera.elevation(-5)
                self._record_methods('camera.elevation(-5)')
            camera.orthogonalize_view_up()
            self.render()
            self._record_methods('camera.orthogonalize_view_up()\nrender()')
            return
        elif keycode == wx.WXK_DOWN:
            if shift:
                camera.pitch(5)
                self._record_methods('camera.pitch(5)')
            else:
                camera.elevation(5)
                self._record_methods('camera.elevation(5)')
            camera.orthogonalize_view_up()
            self.render()
            self._record_methods('camera.orthogonalize_view_up()\nrender()')
            return

        self._vtk_control.OnKeyDown(event)

        # Skipping the event is not ideal but necessary because we
        # have no way of knowing of the event was really handled or
        # not and not skipping will break any keyboard accelerators.
        # In practice this does not seem to pose serious problems.
        event.Skip()

    def OnKeyUp(self, event):
        """This method is overridden to prevent the 's'/'w'/'e'/'q'
        keys from doing the default thing which is generally useless.
        It also handles the 'p' and 'l' keys so the picker and light
        manager are called.  The 'f' key sets the camera focus.
        """
        keycode = event.GetKeyCode()
        modifiers = event.HasModifiers()
        if keycode < 256:
            key = chr(keycode)
            if key.lower() in ['s', 'w', 'e', 'q']:
                event.Skip()
                return
            # Set camera focal point.
            if key.lower() in ['f']:
                if not modifiers:
                    if sys.platform == 'darwin':
                        x, y = self._interactor.event_position
                    else:
                        x = event.GetX()
                        y = self._vtk_control.GetSize()[1] - event.GetY()
                    data = self.picker.pick_world(x, y)
                    coord = data.coordinate
                    if coord is not None:
                        self.camera.focal_point = coord
                        self.render()
                        self._record_methods('camera.focal_point = %r\n'\
                                             'render()'%list(coord))
                        return
            # Handle picking.
            if key.lower() in ['p']:
                if not modifiers:
                    if sys.platform == 'darwin':
                        x, y = self._interactor.event_position
                    else:
                        x = event.GetX()
                        y = self._vtk_control.GetSize()[1] - event.GetY()
                    self.picker.pick(x, y)
                    return
                else:
                    # This is here to disable VTK's own pick handler
                    # which can get called when you press Alt/Ctrl +
                    # 'p'.
                    event.Skip()
                    return
            # Light configuration.
            if key.lower() in ['l']:
                event.Skip()
                return

        self._vtk_control.OnKeyUp(event)
        event.Skip()

    def OnPaint(self, event):
        """This method is overridden temporarily in order to create
        the light manager.  This is necessary because it makes sense
        to create the light manager only when the widget is realized.
        Only when the widget is realized is the VTK render window
        created and only then are the default lights all setup
        correctly.  This handler is removed on the first Paint event
        and the default paint handler of the
        wxVTKRenderWindowInteractor is used instead."""

        if self._vtk_control is None:
            return

        # Call the original handler (this will Show the widget)
        self._vtk_control.OnPaint(event)
        if len(self.renderer.lights) == 0:
            # The renderer is not ready yet, we do not do anything, and
            # we do not remove this callback, so that it will be called
            # later.
            return
        # Now create the light manager.
        self.light_manager = light_manager.LightManager(self)

        renwin = self._renwin
        renwin.update_traits()

        vtk_rw = tvtk.to_vtk(renwin)
        renwin.add_observer('StartEvent', messenger.send)
        messenger.connect(vtk_rw, 'StartEvent', self._start_event_callback)
        renwin.add_observer('EndEvent', messenger.send)
        messenger.connect(vtk_rw, 'EndEvent', self._end_event_callback)

        # Reset the event handler to the default since our job is done.
        wx.EVT_PAINT(self._vtk_control, None)  # Remove the default handler.
        wx.EVT_PAINT(self._vtk_control, self._vtk_control.OnPaint)

    def OnSize(self, event):
        """Overrides the default OnSize in order to refresh the traits
        of the render window."""
        if self._renwin is not None:
            self._vtk_control.OnSize(event)
            self._renwin.update_traits()

    def OnButtonDown(self, event):
        """Overrides the default on button down method.
        """
        self._interacting = True
        self._vtk_control.OnButtonDown(event)

    def OnButtonUp(self, event):
        self._interacting = False
        self._vtk_control.OnButtonUp(event)

    ###########################################################################
    # 'event' interface.
    ###########################################################################

    def _closed_fired(self):
        super(Scene, self)._closed_fired()
        self.picker = None
        self._vtk_control = None

    ###########################################################################
    # Non-public interface.
    ###########################################################################
    def _create_control(self, parent):
        """ Create the toolkit-specific control that represents the widget. """

        # Create the VTK widget.
        self._vtk_control = window = wxVTKRenderWindowInteractor(
            parent, -1, stereo=self.stereo)

        # Override these handlers.
        wx.EVT_CHAR(window, None)  # Remove the default handler.
        wx.EVT_CHAR(window, self.OnKeyDown)
        wx.EVT_KEY_UP(window, None)  # Remove the default handler.
        wx.EVT_KEY_UP(window, self.OnKeyUp)
        wx.EVT_PAINT(window, None)  # Remove the default handler.
        wx.EVT_PAINT(window, self.OnPaint)
        wx.EVT_SIZE(window, None)  # Remove the default handler.
        wx.EVT_SIZE(window, self.OnSize)
        # Override the button down and up handlers as well to note the
        # interaction.  This is to toggle the busy status nicely.
        for evt in (wx.EVT_LEFT_DOWN, wx.EVT_RIGHT_DOWN, wx.EVT_MIDDLE_DOWN):
            evt(window, None)
            evt(window, self.OnButtonDown)
        for evt in (wx.EVT_LEFT_UP, wx.EVT_RIGHT_UP, wx.EVT_MIDDLE_UP):
            evt(window, None)
            evt(window, self.OnButtonUp)

        # Enable the widget.
        window.Enable(1)
        # Switch the default interaction style to the trackball one.
        window.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

        # Grab the renderwindow.
        renwin = self._renwin = tvtk.to_tvtk(window.GetRenderWindow())
        renwin.set(point_smoothing=self.point_smoothing,
                   line_smoothing=self.line_smoothing,
                   polygon_smoothing=self.polygon_smoothing)
        # Create a renderer and add it to the renderwindow
        self._renderer = tvtk.Renderer()
        renwin.add_renderer(self._renderer)
        # Save a reference to our camera so it is not GC'd -- needed for
        # the sync_traits to work.
        self._camera = self.camera

        # Sync various traits.
        self._renderer.background = self.background
        self.sync_trait('background', self._renderer)
        self.renderer.on_trait_change(self.render, 'background')
        self._camera.parallel_projection = self.parallel_projection
        self.sync_trait('parallel_projection', self._camera)
        renwin.off_screen_rendering = self.off_screen_rendering
        self.sync_trait('off_screen_rendering', self._renwin)
        self.render_window.on_trait_change(self.render, 'off_screen_rendering')
        self.render_window.on_trait_change(self.render, 'stereo_render')
        self.render_window.on_trait_change(self.render, 'stereo_type')
        self.camera.on_trait_change(self.render, 'parallel_projection')

        def _show_parent_hack(window, parent):
            """A hack to get the VTK scene properly setup for use."""
            # Force the parent to show itself.
            parent.Show(1)
            # on some platforms, this SetSize() is necessary to cause
            # an OnPaint() when the event loop begins; else we get an
            # empty window until we force a redraw.
            window.SetSize(parent.GetSize())
            # This is necessary on slow machines in order to force the
            # wx events to be handled.
            wx.GetApp().Yield(True)
            window.Render()

        if wx.Platform == '__WXMSW__':
            _show_parent_hack(window, parent)
        else:
            if (wx.VERSION[0] == 2) and (wx.VERSION[1] < 5):
                _show_parent_hack(window, parent)
            window.Update()

        # Because of the way the VTK widget is setup, and because we
        # set the size above, the window sizing is usually completely
        # messed up when the application window is shown.  To work
        # around this a dynamic IDLE event handler is added and
        # immediately removed once it executes.  This event handler
        # simply forces a resize to occur.  The _idle_count allows us
        # to execute the idle function a few times (this seems to work
        # better).
        def _do_idle(event, window=window):
            w = wx.GetTopLevelParent(window)
            # Force a resize
            sz = w.GetSize()
            w.SetSize((sz[0] - 1, sz[1] - 1))
            w.SetSize(sz)
            window._idle_count -= 1
            if window._idle_count < 1:
                wx.EVT_IDLE(window, None)
                del window._idle_count

        window._idle_count = 2
        wx.EVT_IDLE(window, _do_idle)

        self._interactor = tvtk.to_tvtk(window._Iren)
        return window

    def _lift(self):
        """Lift the window to the top. Useful when saving screen to an
        image."""
        if self.render_window.off_screen_rendering:
            # Do nothing if off screen rendering is being used.
            return

        w = self._vtk_control
        while w and not w.IsTopLevel():
            w = w.GetParent()
        if w:
            w.Raise()
            wx.GetApp().Yield(True)
            self.render()

    def _start_event_callback(self, obj, event):
        if self._interacting:
            return
        else:
            self.busy = True

    def _end_event_callback(self, obj, event):
        if self._interacting:
            return
        else:
            self.busy = False

    def _busy_changed(self, val):
        GUI.set_busy(val)

    def _full_screen_fired(self):
        fs = self._fullscreen
        if isinstance(fs, PopupScene):
            fs.close()
            self._fullscreen = None
        elif fs is None:
            ver = tvtk.Version()
            popup = False
            if wx.Platform == '__WXMSW__':
                popup = True
            elif ver.vtk_major_version > 5:
                popup = True
            elif (ver.vtk_major_version == 5) and \
                 ((ver.vtk_minor_version >= 1) or \
                  (ver.vtk_build_version > 2)):
                popup = True
            if popup:
                # There is a bug with earlier versions of VTK that
                # breaks reparenting a window which is why we test for
                # the version above.
                f = PopupScene(self)
                self._fullscreen = f
                f.fullscreen()
            else:
                f = FullScreen(self)
                f.run()  # This will block.
                self._fullscreen = None

    def _disable_fullscreen(self):
        fs = self._fullscreen
        if isinstance(fs, PopupScene):
            fs.close()
            self._fullscreen = None
Exemple #4
0
class Pipeline(HasTraits):
    """ Function used to build pipelines for helper functions """
    #doc = ''
    _source_function = Callable()

    _pipeline = List()

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

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

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

    def store_kwargs(self, kwargs):
        """ Merges the given keyword argument, with traits default and
            store the resulting dictionary in self.kwargs."""
        kwargs = kwargs.copy()
        all_traits = self.get_all_traits()
        if not set(kwargs.keys()).issubset(all_traits.keys()):
            raise ValueError("Invalid keyword arguments : %s" % \
                    ', '.join(
                        str(k) for k in
                        set(kwargs.keys()).difference(all_traits.keys())))
        traits = self.get(self.class_trait_names())
        [traits.pop(key) for key in 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.iteritems():
                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 DataSourceFactory(HasStrictTraits):
    """ Factory for creating data sources. The information about the
        organisation of the data is given by setting the public traits.
    """

    # Whether the position is implicitely inferred from the array indices
    position_implicit = false

    # Whether the data is on an orthogonal grid
    orthogonal_grid = false

    # If the data is unstructured
    unstructured = false

    # If the factory should attempt to connect the data points
    connected = true

    # The position of the data points
    position_x = ArrayOrNone
    position_y = ArrayOrNone
    position_z = ArrayOrNone

    # Connectivity array. If none, it is implicitely inferred from the array
    # indices
    connectivity_triangles = ArrayOrNone

    # Whether or not the data points should be connected.
    lines = false

    # The scalar data array
    scalar_data = ArrayOrNone

    # Whether there is vector data
    has_vector_data = false

    # The vector components
    vector_u = ArrayOrNone
    vector_v = ArrayOrNone
    vector_w = ArrayOrNone

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

    _vtk_source = Instance(tvtk.DataSet)

    _mayavi_source = Instance(Source)

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

    def _add_scalar_data(self):
        """ Adds the scalar data to the vtk source.
        """
        if self.scalar_data is not None:
            scalars = self.scalar_data.ravel()
            self._vtk_source.point_data.scalars = scalars

    def _add_vector_data(self):
        """ Adds the vector data to the vtk source.
        """
        if self.has_vector_data:
            vectors = c_[self.vector_u.ravel(),
                         self.vector_v.ravel(),
                         self.vector_w.ravel(), ]
            self._vtk_source.point_data.vectors = vectors

    def _mk_polydata(self):
        """ Creates a PolyData vtk data set using the factory's
            attributes.
        """
        points = c_[self.position_x.ravel(),
                    self.position_y.ravel(),
                    self.position_z.ravel(), ]
        lines = None
        if self.lines:
            np = len(points) - 1
            lines = zeros((np, 2), 'l')
            lines[:, 0] = arange(0, np - 0.5, 1, 'l')
            lines[:, 1] = arange(1, np + 0.5, 1, 'l')
        self._vtk_source = tvtk.PolyData(points=points, lines=lines)
        if (self.connectivity_triangles is not None and self.connected):
            assert self.connectivity_triangles.shape[1] == 3, \
                    "The connectivity list must be Nx3."
            self._vtk_source.polys = self.connectivity_triangles
        self._mayavi_source = VTKDataSource(data=self._vtk_source)

    def _mk_image_data(self):
        """ Creates an ImageData VTK data set and the associated ArraySource
            using the factory's attributes.
        """
        self._mayavi_source = ArraySource(transpose_input_array=True,
                                          scalar_data=self.scalar_data,
                                          origin=[0., 0., 0],
                                          spacing=[1, 1, 1])
        self._vtk_source = self._mayavi_source.image_data

    def _mk_rectilinear_grid(self):
        """ Creates a RectilinearGrid VTK data set using the factory's
            attributes.
        """
        rg = tvtk.RectilinearGrid()
        x = self.position_x.squeeze()
        if x.ndim == 3:
            x = x[:, 0, 0]
        y = self.position_y.squeeze()
        if y.ndim == 3:
            y = y[0, :, 0]
        z = self.position_z.squeeze()
        if z.ndim == 3:
            z = z[0, 0, :]
        # FIXME: We should check array size here.
        rg.dimensions = (x.size, y.size, z.size)
        rg.x_coordinates = x
        rg.y_coordinates = y
        rg.z_coordinates = z
        self._vtk_source = rg
        self._mayavi_source = VTKDataSource(data=self._vtk_source)

    def _mk_structured_grid(self):
        """ Creates a StructuredGrid VTK data set using the factory's
            attributes.
        """
        # FIXME: We need to figure out the dimensions of the data
        # here, if any.
        sg = tvtk.StructuredGrid(dimensions=self.scalar_data.shape)
        sg.points = c_[self.position_x.ravel(),
                       self.position_y.ravel(),
                       self.position_z.ravel(), ]
        self._vtk_source = sg
        self._mayavi_source = VTKDataSource(data=self._vtk_source)

    #----------------------------------------------------------------------
    # Public interface
    #----------------------------------------------------------------------

    def build_data_source(self, **traits):
        """ Uses all the information given by the user on his data
            structure to figure out the right data structure.
        """
        self.set(**traits)
        if not self.lines:
            if self.position_implicit:
                self._mk_image_data()
            elif self.orthogonal_grid:
                self._mk_rectilinear_grid()
            elif self.connectivity_triangles is None:
                if self.unstructured:
                    self._mk_polydata()
                else:
                    self._mk_structured_grid()
            else:
                self._mk_polydata()
        else:
            self._mk_polydata()
        self._add_scalar_data()
        self._add_vector_data()
        return self._mayavi_source
Exemple #6
0
class Align(HasTraits):
    # The position of the view
    position = Array(shape=(3, ))

    brightness = Range(-2., 2., value=0.)
    contrast = Range(0., 3., value=1.)
    opacity = Range(0., 1., value=.1)
    colormap = Enum(*lut_manager.lut_mode_list())
    fliplut = Bool

    outlines_visible = Bool(default_value=True)
    outline_rep = Enum(outline_reps)
    outline_color = Color(
        default=options.config.get("mayavi_aligner", "outline_color"))
    line_width = Range(0.5,
                       10.,
                       value=float(
                           options.config.get("mayavi_aligner", "line_width")))
    point_size = Range(0.5,
                       10.,
                       value=float(
                           options.config.get("mayavi_aligner", "point_size")))

    epi_filter = Enum(None, "median", "gradient")
    filter_strength = Range(1, 20, value=3)

    scene_3d = Instance(MlabSceneModel, ())
    scene_x = Instance(MlabSceneModel, ())
    scene_y = Instance(MlabSceneModel, ())
    scene_z = Instance(MlabSceneModel, ())

    # The data source
    epi_src = Instance(Source)
    surf_src = Instance(Source)
    xfm = Instance(Filter)
    surf = Instance(Module)

    disable_render = Bool

    flip_fb = Bool
    flip_lr = Bool
    flip_ud = Bool

    save_callback = Instance(types.FunctionType)
    save_btn = Button(label="Save Transform")

    legend = Str(legend)

    #---------------------------------------------------------------------------
    # Object interface
    #---------------------------------------------------------------------------
    def __init__(self, pts, polys, epi, xfm=None, xfmtype='magnet', **traits):
        '''
        Parameters
        ----------
        xfm : array_like, optional
            The initial 4x4 rotation matrix into magnet space 
            (epi with slice affine)
        '''
        self.load_epi(epi, xfm, xfmtype)
        self.pts, self.polys = pts, polys
        self._undolist = []
        self._redo = None
        super(Align, self).__init__(**traits)

    def load_epi(self, epifilename, xfm=None, xfmtype="magnet"):
        """Loads the EPI image from the specified epifilename.
        """
        nii = nibabel.load(epifilename)
        self.epi_file = nii
        epi = nii.get_data().astype(float).squeeze()
        if epi.ndim > 3:
            epi = epi[:, :, :, 0]
        self.affine = nii.get_affine()
        base = nii.get_header().get_base_affine()
        self.base = base
        self.origin = base[:3, -1]
        self.spacing = np.diag(base)[:3]
        if xfm is None:
            self.startxfm = np.dot(base, np.linalg.inv(self.affine))
        elif xfmtype == "magnet":
            self.startxfm = np.dot(np.dot(base, np.linalg.inv(self.affine)),
                                   xfm)
        else:
            print("using xfmtype %s" % xfmtype)
            self.startxfm = xfm

        self.center = self.spacing * nii.get_shape()[:3] / 2 + self.origin

        self.padshape = 2**(np.ceil(np.log2(np.array(epi.shape))))

        epi = np.nan_to_num(epi)
        self.epi_orig = epi - epi.min()
        self.epi_orig /= self.epi_orig.max()
        self.epi_orig *= 2
        self.epi_orig -= 1
        self.epi = self.epi_orig.copy()

    #---------------------------------------------------------------------------
    # Default values
    #---------------------------------------------------------------------------
    def _position_default(self):
        return np.abs(self.origin) + (
            (np.array(self.epi.shape) + 1) % 2) * np.abs(self.spacing) / 2

    def _epi_src_default(self):
        sf = mlab.pipeline.scalar_field(self.epi,
                                        figure=self.scene_3d.mayavi_scene,
                                        name='EPI')
        sf.origin = self.origin
        sf.spacing = self.spacing
        return sf

    def _surf_src_default(self):
        return mlab.pipeline.triangular_mesh_source(
            self.pts[:, 0],
            self.pts[:, 1],
            self.pts[:, 2],
            self.polys,
            figure=self.scene_3d.mayavi_scene,
            name='Cortex')

    def _surf_default(self):
        smooth = mlab.pipeline.poly_data_normals(
            self.xfm, figure=self.scene_3d.mayavi_scene)
        smooth.filter.splitting = False
        surf = mlab.pipeline.surface(smooth, figure=self.scene_3d.mayavi_scene)
        surf.actor.mapper.scalar_visibility = 0
        return surf

    def _xfm_default(self):
        xfm = mlab.pipeline.transform_data(self.surf_src,
                                           figure=self.scene_3d.mayavi_scene)

        def savexfm(info, evt):
            self._undolist.append(xfm.transform.matrix.to_array())
            np.save("/tmp/last_xfm.npy", self.get_xfm())

        xfm.widget.add_observer("EndInteractionEvent", savexfm)
        xfm.widget.add_observer("EndInteractionEvent", self.update_slabs)
        xfm.transform.set_matrix(self.startxfm.ravel())
        xfm.widget.set_transform(xfm.transform)
        return xfm

    #---------------------------------------------------------------------------
    # Scene activation callbacks
    #---------------------------------------------------------------------------
    @on_trait_change('scene_3d.activated')
    def display_scene_3d(self):
        self.scene_3d.mlab.view(40, 50)
        self.scene_3d.scene.renderer.use_depth_peeling = True
        self.scene_3d.scene.background = (0, 0, 0)
        # Keep the view always pointing up
        self.scene_3d.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain(
        )
        self.scene_3d.scene_editor.aligner = self

        self.opacity = float(options.config.get("mayavi_aligner", "opacity"))
        self.xfm.widget.enabled = False
        self.colormap = options.config.get("mayavi_aligner", "colormap")

        self.disable_render = True
        for ax in [self.x_axis, self.y_axis, self.z_axis]:
            ax.update_position()
            ax.reset_view()
        self.disable_render = False

    @on_trait_change('scene_x.activated')
    def display_scene_x(self):
        self.x_axis = XAxis(parent=self)

    @on_trait_change('scene_y.activated')
    def display_scene_y(self):
        self.y_axis = YAxis(parent=self)

    @on_trait_change('scene_z.activated')
    def display_scene_z(self):
        self.z_axis = ZAxis(parent=self)

    #---------------------------------------------------------------------------
    # Traits callback
    #---------------------------------------------------------------------------

    def _save_btn_changed(self):
        if self.save_callback is not None:
            self.save_callback(self)

    def _disable_render_changed(self):
        self.scene_3d.scene.disable_render = self.disable_render

    def _position_changed(self):
        self.disable_render = True
        for ax in [self.x_axis, self.y_axis, self.z_axis]:
            ax.update_position()
        self.disable_render = False

    def _outlines_visible_changed(self):
        self.disable_render = True
        for ax in [self.x_axis, self.y_axis, self.z_axis]:
            ax.toggle_outline()
        self.disable_render = False

    @on_trait_change("colormap, fliplut")
    def update_colormap(self):
        for ax in [self.x_axis, self.y_axis, self.z_axis]:
            if ax.ipw_3d and ax.ipw:
                ax.ipw_3d.parent.scalar_lut_manager.set(
                    lut_mode=self.colormap, reverse_lut=self.fliplut)
                ax.ipw.parent.scalar_lut_manager.set(lut_mode=self.colormap,
                                                     reverse_lut=self.fliplut)

    def _opacity_changed(self):
        self.surf.actor.property.opacity = self.opacity

    @on_trait_change("brightness,contrast")
    def update_brightness(self):
        self.epi_src.scalar_data = (self.epi * self.contrast) + self.brightness

    @on_trait_change("flip_ud")
    def update_flipud(self):
        #self.epi_src.scalar_data = self.epi_src.scalar_data[:,:,::-1]
        flip = np.eye(4)
        flip[2, 2] = -1
        mat = self.xfm.transform.matrix.to_array()
        self.set_xfm(np.dot(mat, flip), "base")

    @on_trait_change("flip_lr")
    def update_fliplr(self):
        #self.epi_src.scalar_data = self.epi_src.scalar_data[::-1]
        flip = np.eye(4)
        flip[0, 0] = -1
        mat = self.xfm.transform.matrix.to_array()
        self.set_xfm(np.dot(mat, flip), "base")

    @on_trait_change("flip_fb")
    def update_flipfb(self):
        #self.epi_src.scalar_data = self.epi_src.scalar_data[:,::-1]
        flip = np.eye(4)
        flip[1, 1] = -1
        mat = self.xfm.transform.matrix.to_array()
        self.set_xfm(np.dot(mat, flip), "base")

    @on_trait_change("epi_filter, filter_strength")
    def update_epifilter(self):
        if self.epi_filter is None:
            self.epi = self.epi_orig.copy()
        elif self.epi_filter == "median":
            fstr = np.floor(self.filter_strength / 2) * 2 + 1
            self.epi = volume.detrend_median(self.epi_orig.T, fstr).T
        elif self.epi_filter == "gradient":
            self.epi = volume.detrend_gradient(self.epi_orig.T,
                                               self.filter_strength).T

        self.update_brightness()

    def update_slabs(self, *args, **kwargs):
        self.disable_render = True
        for ax in [self.x_axis, self.y_axis, self.z_axis]:
            ax.update_slab()
        self.disable_render = False

    def get_xfm(self, xfmtype="magnet"):
        if xfmtype in ["anat->epicoord", "coord"]:
            ibase = np.linalg.inv(self.base)
            xfm = self.xfm.transform.matrix.to_array()
            return np.dot(ibase, xfm)
        elif xfmtype in ["anat->epibase", "base"]:
            return self.xfm.transform.matrix.to_array()
        elif xfmtype in ['anat->magnet', "magnet"]:
            ibase = np.linalg.inv(self.base)
            xfm = self.xfm.transform.matrix.to_array()
            return np.dot(self.affine, np.dot(ibase, xfm))

    def set_xfm(self, matrix, xfmtype='magnet'):
        assert xfmtype in "magnet coord base".split(), "Unknown transform type"
        if xfmtype == "coord":
            matrix = np.dot(self.base, matrix)
        elif xfmtype == "magnet":
            iaff = np.linalg.inv(self.affine)
            matrix = np.dot(self.base, np.dot(iaff, matrix))

        self.xfm.transform.set_matrix(matrix.ravel())
        self.xfm.widget.set_transform(self.xfm.transform)
        self.xfm.update_pipeline()
        self.update_slabs()

    def undo(self):
        if len(self._undolist) > 0:
            self.xfm.transform.set_matrix(self._undolist[-1].ravel())
            self.xfm.widget.set_transform(self.xfm.transform)
            self.xfm.update_pipeline()
            self.update_slabs()
            self._redo = self._undolist.pop()

    #---------------------------------------------------------------------------
    # The layout of the dialog created
    #---------------------------------------------------------------------------
    view = View(HGroup(
        Group(
            Item('scene_y', editor=SceneEditor(scene_class=FlatScene)),
            Item('scene_z', editor=SceneEditor(scene_class=FlatScene)),
            show_labels=False,
        ),
        Group(
            Item('scene_x', editor=SceneEditor(scene_class=FlatScene)),
            Item('scene_3d', editor=SceneEditor(scene_class=ThreeDScene)),
            show_labels=False,
        ),
        Group(Group(
            Item("save_btn",
                 show_label=False,
                 visible_when="save_callback is not None"),
            "brightness",
            "contrast",
            "epi_filter",
            Item('filter_strength', visible_when="epi_filter is not None"),
            "_",
            "opacity",
            "_",
            Item('colormap',
                 editor=ImageEnumEditor(values=lut_manager.lut_mode_list(),
                                        cols=6,
                                        path=lut_manager.lut_image_dir)),
            "fliplut",
            "_",
            "flip_ud",
            "flip_lr",
            "flip_fb",
            "_",
            Item('outline_color', editor=ColorEditor()),
            'outline_rep',
            'line_width',
            'point_size',
            '_',
        ),
              Group(
                  Item('legend',
                       editor=TextEditor(),
                       style='readonly',
                       show_label=False,
                       emphasized=True,
                       dock='vertical'),
                  show_labels=False,
              ),
              orientation='vertical'),
    ),
                resizable=True,
                title='Aligner')
Exemple #7
0
class CoordinateBox(HasTraits):
    """
    Represents a box in screen space, and provides convenience properties to
    access bounds and coordinates in a variety of ways.

    Primary attributes (not properties):
        position : [x, y]
        bounds : [width, height]

    Secondary attributes (properties):
        x, y   : coordinates of the lower-left pixel of the box
        x2, y2 : coordinates of the upper-right pixel of the box
        width  : the number of horizontal pixels in the box; equal to x2-x+1
        height : the number of vertical pixels in the box; equal to y2-y+1

    Note that setting x and y will modify the position, but setting any of the
    other secondary attributes will modify the bounds of the box.
    """

    bounds = bounds_trait

    # The position relative to the container.  If container is None, then
    # position will be set to (0,0).
    position = coordinate_trait

    x = Property

    y = Property

    x2 = Property

    y2 = Property

    width = Property

    height = Property

    # ------------------------------------------------------------------------
    # Constraints-based layout
    # ------------------------------------------------------------------------

    if ENABLE_CONSTRAINTS:

        # A read-only symbolic object that represents the left boundary of
        # the component
        left = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the right boundary
        # of the component
        right = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the bottom boundary
        # of the component
        bottom = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the top boundary of
        # the component
        top = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the width of the
        # component
        layout_width = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the height of the
        # component
        layout_height = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the vertical center
        # of the component
        v_center = Property(fget=get_from_constraints_namespace)

        # A read-only symbolic object that represents the horizontal
        # center of the component
        h_center = Property(fget=get_from_constraints_namespace)

        # A size hint for the layout
        layout_size_hint = Tuple(0.0, 0.0)

        # How strongly a layout box hugs it's width hint.
        hug_width = ConstraintPolicyEnum("weak")

        # How strongly a layout box hugs it's height hint.
        hug_height = ConstraintPolicyEnum("weak")

        # How strongly a layout box resists clipping its contents.
        resist_width = ConstraintPolicyEnum("strong")

        # How strongly a layout box resists clipping its contents.
        resist_height = ConstraintPolicyEnum("strong")

        # A namespace containing the constraints for this CoordinateBox
        _constraints_vars = Instance(ConstraintsNamespace)

        # The list of hard constraints which must be applied to the object.
        _hard_constraints = Property

        # The list of size constraints to apply to the object.
        _size_constraints = Property

    # ------------------------------------------------------------------------
    # Public methods
    # ------------------------------------------------------------------------

    def is_in(self, x, y):
        "Returns if the point x,y is in the box"
        p = self.position
        b = self.bounds
        dx = x - p[0]
        dy = y - p[1]
        return (dx >= 0) and (dx < b[0]) and (dy >= 0) and (dy < b[1])

    def as_coordinates(self):
        "Returns a 4-tuple (x, y, x2, y2)"
        p = self.position
        b = self.bounds
        return (p[0], p[1], p[0] + b[0] - 1, p[1] + b[1] - 1)

    # ------------------------------------------------------------------------
    # Property setters and getters
    # ------------------------------------------------------------------------

    def _get_x(self):
        return self.position[0]

    def _set_x(self, val):
        self.position[0] = val

    def _get_y(self):
        return self.position[1]

    def _set_y(self, val):
        self.position[1] = val

    def _get_width(self):
        return self.bounds[0]

    def _set_width(self, val):

        if isinstance(val, str):
            try:
                val = float(val)
            except ValueError:
                pass

        old_value = self.bounds[0]
        self.bounds[0] = val
        self.trait_property_changed("width", old_value, val)

    def _get_height(self):
        return self.bounds[1]

    def _set_height(self, val):
        if isinstance(val, str):
            try:
                val = float(val)
            except ValueError:
                pass
        old_value = self.bounds[1]
        self.bounds[1] = val
        self.trait_property_changed("height", old_value, val)

    def _get_x2(self):
        if self.bounds[0] == 0:
            return self.position[0]
        return self.position[0] + self.bounds[0] - 1

    def _set_x2(self, val):
        self.position[0] = val - self.bounds[0] + 1

    def _old_set_x2(self, val):
        new_width = val - self.position[0] + 1
        if new_width < 0.0:
            raise RuntimeError("Attempted to set negative component width.")
        else:
            self.bounds[0] = new_width

    def _get_y2(self):
        if self.bounds[1] == 0:
            return self.position[1]
        return self.position[1] + self.bounds[1] - 1

    def _set_y2(self, val):
        self.position[1] = val - self.bounds[1] + 1

    def _old_set_y2(self, val):
        new_height = val - self.position[1] + 1
        if new_height < 0.0:
            raise RuntimeError("Attempted to set negative component height.")
        else:
            self.bounds[1] = new_height

    if ENABLE_CONSTRAINTS:

        def __constraints_vars_default(self):
            obj_name = self.id if hasattr(self, "id") else ""
            cns_names = ConstraintsNamespace(type(self).__name__, obj_name)
            add_symbolic_constraints(cns_names)
            return cns_names

        def _get__hard_constraints(self):
            """ Generate the constraints which must always be applied.
            """
            left = self.left
            bottom = self.bottom
            width = self.layout_width
            height = self.layout_height
            cns = [left >= 0, bottom >= 0, width >= 0, height >= 0]
            return cns

        def _get__size_constraints(self):
            """ Creates the list of size hint constraints for this box.
            """
            cns = []
            push = cns.append
            width_hint, height_hint = self.layout_size_hint
            width = self.layout_width
            height = self.layout_height
            hug_width, hug_height = self.hug_width, self.hug_height
            resist_width, resist_height = self.resist_width, self.resist_height
            if width_hint >= 0:
                if hug_width != "ignore":
                    cn = (width == width_hint) | hug_width
                    push(cn)
                if resist_width != "ignore":
                    cn = (width >= width_hint) | resist_width
                    push(cn)
            if height_hint >= 0:
                if hug_height != "ignore":
                    cn = (height == height_hint) | hug_height
                    push(cn)
                if resist_height != "ignore":
                    cn = (height >= height_hint) | resist_height
                    push(cn)

            return cns
class AdvancedEditorAreaPane(TaskPane, MEditorAreaPane):
    """ The toolkit-specific implementation of an AdvancedEditorAreaPane.

    See the IAdvancedEditorAreaPane interface for API documentation.
    """


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

    _main_window_layout = Instance(MainWindowLayout)

    ###########################################################################
    # 'TaskPane' interface.
    ###########################################################################

    def create(self, parent):
        """ Create and set the toolkit-specific control that represents the
            pane.
        """
        self.control = control = EditorAreaWidget(self, parent)
        self._filter = EditorAreaDropFilter(self)
        self.control.installEventFilter(self._filter)

        # Add shortcuts for scrolling through tabs.
        if sys.platform == 'darwin':
            next_seq = 'Ctrl+}'
            prev_seq = 'Ctrl+{'
        else:
            next_seq = 'Ctrl+PgDown'
            prev_seq = 'Ctrl+PgUp'
        shortcut = QtGui.QShortcut(QtGui.QKeySequence(next_seq), self.control)
        shortcut.activated.connect(self._next_tab)
        shortcut = QtGui.QShortcut(QtGui.QKeySequence(prev_seq), self.control)
        shortcut.activated.connect(self._previous_tab)

        # Add shortcuts for switching to a specific tab.
        mod = 'Ctrl+' if sys.platform == 'darwin' else 'Alt+'
        mapper = QtCore.QSignalMapper(self.control)
        mapper.mapped.connect(self._activate_tab)
        for i in xrange(1, 10):
            sequence = QtGui.QKeySequence(mod + str(i))
            shortcut = QtGui.QShortcut(sequence, self.control)
            shortcut.activated.connect(mapper.map)
            mapper.setMapping(shortcut, i - 1)

    def destroy(self):
        """ Destroy the toolkit-specific control that represents the pane.
        """
        self.control.removeEventFilter(self._filter)
        self._filter = None

        for editor in self.editors:
            editor_widget = editor.control.parent()
            self.control.destroy_editor_widget(editor_widget)
            editor.editor_area = None

        super(AdvancedEditorAreaPane, self).destroy()

    ###########################################################################
    # 'IEditorAreaPane' interface.
    ###########################################################################

    def activate_editor(self, editor):
        """ Activates the specified editor in the pane.
        """
        editor_widget = editor.control.parent()
        editor_widget.setVisible(True)
        editor_widget.raise_()
        editor.control.setFocus()
        self.active_editor = editor

    def add_editor(self, editor):
        """ Adds an editor to the pane.
        """
        editor.editor_area = self
        editor_widget = EditorWidget(editor, self.control)
        self.control.add_editor_widget(editor_widget)
        self.editors.append(editor)

    def remove_editor(self, editor):
        """ Removes an editor from the pane.
        """
        editor_widget = editor.control.parent()
        self.editors.remove(editor)
        self.control.remove_editor_widget(editor_widget)
        editor.editor_area = None
        if not self.editors:
            self.active_editor = None

    ###########################################################################
    # 'IAdvancedEditorAreaPane' interface.
    ###########################################################################

    def get_layout(self):
        """ Returns a LayoutItem that reflects the current state of the editors.
        """
        return self._main_window_layout.get_layout_for_area(
            QtCore.Qt.LeftDockWidgetArea)

    def set_layout(self, layout):
        """ Applies a LayoutItem to the editors in the pane.
        """
        if layout is not None:
            self._main_window_layout.set_layout_for_area(
                layout, QtCore.Qt.LeftDockWidgetArea)

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

    def _activate_tab(self, index):
        """ Activates the tab with the specified index, if there is one.
        """
        widgets = self.control.get_dock_widgets_ordered()
        if index < len(widgets):
            self.activate_editor(widgets[index].editor)

    def _next_tab(self):
        """ Activate the tab after the currently active tab.
        """
        if self.active_editor:
            widgets = self.control.get_dock_widgets_ordered()
            index = widgets.index(self.active_editor.control.parent()) + 1
            if index < len(widgets):
                self.activate_editor(widgets[index].editor)

    def _previous_tab(self):
        """ Activate the tab before the currently active tab.
        """
        if self.active_editor:
            widgets = self.control.get_dock_widgets_ordered()
            index = widgets.index(self.active_editor.control.parent()) - 1
            if index >= 0:
                self.activate_editor(widgets[index].editor)

    def _get_label(self, editor):
        """ Return a tab label for an editor.
        """
        label = editor.name
        if editor.dirty:
            label = '*' + label
        return label

    #### Trait initializers ###################################################

    def __main_window_layout_default(self):
        return EditorAreaMainWindowLayout(editor_area=self)

    #### Trait change handlers ################################################

    @on_trait_change('editors:[dirty, name]')
    def _update_label(self, editor, name, new):
        editor.control.parent().update_title()

    @on_trait_change('editors:tooltip')
    def _update_tooltip(self, editor, name, new):
        editor.control.parent().update_tooltip()
Exemple #9
0
class XFETSEval(FETSEval):
    '''Base class of an eXtended or enriched element.
    '''
    parent_fets_eval = Instance(IFETSEval)

    n_parents = Int(desc='number of parents included in the evaluation')
Exemple #10
0
    Undefined,
    cached_property,
)

from traits.trait_base import not_none, xgetattr, xsetattr

from .editor_factory import EditorFactory

from .context_value import ContextValue

from .undo import UndoItem

from .item import Item

# Reference to an EditorFactory object
factory_trait = Instance(EditorFactory)


class Editor(HasPrivateTraits):
    """ Represents an editing control for an object trait in a Traits-based
        user interface.
    """

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

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

    #: The object this editor is editing (e.g. object.link1.link2):
Exemple #11
0
class Editor(HasPrivateTraits):
    """ Represents an editing control for an object trait in a Traits-based
        user interface.
    """

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    #: The EditorFactory used to create this editor:
    factory = Instance(EditorFactory, clean_up=True)

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

    #: Current value for object.name:
    value = Property()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        This should normally be overridden in a subclass.

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

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

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

    def set_tooltip_text(self, control, text):
        """ Sets the tooltip for a toolkit control to the provided text.

        This method must be overriden by subclasses.

        Parameters
        ----------
        text : str
            The text to use for the tooltip.
        control : toolkit control
            The toolkit control that is having the tooltip set.
        """
        raise NotImplementedError("This method must be overriden.")

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

        This simply delegates to the factory's `string_value` method.
        Sub-classes may choose to override the default implementation.

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

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

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

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

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

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

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

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

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

        Parameters
        ----------
        parent : toolkit control
            The parent toolkit object of the editor's toolkit objects.
        """
        name = self.extended_name
        if name != "None":
            self.context_object.on_trait_change(self._update_editor,
                                                name,
                                                dispatch="ui")
        self.init(parent)
        self._sync_values()
        self.update_editor()

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

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

        Subclasses may chose to override this method to perform additional
        clean-up.
        """
        if self.ui is None:
            return

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

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

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

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

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

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

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

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

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

        Can be overridden in a subclass for special value types.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def set_tooltip(self, control=None):
        """ Sets the tooltip for a specified toolkit control.

        This uses the tooltip_text method to get the text to use.

        Parameters
        ----------
        control : optional toolkit control
            The toolkit control that is having the tooltip set.  If None
            then the editor's control is used.

        Returns
        -------
        tooltip_set : bool
            Whether or not a tooltip value could be set.
        """
        text = self.tooltip_text()
        if text is None:
            return False

        if control is None:
            control = self.control

        self.set_tooltip_text(control, text)

        return True

    def tooltip_text(self):
        """ Get the text for a tooltip, checking various sources.

        This checks for text from, in order:

        - the editor's description trait
        - the base trait's 'tooltip' metadata
        - the base trait's 'desc' metadata

        Returns
        -------
        text : str or None
            The text for the tooltip, or None if no suitable text can
            be found.
        """

        if self.description:
            return self.description

        base_trait = self.object.base_trait(self.name)

        text = base_trait.tooltip
        if text is not None:
            return text

        text = base_trait.desc
        if text is not None:
            return "Specifies " + text

        return None

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

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

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

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

            raise_to_debug()

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

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

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

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

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

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

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

        This is designed to be used as a trait listener.
        """
        # If background threads have modified the trait the editor is bound to,
        # their trait notifications are queued to the UI thread. It is possible
        # that by the time the UI thread dispatches these events, the UI the
        # editor is part of has already been closed. So we need to check if we
        # are still bound to a live UI, and if not, exit immediately:
        if self.ui is None:
            return

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

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

        # Log the change that was made (as long as the Item is not readonly
        # or it is not for an event):
        if (self.item.style != "readonly"
                and object.base_trait(name).type != "event"):
            # Indicate that the contents of the UI have been changed:
            self.ui.modified = True

            if self.updating:
                self.log_change(self.get_undo_item, object, name, old_value,
                                new_value)

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

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

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

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

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

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

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

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

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

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

        if is_list:

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

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

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

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

        self.on_trait_change(editor_trait_modified, editor_name)

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

        if is_list:

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

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

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

        This calls the appropriate setattr method on the handler to perform
        the actual change.
        """
        with self.updating_value():
            try:
                handler = self.ui.handler
                obj_name = self.object_name
                name = self.name
                method = (getattr(handler, "%s_%s_setattr" %
                                  (obj_name, name), None)
                          or getattr(handler, "%s_setattr" % name, None)
                          or getattr(handler, "setattr"))
                method(self.ui.info, self.object, name, value)
            except TraitError as excp:
                self.error(excp)
                raise

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

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

        In some cases a proxy object is edited rather than an object directly
        in the context, in which case we return ``self.object``.
        """
        object_name = self.object_name
        context_key = object_name.split(".", 1)[0]
        if (object_name != "") and (context_key in self.ui.context):
            return self.ui.context[context_key]

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

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

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

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

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

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

    def _get_str_value(self):
        """ Returns the text representation of the object trait.
        """
        return self.string_value(getattr(self.object, self.name, Undefined))
Exemple #12
0
class RTraceDomainList(HasTraits):

    label = Str('RTraceDomainField')
    sd = WeakRef(ISDomain)
    position = Enum('nodes', 'int_pnts')
    subfields = List

    def redraw(self):
        '''Delegate the calculation to the pipeline
        '''
        # self.mvp_mgrid_geo.redraw() # 'label_scalars')
        self.mvp_mgrid_geo.rebuild_pipeline(self.vtk_node_structure)

    vtk_node_structure = Property(Instance(tvtk.UnstructuredGrid))

    # @cached_property

    def _get_vtk_node_structure(self):
        self.position = 'nodes'
        return self.vtk_structure

    vtk_ip_structure = Property(Instance(tvtk.UnstructuredGrid))

    # @cached_property

    def _get_vtk_ip_structure(self):
        self.position = 'int_pnts'
        return self.vtk_structure

    vtk_structure = Property(Instance(tvtk.UnstructuredGrid))

    def _get_vtk_structure(self):
        ug = tvtk.UnstructuredGrid()
        cell_array, cell_offsets, cell_types = self.vtk_cell_data
        n_cells = cell_types.shape[0]
        ug.points = self.vtk_X
        vtk_cell_array = tvtk.CellArray()
        vtk_cell_array.set_cells(n_cells, cell_array)
        ug.set_cells(cell_types, cell_offsets, vtk_cell_array)
        return ug

    vtk_X = Property

    def _get_vtk_X(self):
        point_arr_list = []
        for sf in self.subfields:
            if sf.skip_domain:
                continue
            sf.position = self.position
            sf_vtk_X = sf.vtk_X
            if sf_vtk_X.shape[0] == 0:  # all elem are deactivated
                continue
            point_arr_list.append(sf_vtk_X)
        if len(point_arr_list) > 0:
            # print 'point_arr_list ', point_arr_list
            return vstack(point_arr_list)
        else:
            return zeros((0, 3), dtype='float_')

    # point offset to use when more fields are patched together within
    # RTDomainList

    point_offset = Int(0)

    # cell offset to use when more fields are patched together within
    # RTDomainList

    cell_offset = Int(0)

    vtk_cell_data = Property

    def _get_vtk_cell_data(self):
        cell_array_list = []
        cell_offset_list = []
        cell_types_list = []
        point_offset = self.point_offset
        cell_offset = self.cell_offset
        for sf in self.subfields:
            if sf.skip_domain:
                continue
            sf.position = self.position
            sf.point_offset = point_offset
            sf.cell_offset = cell_offset
            cell_array, cell_offsets, cell_types = sf.vtk_cell_data
            cell_array_list.append(cell_array)
            cell_offset_list.append(cell_offsets)
            cell_types_list.append(cell_types)
            point_offset += sf.n_points
            cell_offset += cell_array.shape[0]
        if len(cell_array_list) > 0:
            cell_array = hstack(cell_array_list)
            cell_offsets = hstack(cell_offset_list)
            cell_types = hstack(cell_types_list)
        else:
            cell_array = array([], dtype='int_')
            cell_offsets = array([], dtype='int_')
            cell_types = array([], dtype='int_')
        return (cell_array, cell_offsets, cell_types)

    #-------------------------------------------------------------------------
    # Visualization pipelines
    #-------------------------------------------------------------------------

    mvp_mgrid_geo = Trait(MVUnstructuredGrid)

    #    def _mvp_mgrid_geo_default(self):
    #        return MVUnstructuredGrid( name = 'Response tracer mesh',
    #                                   points = self.vtk_r,
    #                                   cell_data = self.vtk_cell_data,
    #                                    )

    def _mvp_mgrid_geo_default(self):
        return MVUnstructuredGrid(name='Response tracer mesh',
                                  warp=False,
                                  warp_var='')

    view = View(resizable=True)
Exemple #13
0
class NodeTree(Tree):
    """ A tree control with extensible node types. """

    #### 'Tree' interface #####################################################

    # The model that provides the data for the tree.
    model = Instance(NodeTreeModel, ())

    #### 'NodeTree' interface #################################################

    # The node manager looks after all node types.
    node_manager = Property(Instance(NodeManager))

    # The node types in the tree.
    node_types = Property(List(NodeType))

    ###########################################################################
    # 'NodeTree' interface.
    ###########################################################################

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

    # node_manager
    def _get_node_manager(self):
        """ Returns the root node of the tree. """

        return self.model.node_manager

    def _set_node_manager(self, node_manager):
        """ Sets the root node of the tree. """

        self.model.node_manager = node_manager

        return

    # node_types
    def _get_node_types(self):
        """ Returns the node types in the tree. """

        return self.model.node_manager.node_types

    def _set_node_types(self, node_types):
        """ Sets the node types in the tree. """

        self.model.node_manager.node_types = node_types

        return

    ###########################################################################
    # 'Tree' interface.
    ###########################################################################

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

    def _node_activated_changed(self, obj):
        """ Called when a node has been activated (i.e., double-clicked). """

        default_action = self.model.get_default_action(obj)
        if default_action is not None:
            self._perform_default_action(default_action, obj)

        return

    def _node_right_clicked_changed(self, (obj, point)):
        """ Called when the right mouse button is clicked on the tree. """

        # Add the node that the right-click occurred on to the selection.
        self.select(obj)

        # fixme: This is a hack to allow us to attach the node that the
        # right-clicked occurred on to the action event.
        self._context = obj

        # Ask the model for the node's context menu.
        menu_manager = self.model.get_context_menu(obj)
        if menu_manager is not None:
            self._popup_menu(menu_manager, obj, point)

        return
Exemple #14
0
class MultithreadingRouter(HasRequiredTraits):
    """
    Implementation of the IMessageRouter interface for the case where the
    sender will be in a background thread.

    Parameters
    ----------
    event_loop : IEventLoop
        The event loop used to trigger message dispatch.

    """
    def start(self):
        """
        Start routing messages.

        This method must be called before any call to ``pipe`` or
        ``close_pipe`` can be made.

        Not thread-safe. Must always be called in the main thread.

        Raises
        ------
        RuntimeError
            If the router has already been started.
        """
        if self._running:
            raise RuntimeError("router is already running")

        self._message_queue = queue.Queue()
        self._link_to_event_loop()

        self._running = True
        logger.debug(f"{self} started")

    def stop(self):
        """
        Stop routing messages.

        This method should be called in the main thread after all pipes
        are finished with. Calls to ``pipe`` or ``close_pipe`` are
        not permitted after this method has been called.

        Logs a warning if there are unclosed pipes.

        Not thread safe. Must always be called in the main thread.

        Raises
        ------
        RuntimeError
            If the router is not running.
        """
        if not self._running:
            raise RuntimeError("router is not running")

        if self._receivers:
            logger.warning(f"{self} has {len(self._receivers)} unclosed pipes")

        self._unlink_from_event_loop()

        self._message_queue = None

        self._running = False
        logger.debug(f"{self} stopped")

    def pipe(self):
        """
        Create a (sender, receiver) pair for sending and receiving messages.

        The sender will be passed to the background task and used to send
        messages, while the receiver remains in the foreground.

        Not thread safe. Must always be called in the main thread.

        Returns
        -------
        sender : MultithreadingSender
            Object to be passed to the background task.
        receiver : MultithreadingReceiver
            Object kept in the foreground, which reacts to messages.

        Raises
        ------
        RuntimeError
            If the router is not currently running.
        """
        if not self._running:
            raise RuntimeError("router is not running")

        connection_id = next(self._connection_ids)
        sender = MultithreadingSender(
            connection_id=connection_id,
            pingee=self._pingee,
            message_queue=self._message_queue,
        )
        receiver = MultithreadingReceiver(connection_id=connection_id)
        self._receivers[connection_id] = receiver
        logger.debug(
            f"{self} created pipe #{connection_id} with receiver {receiver}")
        return sender, receiver

    def close_pipe(self, receiver):
        """
        Close the receiver end of a pipe produced by ``pipe``.

        Removes the receiver from the routing table, so that no new messages
        can reach that receiver.

        Not thread safe. Must always be called in the main thread.

        Parameters
        ----------
        receiver : MultithreadingReceiver
            Receiver half of the pair returned by the ``pipe`` method.

        Raises
        ------
        RuntimeError
            If the router is not currently running.
        """
        if not self._running:
            raise RuntimeError("router is not running")

        connection_id = receiver.connection_id
        self._receivers.pop(connection_id)
        logger.debug(
            f"{self} closed pipe #{connection_id} with receiver {receiver}")

    def route_until(self, condition, timeout=None):
        """
        Manually drive the router until a given condition occurs, or timeout.

        This is primarily used as part of a clean shutdown.

        Note: this has the side-effect of moving the router from "event loop"
        mode to "manual" mode. This mode switch is permanent, in the sense that
        after this point, the router will no longer respond to pings: any
        messages will need to be processed through this function.

        Parameters
        ----------
        condition
            Zero-argument callable returning a boolean. When this condition
            becomes true, this method will stop routing messages. If the
            condition is already true on entry, no messages will be routed.
        timeout : float, optional
            Maximum number of seconds to route messages for.

        Raises
        ------
        RuntimeError
            If the condition did not become true before timeout.
        """
        self._unlink_from_event_loop()

        if timeout is None:
            while not condition():
                self._route_message(block=True)
        else:
            end_time = time.monotonic() + timeout
            while not condition():
                try:
                    time_remaining = end_time - time.monotonic()
                    self._route_message(block=True, timeout=time_remaining)
                except queue.Empty:
                    raise RuntimeError("Timed out waiting for messages")

    # Public traits ###########################################################

    #: The event loop used to trigger message dispatch.
    event_loop = Instance(IEventLoop, required=True)

    # Private traits ##########################################################

    #: Internal queue for messages from all senders.
    _message_queue = Instance(queue.Queue)

    #: Source of new connection ids.
    _connection_ids = Instance(collections.abc.Iterator)

    #: Receivers, keyed by connection_id.
    _receivers = Dict(Int(), Instance(MultithreadingReceiver))

    #: Receiver for the "message_sent" signal.
    _pingee = Instance(IPingee)

    #: Bool keeping track of whether we're linked to the event loop
    #: or not.
    _linked = Bool(False)

    #: Router status: True if running, False if stopped.
    _running = Bool(False)

    # Private methods #########################################################

    def _link_to_event_loop(self):
        """
        Link this router to the event loop.
        """
        if self._linked:
            # Raise, because lifetime management of self._pingee is delicate,
            # so if we ever get here then something likely needs fixing.
            raise RuntimeError("Already linked to the event loop")

        self._pingee = self.event_loop.pingee(on_ping=self._route_message)
        self._pingee.connect()
        self._linked = True

    def _unlink_from_event_loop(self):
        """
        Unlink this router from the event loop, if it's linked.

        After this call, the router will no longer react to any pending
        tasks on the event loop.
        """
        if self._linked:
            # Note: it might be tempting to set self._pingee to None at this
            # point, and to use the None-ness (or not) of self._pingee to avoid
            # needing self._linked. But it's important not to do so: we need to
            # be sure that the main thread reference to the Pingee outlives any
            # reference on background threads. Otherwise we end up collection a
            # Qt object (the Pingee) on a thread other than the one it was
            # created on, and that's unsafe in general.
            self._pingee.disconnect()
            self._linked = False

    def _route_message(self, *, block=False, timeout=None):
        """
        Get and dispatch a message from the local message queue.

        Parameters
        ----------
        block : bool, optional
            If True, block until either a message arrives or until timeout. If
            False (the default), we expect a message to already be present in
            the queue.
        timeout : float, optional
            Maximum time to wait for a message to arrive. If no timeout
            is given and ``block`` is True, wait indefinitely. If ``block``
            is False, this parameter is ignored.

        Raises
        ------
        queue.Empty
            If no message arrives within the given timeout.
        """
        connection_id, message = self._message_queue.get(
            block=block,
            timeout=None if timeout is None else max(timeout, 0.0),
        )
        try:
            receiver = self._receivers[connection_id]
        except KeyError:
            logger.warning(
                f"{self} discarding message from closed pipe #{connection_id}."
            )
        else:
            receiver.message = message

    def __connection_ids_default(self):
        return itertools.count()
Exemple #15
0
class MainGUI(HasTraits):
    """ MainGUI is the main class under which the Model-View-Control (MVC) model is defined
    """
    camera_list = List
    imgplt_flag = 0
    pass_init = Bool(False)
    update_thread_plot = Bool(False)
    tr_thread = Instance(TrackThread)
    selected = Any

    # Defines GUI view --------------------------
    view = View(
        Group(Group(Item(name='exp1',
                         editor=tree_editor_exp,
                         show_label=False,
                         width=-400,
                         resizable=False),
                    Item('camera_list',
                         style='custom',
                         editor=ListEditor(use_notebook=True,
                                           deletable=False,
                                           dock_style='tab',
                                           page_name='.name',
                                           selected='selected'),
                         show_label=False),
                    orientation='horizontal',
                    show_left=False),
              orientation='vertical'),
        title='pyPTV',
        id='main_view',
        width=1.,
        height=1.,
        resizable=True,
        handler=TreeMenuHandler(),  # <== Handler class is attached
        menubar=menu_bar)

    def _selected_changed(self):
        self.current_camera = int(self.selected.name.split(' ')[1]) - 1

    #---------------------------------------------------
    # Constructor and Chaco windows initialization
    #---------------------------------------------------
    def __init__(self):
        super(MainGUI, self).__init__()
        colors = ['yellow', 'green', 'red', 'blue']
        self.exp1 = Experiment()
        self.exp1.populate_runs(exp_path)
        self.plugins = Plugins()
        self.n_camera = self.exp1.active_params.m_params.Num_Cam
        print self.n_camera
        self.orig_image = []
        self.hp_image = []
        self.current_camera = 0
        self.camera_list = []
        for i in range(self.n_camera):
            self.camera_list.append(CameraWindow(colors[i]))
            self.camera_list[i].name = "Camera " + str(i + 1)
            self.camera_list[i].on_trait_change(self.right_click_process,
                                                'rclicked')
            self.orig_image.append(np.array([], dtype=np.ubyte))
            self.hp_image.append(np.array([]))
        ptv.py_init_proc_c()  #intialization of globals in ptv C module

    #------------------------------------------------------
    def right_click_process(self):
        x_clicked, y_clicked, n_camera = 0, 0, 0
        h_img = self.exp1.active_params.m_params.imx
        v_img = self.exp1.active_params.m_params.imy
        print h_img, v_img
        for i in range(len(self.camera_list)):

            n_camera = i
            x_clicked,y_clicked=self.camera_list[i]._click_tool.x,\
                                self.camera_list[i]._click_tool.y
            x1, y1, x2, y2, x1_points, y1_points, intx1, inty1 = ptv.py_right_click(
                x_clicked, y_clicked, n_camera)
            if (x1 != -1 and y1 != -1):
                self.camera_list[n_camera].right_p_x0.append(intx1)
                self.camera_list[n_camera].right_p_y0.append(inty1)
                self.camera_list[n_camera].drawcross("right_p_x0","right_p_y0",
                    self.camera_list[n_camera].right_p_x0\
                ,self.camera_list[n_camera].right_p_y0,"cyan",3,marker1="circle")
                self.camera_list[n_camera]._plot.request_redraw()
                print "right click process"
                print x1, y1, x2, y2, x1_points, y1_points
                color_camera = ['yellow', 'red', 'blue', 'green']
                #print [x1[i]],[y1[i]],[x2[i]],[y2[i]]
                for j in range(len(self.camera_list)):
                    if j is not n_camera:
                        count = self.camera_list[i]._plot.plots.keys()
                        self.camera_list[j].drawline("right_cl_x"+str(len(count)),\
                        "right_cl_y"+str(len(count)),x1[j],y1[j],x2[j],y2[j],\
                        color_camera[n_camera])
                        self.camera_list[
                            j]._plot.index_mapper.range.set_bounds(0, h_img)
                        self.camera_list[
                            j]._plot.value_mapper.range.set_bounds(0, v_img)
                        self.camera_list[j].drawcross("right_p_x1","right_p_y1",x1_points[j],y1_points[j],\
                        color_camera[n_camera],2)
                        self.camera_list[j]._plot.request_redraw()
            else:
                print("No nearby points for epipolar lines")
            self.camera_list[i].rclicked = 0

    def update_plots(self, images, is_float=False):
        for i in range(len(images)):
            self.camera_list[i].update_image(images[i], is_float)
            self.camera_list[i]._plot.request_redraw()

    # set_images sets ptv's C module img[] array
    def set_images(self, images):
        for i in range(len(images)):
            ptv.py_set_img(images[i], i)

    def get_images(self, plot_index, images):
        for i in plot_index:
            ptv.py_get_img(images[i], i)

    def drawcross(self, str_x, str_y, x, y, color1, size1):
        for i in range(len(self.camera_list)):
            self.camera_list[i].drawcross(str_x, str_y, x[i], y[i], color1,
                                          size1)
            self.camera_list[i]._plot.request_redraw()

    def clear_plots(self, remove_background=True):
        # this function deletes all plotes except basic image plot

        if not remove_background:
            index = 'plot0'
        else:
            index = None

        for i in range(len(self.camera_list)):
            plot_list = self.camera_list[i]._plot.plots.keys()
            #if not remove_background:
            #   index=None
            try:
                plot_list.remove(index)
            except:
                pass
            self.camera_list[i]._plot.delplot(*plot_list[0:])
            self.camera_list[i]._plot.tools = []
            self.camera_list[i]._plot.request_redraw()
            for j in range(len(self.camera_list[i]._quiverplots)):
                self.camera_list[i]._plot.remove(
                    self.camera_list[i]._quiverplots[j])
            self.camera_list[i]._quiverplots = []
            self.camera_list[i].right_p_x0 = []
            self.camera_list[i].right_p_y0 = []
            self.camera_list[i].right_p_x1 = []
            self.camera_list[i].right_p_y1 = []

    def _update_thread_plot_changed(self):
        n_camera = len(self.camera_list)

        if self.update_thread_plot and self.tr_thread:
            print "updating plots..\n"
            step = self.tr_thread.track_step

            x0,x1,x2,y0,y1,y2,pnr1,pnr2,pnr3,m_tr=\
            self.tr_thread.intx0,self.tr_thread.intx1,self.tr_thread.intx2,\
            self.tr_thread.inty0,self.tr_thread.inty1,self.tr_thread.inty2,self.tr_thread.pnr1,\
            self.tr_thread.pnr2,self.tr_thread.pnr3,self.tr_thread.m_tr
            for i in range(n_camera):
                self.camera_list[i].drawcross(
                    str(step) + "x0",
                    str(step) + "y0", x0[i], y0[i], "green", 2)
                self.camera_list[i].drawcross(
                    str(step) + "x1",
                    str(step) + "y1", x1[i], y1[i], "yellow", 2)
                self.camera_list[i].drawcross(
                    str(step) + "x2",
                    str(step) + "y2", x2[i], y2[i], "white", 2)
                self.camera_list[i].drawquiver(x0[i], y0[i], x1[i], y1[i],
                                               "orange")
                self.camera_list[i].drawquiver(x1[i], y1[i], x2[i], y2[i],
                                               "white")
##                for j in range (m_tr):
##                    str_plt=str(step)+"_"+str(j)
##
##                    self.camera_list[i].drawline\
##                    (str_plt+"vec_x0",str_plt+"vec_y0",x0[i][j],y0[i][j],x1[i][j],y1[i][j],"orange")
##                    self.camera_list[i].drawline\
##                    (str_plt+"vec_x1",str_plt+"vec_y1",x1[i][j],y1[i][j],x2[i][j],y2[i][j],"white")
            self.load_set_seq_image(step, update_all=False, display_only=True)
            self.camera_list[self.current_camera]._plot.request_redraw()
            time.sleep(0.1)
            self.tr_thread.can_continue = True
            self.update_thread_plot = False

    def load_set_seq_image(self, seq, update_all=True, display_only=False):
        n_camera = len(self.camera_list)
        if not hasattr(self, 'base_name'):
            self.base_name = []
            for i in range(n_camera):
                exec(
                    "self.base_name.append(self.exp1.active_params.m_params.Basename_%d_Seq)"
                    % (i + 1))
                print self.base_name[i]

        i = seq
        seq_ch = "%04d" % i

        if not update_all:
            j = self.current_camera
            img_name = self.base_name[j] + seq_ch
            self.load_disp_image(img_name, j, display_only)
        else:
            for j in range(n_camera):
                img_name = self.base_name[j] + seq_ch
                self.load_disp_image(img_name, j, display_only)

    def load_disp_image(self, img_name, j, display_only=False):
        print("Setting image: %s" % str(img_name))
        try:
            temp_img = img_as_ubyte(imread(img_name))
        except:
            print("Error reading file, setting zero image")
            h_img = self.exp1.active_params.m_params.imx
            v_img = self.exp1.active_params.m_params.imy
            temp_img = img_as_ubyte(np.zeros((h_img, v_img)))
        if not display_only:
            ptv.py_set_img(temp_img, j)
        if len(temp_img) > 0:
            self.camera_list[j].update_image(temp_img)
Exemple #16
0
class FFTDemo(HasTraits):

    select = Enum([cls.__name__ for cls in BaseWave.__subclasses__()])
    wave = Instance(BaseWave)

    # FFT计算所使用的取样点数,这里用一个Enum类型的属性以供用户从列表中选择
    fftsize = Enum(256, [(2**x) for x in range(6, 12)])

    # 用于显示FFT的结果
    peak_list = Str
    # 采用多少个频率合成形
    N = Range(1, 40, 4)

    figure = Instance(Figure, ())

    view = View(
        HSplit(
            VSplit(
                VGroup(
                    Item("select", label=u"波形类型"),
                    Item("wave", style="custom", label=u"波形属性"),
                    Item("fftsize", label=u"FFT点数"),
                    Item("N", label=u"合成波频率数"),
                    show_labels=True,
                ),
                Item("peak_list",
                     style="custom",
                     show_label=False,
                     width=100,
                     height=250)),
            VGroup(Item("figure", editor=MPLFigureEditor(toolbar=True)),
                   show_labels=False),
        ),
        title=u"FFT演示",
        resizable=True,
        width=1280,
        height=600,
    )

    def __init__(self, **traits):
        super(FFTDemo, self).__init__(**traits)

        self.ax1 = self.figure.add_subplot(211)
        self.ax2 = self.figure.add_subplot(212)

        self.ax1.set_title("FFT")
        self.ax2.set_title("Wave")

        self.line_peaks, = self.ax1.plot([0], [0], "o")
        self.line_wave, = self.ax2.plot([0], [0])
        self.line_wave2, = self.ax2.plot([0], [0])

        self._select_changed()

    def _select_changed(self):
        klass = globals()[self.select]
        self.wave = klass()

    @on_trait_change("wave, wave.[], fftsize")
    def draw(self):
        fftsize = self.fftsize
        x_data = np.arange(0, 1.0, 1.0 / self.fftsize)
        func = self.wave.get_func()
        # 将func函数的返回值强制转换成float64
        y_data = func(x_data).astype(float)

        # 计算频谱
        fft_parameters = np.fft.fft(y_data) / len(y_data)
        self.fft_parameters = fft_parameters

        # 计算各个频率的振幅
        fft_data = np.clip(
            20 * np.log10(np.abs(fft_parameters))[:fftsize / 2 + 1], -120, 120)

        self.line_peaks.set_data(np.arange(0, len(fft_data)),
                                 fft_data)  # x坐标为频率编号
        self.line_wave.set_data(np.arange(0, self.fftsize), y_data)

        # 合成波的x坐标为取样点,显示2个周期
        self.line_wave2.set_xdata(np.arange(0, 2 * self.fftsize))

        # 将振幅大于-80dB的频率输出
        peak_index = (fft_data > -80)
        peak_value = fft_data[peak_index][:20]
        result = []
        for f, v in zip(np.flatnonzero(peak_index), peak_value):
            result.append("%02d : %g" % (f, v))
        self.peak_list = "\n".join(result)

        self.ax1.relim()
        self.ax1.autoscale_view()
        self.plot_sin_combine()

    @on_trait_change("N")
    def plot_sin_combine(self):
        index, data = fft_combine(self.fft_parameters, self.N, 2)
        self.line_wave2.set_ydata(data)
        self.ax2.relim()
        self.ax2.autoscale_view()
        if self.figure.canvas is not None:
            self.figure.canvas.draw()
Exemple #17
0
class Axis(HasTraits):
    axis = Int
    parent = Instance('Align')
    invert = Bool(value=False)

    ipw_3d = Instance(PipelineBase)
    ipw = Instance(PipelineBase)
    cursor = Instance(Module)
    surf = Instance(PipelineBase)
    outline = Instance(PipelineBase)
    slab = Instance(tvtk.ClipPolyData)
    handle = Instance(RotationWidget)
    planes = List

    scene_3d = DelegatesTo('parent')
    position = DelegatesTo('parent')
    disable_render = DelegatesTo('parent')
    xfm = DelegatesTo('parent')

    outline_color = DelegatesTo('parent')
    outline_rep = DelegatesTo('parent')
    line_width = DelegatesTo('parent')
    point_size = DelegatesTo('parent')

    def __init__(self, **kwargs):
        super(Axis, self).__init__(**kwargs)
        self.slab
        self.outline
        self.ipw_3d
        self.ipw
        self._last = -1
        self._keytime = None

        spacing = list(np.abs(self.parent.spacing))
        shape = list(self.parent.epi.shape)
        spacing.pop(self.axis)
        shape.pop(self.axis)
        if self.axis == 1:
            shape = shape[::-1]
            spacing = spacing[::-1]
        shape.append(0)
        spacing.append(1)
        self.spacing = np.array(spacing)
        self.shape = np.array(shape)

        self.handle

    def reset_view(self):
        center = self.shape * self.spacing / 2. + (self.shape +
                                                   1) % 2 * self.spacing / 2.
        width = (self.shape * self.spacing)[:2]
        width = np.min(width) * 0.5

        self.scene.scene.background = (0, 0, 0)
        mlab.view(*([(0, 0), (90, 0), (0, 0)][self.axis]),
                  focalpoint=center,
                  figure=self.scene.mayavi_scene)
        self.scene.scene.parallel_projection = True
        self.scene.scene.camera.parallel_scale = width * 1.2
        self.scene.scene.interactor.interactor_style = tvtk.InteractorStyleImage(
        )

        try:  #WX window
            self.scene.scene_editor.control.SetFocusFromKbd

            def focusfunc(vtkobj, i):
                self.scene.scene_editor.control.SetFocusFromKbd()
        except AttributeError:  #QT window
            self.scene.scene_editor.control.setFocus

            def focusfunc(vtkobj, i):
                self.scene.scene_editor.control.setFocus()

        self.scene.interactor.add_observer("MouseMoveEvent", focusfunc)
        self.scene.interactor.add_observer("KeyReleaseEvent", self.handle_keys)
        self._outline_color_changed()

    def handle_keys(self, evt, name):
        key, sym = evt.GetKeyCode(), evt.GetKeySym()
        #print repr(key), repr(sym), evt.GetShiftKey(), evt.GetControlKey()

        if key in ('', chr(127)):
            i = -1 if self.invert else 1
            mult = (2, .2)[evt.GetShiftKey()]
            smult = (1.1, 1.01)[evt.GetShiftKey()]

            rotccw, rotcw = "Insert", "Prior"
            moves = dict(Up=(0, 1, 0),
                         Down=(0, -1, 0),
                         Left=(-1, 0, 0),
                         Right=(1, 0, 0))
            if self.invert:
                rotccw, rotcw = rotcw, rotccw
                moves = dict(Up=(1, 0, 0),
                             Down=(-1, 0, 0),
                             Left=(0, 1, 0),
                             Right=(0, -1, 0))

            if sym in moves:
                self.handle.move(np.array(moves[sym]) * mult)
            elif sym == rotccw:  #ins
                self.handle.move(angle=np.pi / 120. * i * mult)
            elif sym == rotcw:  #pgup
                self.handle.move(angle=-np.pi / 120. * i * mult)
            elif sym == "Del":
                self.transform(scale=(smult, 1))
            elif sym == "Home":
                self.transform(scale=(1, smult))
            elif sym == "End":
                self.transform(scale=(1, 1 / smult))
            elif sym == "Next":
                self.transform(scale=(1 / smult, 1))
        elif key == ']':
            self.next_slice()
        elif key == '[':
            self.prev_slice()
        elif key == 'H':
            self.parent.outlines_visible = not self.parent.outlines_visible
        elif key == 'Z' and evt.GetControlKey() == 1:
            self.parent.undo()

        #clear out key buffer, otherwise the ctrl release will have the wrong state
        evt.SetKeyEventInformation(0, 0, '', 0, '')

    @on_trait_change("parent.scene_3d.activated")
    def activate_3d(self):
        self.ipw_3d.ipw.interaction = 0
        self.surf

    def _planes_default(self):
        pos = [0, 0, 0]
        vec = [0, 0, 0]
        off = (self.parent.epi.shape[self.axis] + 1) % 2 * abs(
            self.parent.spacing[self.axis]) / 2.
        vec[self.axis] = 1
        pos[self.axis] = off + abs(self.parent.spacing[self.axis]) / 2.
        top = tvtk.Planes(normals=[vec[:]], points=[pos[:]])
        vec[self.axis] = -1
        pos[self.axis] = off - abs(self.parent.spacing[self.axis]) / 2.
        bot = tvtk.Planes(normals=[vec[:]], points=[pos[:]])
        return [top, bot]

    def _slab_default(self):
        top = tvtk.ClipPolyData(
            clip_function=self.planes[0],
            inside_out=1,
            input=self.parent.surf.parent.parent.filter.output)
        bot = tvtk.ClipPolyData(clip_function=self.planes[1],
                                inside_out=1,
                                input=top.output)
        bot.update()
        return bot

    def _outline_default(self):
        origin, spacing = self.parent.origin, self.parent.spacing
        translate = origin * np.sign(spacing) - np.abs(spacing) / 2.

        mlab.figure(self.scene.mayavi_scene)
        if self.slab.output.points is None or len(self.slab.output.points) < 3:
            pts = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
            polys = [[0, 1, 2]]
        else:
            pts = self.slab.output.points.to_array()
            polys = self.slab.output.polys.to_array().reshape(-1, 4)[:, 1:]
        src = mlab.pipeline.triangular_mesh_source(
            pts[:, 0],
            pts[:, 1],
            pts[:, 2],
            polys,
            figure=self.scene.mayavi_scene)
        xfm = mlab.pipeline.transform_data(src, figure=self.scene.mayavi_scene)
        xfm.filter.transform.post_multiply()
        xfm.filter.transform.translate(-translate)
        xfm.widget.enabled = False
        surf = mlab.pipeline.surface(xfm,
                                     figure=self.scene.mayavi_scene,
                                     color=(1, 1, 1),
                                     representation=self.outline_rep)
        surf.actor.property.line_width = self.line_width
        surf.actor.property.point_size = self.point_size
        return src

    def _surf_default(self):
        if self.slab.output.points is None or len(self.slab.output.points) < 3:
            pts = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
            polys = [[0, 1, 2]]
        else:
            pts = self.slab.output.points.to_array()
            polys = self.slab.output.polys.to_array().reshape(-1, 4)[:, 1:]
        src = mlab.pipeline.triangular_mesh_source(
            pts[:, 0],
            pts[:, 1],
            pts[:, 2],
            polys,
            figure=self.scene_3d.mayavi_scene)
        surf = mlab.pipeline.surface(src,
                                     color=(1, 1, 1),
                                     figure=self.scene_3d.mayavi_scene,
                                     representation=self.outline_rep)
        surf.actor.property.line_width = self.line_width
        surf.actor.property.point_size = self.point_size
        return src

    def _ipw_3d_default(self):
        spos = self.position + self.parent.origin * np.sign(
            self.parent.spacing)
        space = list(np.abs(self.parent.spacing))
        shape = list(np.array(self.parent.epi.shape) / self.parent.padshape)
        space.pop(self.axis)
        shape.pop(self.axis)
        if self.axis == 1:
            space = space[::-1]
            shape = shape[::-1]

        space.append(1)
        shape = [(0, 0), (shape[0], 0), (0, shape[1]), (shape[0], shape[1])]
        origin = [space[0] / 2., space[1] / 2., 0]

        self.ipw_space = (space, shape)

        ipw = mlab.pipeline.image_plane_widget(
            self.parent.epi_src,
            figure=self.scene_3d.mayavi_scene,
            plane_orientation='%s_axes' % 'xyz'[self.axis],
            name='Cut %s' % self.axis)
        ipw.ipw.color_map.output_format = 'rgb'
        ipw.ipw.set(texture_interpolate=0,
                    reslice_interpolate='nearest_neighbour',
                    slice_position=spos[self.axis])
        ipw.ipw.reslice.set(output_spacing=space, output_origin=origin)
        ipw.ipw.poly_data_algorithm.output.point_data.t_coords = shape
        return ipw

    def _ipw_default(self):
        extent = list(np.abs(self.parent.epi.shape * self.parent.spacing))
        extent.pop(self.axis)
        if self.axis == 1:
            extent = extent[::-1]

        side_src = self.ipw_3d.ipw.reslice_output

        # Add a callback on the image plane widget interaction to
        # move the others
        def move_view(obj, evt):
            # Disable rendering on all scene
            cpos = obj.GetCurrentCursorPosition()
            position = list(cpos * side_src.spacing)[:2]
            position.insert(self.axis, self.position[self.axis])
            # We need to special case y, as the view has been rotated.
            if self.axis == 1:
                position = position[::-1]

            self.position = position

        ipw = mlab.pipeline.image_plane_widget(side_src,
                                               plane_orientation='z_axes',
                                               figure=self.scene.mayavi_scene,
                                               name='Cut view %s' % self.axis)
        ipw.ipw.plane_property.opacity = 0
        ipw.ipw.selected_plane_property.opacity = 0
        ipw.ipw.poly_data_algorithm.set(point1=[extent[0], 0, 0],
                                        point2=[0, extent[1], 0])
        ipw.ipw.set(left_button_action=0,
                    middle_button_auto_modifier=2,
                    right_button_auto_modifier=2,
                    texture_interpolate=0,
                    reslice_interpolate='nearest_neighbour')
        ipw.parent.scalar_lut_manager.set(use_default_range=False,
                                          default_data_range=[-1, 1],
                                          data_range=[-1, 1])
        ipw.ipw.add_observer('InteractionEvent', move_view)
        ipw.ipw.add_observer('StartInteractionEvent', move_view)
        return ipw

    def _cursor_default(self):
        return mlab.points3d(*self.position,
                             mode='axes',
                             color=(0, 0, 0),
                             scale_factor=2 * max(self.parent.epi[0].shape),
                             figure=self.scene.mayavi_scene,
                             name='Cursor view %s' % self.axis)

    def _handle_default(self):
        center = self.shape * self.spacing / 2. + (self.shape +
                                                   1) % 2 * self.spacing / 2.
        width = (self.shape * self.spacing)[:2]
        width = np.min(width) * 0.5

        def handlemove(handle, pos, angle, radius):
            self.transform(pos, angle, radius)

        return RotationWidget(self.scene.scene.mayavi_scene,
                              handlemove,
                              radius=width,
                              pos=center)

    def _disable_render_changed(self):
        self.scene.scene.disable_render = self.disable_render

    def toggle_outline(self):
        self.outline.children[0].children[
            0].visible = self.parent.outlines_visible

    def _outline_color_changed(self):
        try:
            if isinstance(self.outline_color, string_types):
                color = cc.to_rgb(self.outline_color)
            else:
                color = tuple([c / 255. for c in tuple(self.outline_color)])
        except TypeError:
            color = self.outline_color.getRgbF()[:3]
        self.surf.children[0].children[0].actor.property.color = color
        self.outline.children[0].children[0].children[
            0].actor.property.color = color

    def _outline_rep_changed(self):
        self.surf.children[0].children[
            0].actor.property.representation = self.outline_rep
        self.outline.children[0].children[0].children[
            0].actor.property.representation = self.outline_rep

    def _line_width_changed(self):
        self.surf.children[0].children[
            0].actor.property.line_width = self.line_width
        self.outline.children[0].children[0].children[
            0].actor.property.line_width = self.line_width

    def _point_size_changed(self):
        self.surf.children[0].children[
            0].actor.property.point_size = self.point_size
        self.outline.children[0].children[0].children[
            0].actor.property.point_size = self.point_size

    def next_slice(self):
        '''View the next slice'''
        pos = list(self.position)
        pos[self.axis] += np.abs(self.parent.spacing)[self.axis]
        self.position = pos

    def prev_slice(self):
        '''View the previous slice'''
        pos = list(self.position)
        pos[self.axis] -= np.abs(self.parent.spacing)[self.axis]
        self.position = pos

    def transform(self, pos=(0, 0), angle=0, scale=1):
        '''In-plane transformation function. Update the 3D transform based on the 2D changes'''
        center = self.shape * self.spacing / 2. + (self.shape +
                                                   1) % 2 * self.spacing / 2.
        inv = self.xfm.transform.homogeneous_inverse

        wpos = self.handle.center.representation.world_position
        wpos -= center
        if not isinstance(scale, (tuple, list, np.ndarray)):
            scale = [scale, scale]

        if self.axis == 1:
            trans = np.insert(pos[:2][::-1], self.axis, 0)
            wpos = np.insert(wpos[:2][::-1], self.axis,
                             self.ipw_3d.ipw.slice_position)
            #angle = -angle
        else:
            trans = np.insert(pos[:2], self.axis, 0)
            wpos = np.insert(wpos[:2], self.axis,
                             self.ipw_3d.ipw.slice_position)
        scale = np.insert(scale, self.axis, 1)

        self.parent._undolist.append(self.xfm.transform.matrix.to_array())

        self.xfm.transform.post_multiply()
        self.xfm.transform.translate(-wpos)
        self.xfm.transform.rotate_wxyz(np.degrees(angle),
                                       *self.ipw_3d.ipw.normal)
        self.xfm.transform.scale(scale)
        self.xfm.transform.translate(wpos)
        self.xfm.transform.translate(trans)
        self.xfm.transform.pre_multiply()

        self.xfm.widget.set_transform(self.xfm.filter.transform)
        self.xfm.update_pipeline()
        self.parent.update_slabs()

        np.save("/tmp/last_xfm.npy", self.parent.get_xfm())

    def update_position(self):
        """ Update the position of the cursors on each side view, as well
            as the image_plane_widgets in the 3D view.
        """

        offset = np.abs(self.parent.spacing) / 2
        p = list(self.position + offset)
        p.pop(self.axis)
        if self.axis == 1:
            p = p[::-1]
        p.append(0)
        self.cursor.parent.parent.data.points = [p]

        if self.position[self.axis] != self._last:
            self._last = self.position[self.axis]
            origin = self.parent.origin * np.sign(self.parent.spacing)

            space, shape = self.ipw_space
            self.ipw_3d.ipw.slice_position = self.position[self.axis] + origin[
                self.axis]
            self.ipw_3d.ipw.reslice.set(
                output_spacing=space,
                output_origin=[space[0] / 2., space[1] / 2., 0])
            self.ipw_3d.ipw.poly_data_algorithm.output.point_data.t_coords = shape
            self.ipw.ipw.poly_data_algorithm.output.point_data.t_coords = shape

            origin, spacing = self.parent.origin, self.parent.spacing
            origin = origin * np.sign(spacing) - np.abs(spacing) / 2.

            gap = abs(spacing[self.axis]) / 2.
            pos = self.ipw_3d.ipw.slice_position
            pts = [0, 0, 0]
            pts[self.axis] = pos + gap
            self.planes[0].points = [tuple(pts)]
            pts[self.axis] = pos - gap
            self.planes[1].points = [tuple(pts)]
            self.update_slab()

    def update_slab(self):
        self.slab.update()
        self.outline.data.set(points=self.slab.output.points,
                              polys=self.slab.output.polys)
        self.surf.data.set(points=self.slab.output.points,
                           polys=self.slab.output.polys)
Exemple #18
0
class CompositeGridModel(GridModel):
    """ A CompositeGridModel is a model whose underlying data is
    a collection of other grid models. """

    # The models this model is comprised of.
    data = List(Instance(GridModel))

    # The rows in the model.
    rows = Union(None, List(Instance(GridRow)))

    # The cached data indexes.
    _data_index = Dict()

    # ------------------------------------------------------------------------
    # 'object' interface.
    # ------------------------------------------------------------------------
    def __init__(self, **traits):
        """ Create a CompositeGridModel object. """

        # Base class constructor
        super(CompositeGridModel, self).__init__(**traits)

        self._row_count = None

    # ------------------------------------------------------------------------
    # 'GridModel' interface.
    # ------------------------------------------------------------------------
    def get_column_count(self):
        """ Return the number of columns for this table. """

        # for the composite grid model, this is simply the sum of the
        # column counts for the underlying models
        count = 0
        for model in self.data:
            count += model.get_column_count()

        return count

    def get_column_name(self, index):
        """ Return the name of the column specified by the
        (zero-based) index. """

        model, new_index = self._resolve_column_index(index)

        return model.get_column_name(new_index)

    def get_column_size(self, index):
        """ Return the size in pixels of the column indexed by col.
            A value of -1 or None means use the default. """

        model, new_index = self._resolve_column_index(index)
        return model.get_column_size(new_index)

    def get_cols_drag_value(self, cols):
        """ Return the value to use when the specified columns are dragged or
        copied and pasted. cols is a list of column indexes. """

        values = []
        for col in cols:
            model, real_col = self._resolve_column_index(col)
            values.append(model.get_cols_drag_value([real_col]))

        return values

    def get_cols_selection_value(self, cols):
        """ Return the value to use when the specified cols are selected.
        This value should be enough to specify to other listeners what is
        going on in the grid. rows is a list of row indexes. """

        return self.get_cols_drag_value(self, cols)

    def get_column_context_menu(self, col):
        """ Return a MenuManager object that will generate the appropriate
        context menu for this column."""

        model, new_index = self._resolve_column_index(col)

        return model.get_column_context_menu(new_index)

    def sort_by_column(self, col, reverse=False):
        """ Sort model data by the column indexed by col. The reverse flag
        indicates that the sort should be done in reverse. """
        pass

    def is_column_read_only(self, index):
        """ Return True if the column specified by the zero-based index
        is read-only. """
        model, new_index = self._resolve_column_index(index)

        return model.is_column_read_only(new_index)

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

        # see if we've already calculated the row_count
        if self._row_count is None:
            row_count = 0
            # return the maximum rows of any of the contained models
            for model in self.data:
                rows = model.get_row_count()
                if rows > row_count:
                    row_count = rows

            # save the result for next time
            self._row_count = row_count

        return self._row_count

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

        label = None
        # if the rows list exists then grab the label from there...
        if self.rows is not None:
            if len(self.rows) > index:
                label = self.rows[index].label
        # ... otherwise generate it from the zero-based index.
        else:
            label = str(index + 1)

        return label

    def get_rows_drag_value(self, rows):
        """ Return the value to use when the specified rows are dragged or
        copied and pasted. rows is a list of row indexes. """
        row_values = []
        for rindex in rows:
            row = []
            for model in self.data:
                new_data = model.get_rows_drag_value([rindex])
                # if it's a list then we assume that it represents more than
                # one column's worth of values
                if isinstance(new_data, list):
                    row.extend(new_data)
                else:
                    row.append(new_data)

            # now save our new row value
            row_values.append(row)

        return row_values

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

        read_only = False
        if self.rows is not None and len(self.rows) > index:
            read_only = self.rows[index].read_only

        return read_only

    def get_type(self, row, col):
        """ Return the type of the value stored in the table at (row, col). """
        model, new_col = self._resolve_column_index(col)

        return model.get_type(row, new_col)

    def get_value(self, row, col):
        """ Return the value stored in the table at (row, col). """
        model, new_col = self._resolve_column_index(col)

        return model.get_value(row, new_col)

    def get_cell_selection_value(self, row, col):
        """ Return the value stored in the table at (row, col). """
        model, new_col = self._resolve_column_index(col)

        return model.get_cell_selection_value(row, new_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. Note that the objects in selection_list are
        model-specific. """

        coords = []
        for selection in selection_list:
            # we have to look through each of the models in order
            # for the selected object
            for model in self.data:
                cells = model.resolve_selection([selection])
                # we know this model found the object if cells comes back
                # non-empty
                if cells is not None and len(cells) > 0:
                    coords.extend(cells)
                    break

        return coords

    # fixme: this context menu stuff is going in here for now, but it
    # seems like this is really more of a view piece than a model piece.
    # this is how the tree control does it, however, so we're duplicating
    # that here.
    def get_cell_context_menu(self, row, col):
        """ Return a MenuManager object that will generate the appropriate
        context menu for this cell."""

        model, new_col = self._resolve_column_index(col)

        return model.get_cell_context_menu(row, new_col)

    def is_cell_empty(self, row, col):
        """ Returns True if the cell at (row, col) has a None value,
        False otherwise."""
        model, new_col = self._resolve_column_index(col)

        if model is None:
            return True

        else:
            return model.is_cell_empty(row, new_col)

    def is_cell_editable(self, row, col):
        """ Returns True if the cell at (row, col) is editable,
        False otherwise. """
        model, new_col = self._resolve_column_index(col)

        return model.is_cell_editable(row, new_col)

    def is_cell_read_only(self, row, col):
        """ Returns True if the cell at (row, col) is not editable,
        False otherwise. """

        model, new_col = self._resolve_column_index(col)

        return model.is_cell_read_only(row, new_col)

    def get_cell_bg_color(self, row, col):
        """ Return a wxColour object specifying what the background color
            of the specified cell should be. """
        model, new_col = self._resolve_column_index(col)

        return model.get_cell_bg_color(row, new_col)

    def get_cell_text_color(self, row, col):
        """ Return a wxColour object specifying what the text color
            of the specified cell should be. """
        model, new_col = self._resolve_column_index(col)

        return model.get_cell_text_color(row, new_col)

    def get_cell_font(self, row, col):
        """ Return a wxFont object specifying what the font
            of the specified cell should be. """
        model, new_col = self._resolve_column_index(col)

        return model.get_cell_font(row, new_col)

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

            Return 'left' for left alignment, 'right' for right alignment,
            or 'center' for center alignment. """
        model, new_col = self._resolve_column_index(col)

        return model.get_cell_halignment(row, new_col)

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

            Return 'top' for top alignment, 'bottom' for bottom alignment,
            or 'center' for center alignment. """
        model, new_col = self._resolve_column_index(col)

        return model.get_cell_valignment(row, new_col)

    # ------------------------------------------------------------------------
    # protected 'GridModel' interface.
    # ------------------------------------------------------------------------
    def _delete_rows(self, pos, num_rows):
        """ Implementation method for delete_rows. Should return the
        number of rows that were deleted. """

        for model in self.data:
            model._delete_rows(pos, num_rows)

        return num_rows

    def _insert_rows(self, pos, num_rows):
        """ Implementation method for insert_rows. Should return the
        number of rows that were inserted. """

        for model in self.data:
            model._insert_rows(pos, num_rows)

        return num_rows

    def _set_value(self, row, col, value):
        """ Implementation method for set_value. Should return the
        number of rows, if any, that were appended. """

        model, new_col = self._resolve_column_index(col)
        model._set_value(row, new_col, value)
        return 0

    # ------------------------------------------------------------------------
    # private interface
    # ------------------------------------------------------------------------

    def _resolve_column_index(self, index):
        """ Resolves a column index into the correct model and adjusted
        index. Returns the target model and the corrected index. """

        real_index = index
        cached = None  # self._data_index.get(index)
        if cached is not None:
            model, col_index = cached
        else:
            model = None
            for m in self.data:
                cols = m.get_column_count()
                if real_index < cols:
                    model = m
                    break
                else:
                    real_index -= cols
            self._data_index[index] = (model, real_index)

        return model, real_index

    def _data_changed(self):
        """ Called when the data trait is changed.

        Since this is called when our underlying models change, the cached
        results of the column lookups is wrong and needs to be invalidated.
        """

        self._data_index.clear()

    def _data_items_changed(self):
        """ Called when the members of the data trait have changed.

        Since this is called when our underlying model change, the cached
        results of the column lookups is wrong and needs to be invalidated.
        """
        self._data_index.clear()
Exemple #19
0
class PreferenceDialog(SplitDialog):
    """ The preference dialog. """

    #### 'Dialog' interface ###################################################

    # The dialog title.
    title = Str('Preferences')

    #### 'SplitDialog' interface ##############################################

    # The ratio of the size of the left/top pane to the right/bottom pane.
    ratio = Float(0.25)

    #### 'PreferenceDialog' interface #########################################

    # The root of the preference hierarchy.
    root = Instance(PreferenceNode)

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

    # The preference pages in the dialog (they are created lazily).
    _pages = Dict

    # The current visible preference page.
    _current_page = Any

    ###########################################################################
    # Protected 'Dialog' interface.
    ###########################################################################

    def _create_buttons(self, parent):
        """ Creates the buttons. """

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        # 'Done' button.
        done = wx.Button(parent, wx.ID_OK, "Done")
        done.SetDefault()
        parent.Bind(wx.EVT_BUTTON, self._wx_on_ok, wx.ID_OK)
        sizer.Add(done)

        return sizer

    ###########################################################################
    # Protected 'SplitDialog' interface.
    ###########################################################################

    def _create_lhs(self, parent):
        """ Creates the panel containing the preference page tree. """

        return self._create_tree(parent)

    def _create_rhs(self, parent):
        """ Creates the panel containing the selected preference page. """

        panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN)
        sizer = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(sizer)
        panel.SetAutoLayout(True)

        # The 'pretty' title bar ;^)
        self.__title = HeadingText(panel)
        sizer.Add(self.__title.control, 0,
                  wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        # The preference page of the node currently selected in the tree.
        self._layered_panel = LayeredPanel(panel, min_width=-1, min_height=-1)
        sizer.Add(self._layered_panel.control, 1,
                  wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)

        # The 'Restore Defaults' button etc.
        buttons = self._create_page_buttons(panel)
        sizer.Add(buttons, 0, wx.ALIGN_RIGHT | wx.TOP | wx.RIGHT, 5)

        # A separator.
        line = wx.StaticLine(panel, -1)
        sizer.Add(line, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)

        # Resize the panel to fit the sizer's minimum size.
        sizer.Fit(panel)

        return panel

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

    def _create_tree(self, parent):
        """ Creates the preference page tree. """

        tree_viewer = TreeViewer(parent,
                                 input=self.root,
                                 show_images=False,
                                 show_root=False,
                                 content_provider=DefaultTreeContentProvider())

        tree_viewer.on_trait_change(self._on_selection_changed, 'selection')

        return tree_viewer.control

    def _create_page_buttons(self, parent):
        """ Creates the 'Restore Defaults' button, etc.

        At the moment the "etc." is an optional 'Help' button.

        """

        self._button_sizer = sizer = wx.BoxSizer(wx.HORIZONTAL)

        # 'Help' button. Comes first so 'Restore Defaults' doesn't jump around.
        self._help = help = wx.Button(parent, -1, "Help")
        parent.Bind(wx.EVT_BUTTON, self._on_help, help.GetId())
        sizer.Add(help, 0, wx.RIGHT, 5)

        # 'Restore Defaults' button.
        restore = wx.Button(parent, -1, "Restore Defaults")
        parent.Bind(wx.EVT_BUTTON, self._on_restore_defaults, restore.GetId())
        sizer.Add(restore)

        return sizer

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

    def _on_restore_defaults(self, event):
        """ Called when the 'Restore Defaults' button is pressed. """

        page = self._pages[self._layered_panel.current_layer_name]
        page.restore_defaults()

        return

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

        page = self._pages[self._layered_panel.current_layer_name]
        page.show_help_topic()

        return

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

    def _on_selection_changed(self, selection):
        """ Called when a node in the tree is selected. """

        if len(selection) > 0:
            # The tree is in single selection mode.
            node = selection[0]

            # We only show the help button if the selected node has a help
            # topic Id.
            if len(node.help_id) > 0:
                self._button_sizer.Show(self._help, True)

            else:
                self._button_sizer.Show(self._help, False)

            # Show the selected preference page.
            layered_panel = self._layered_panel
            parent = self._layered_panel.control

            # If we haven't yet displayed the node's preference page during the
            # lifetime of this dialog, then we have to create it.
            if not layered_panel.has_layer(node.name):
                page = node.create_page()
                layered_panel.add_layer(node.name, page.create_control(parent))
                self._pages[node.name] = page

            layered_panel.show_layer(node.name)
            self.__title.text = node.name

        return
class ExecutionModel(HasTraits):

    """ TO DO:
            - Generate Block from self.statements
    """

    implements(IExecutable)

    # List of function calls, groups, and expressions
    # These must all have inputs, outputs, uuid
    statements = List(Any)

    # List of Groups present in the exec model identified by their UUID
    groups = Property(depends_on=['statements'])
    _groups = List(Instance(UUID))
    
    # Topologically sorted list of function calls in statements
    sorted_statements = Property(depends_on=['statements', 'statements_items'])

    # Dependency Graph of all the statements in the body
    dep_graph = Property(depends_on=['statements', 'statements_items'])

    # The imports and local definitions
    imports_and_locals = Property(depends_on=['statements', 'statements_items'])

    # The body of the code corresponding to the statements
    body = Property(depends_on=['statements', 'statements_items'])

    # All the code put together = imports + local_defs + body
    code = Property(depends_on=['statements', 'statements_items',
        'statements.call_signature', 'statements.inputs.binding',
        'statements.outputs.binding'])

    # The block corresponding to the source code:
    block = Property(depends_on=['statements'])

    # Flag to impose if the code can be executed or not. 
    # It is needed to speed up the use of the canvas when we are adding new 
    # blocks instead of executing the code. 
    allow_execute = Bool(True)
        
    #---------------------------------------------------------------------------
    #  object interface:
    #---------------------------------------------------------------------------

    #--- Class Methods -- constructors -----------------------------------------

    @classmethod
    def from_code(cls, code):
        """ Create an ExecutionModel object from the code.
            Get the ast for the code and pass to from_ast() class method.
        """

        ast = compiler.parse(code)
        return ExecutionModel.from_ast(ast)

    @classmethod
    def from_file(cls, filename):
        ast = compiler.parseFile(filename)
        return ExecutionModel.from_ast(ast)

    @classmethod
    def from_ast(cls, ast):
        """ Create an ExecutionModel object from the ast. """

        # Build dictionaries for import information and local defs
        info = find_functions(ast)
        
        statement_info = walk(ast, StatementWalker())

        statements = parse_stmts_info(statement_info,info)

        return cls(statements=statements)
    
    def generate_unique_function_name(self,base_name=""):
        """ Returns a unique name for a new function based on the names of
        existing functions and imports in the code.
        """
        statements = self.statements
        functions = funcs_name_retrieve(statements)
        
        # Basic name generation method: template + counter
        if base_name == "":
            base_name = "new_function"
        if base_name not in functions:
            return base_name
        i = 1
        while base_name + str(i) in functions:
            i += 1
        return base_name + str(i)

    #---------------------------------------------------------------------------
    # IExecutable interface
    #---------------------------------------------------------------------------

    def execute(self, context, globals=None, inputs=None, outputs=None):
        """ Execute the code in the given context.

        Parameters
        ----------
        context : sufficiently dict-like object
            The namespace to execute the code in.
        globals : dict, optional
            The global namespace for the code.
        inputs : list of str, optional
            Names that can be used to restrict the execution to portions of the
            code. Ideally, only code that is affected by these inputs variables
            is executed.
        outputs : list of str, optional
            Names that can be used to restrict the execution to portions of the
            code. Ideally, only code that affects these outputs variables is 
            executed.
        """
        
        if not self.allow_execute:
            return
         
        if globals is None:
            globals = {}

        if inputs is not None or outputs is not None:
            # Only do this if we have to.
            restricted = self.restricted(inputs=inputs, outputs=outputs)
        else:
            restricted = self

        # Only execute the portions of the code which can be executed given the
        # names in the context.
        available_names = set(context.keys())
        required_names, _ = restricted.mark_unsatisfied_inputs(
            available_names)
        if required_names:
            bad = restricted.restricted(inputs=required_names)
            good_statements = [stmt for stmt in restricted.statements if stmt not in bad.statements]
            restricted = self.__class__(statements=good_statements)

        # Big optimization with small effort! The exec command works 
        # really bad when the passed context contains "big" object like
        # that produced by our computations. We remove all of them that are 
        # going to be overwritten. 
        restricted._clean_old_results_from_context(context)

        try:
            t_in = time.time()
            # This is likely the most important line in block canvas
            exec restricted.code in globals, context
            t_out = time.time()
            print '%f seconds: Execution time' % (t_out-t_in)
            
        except Exception, _:
            print 'Got exception from code:'
            print restricted.code
            print
            traceback.print_exc()
Exemple #21
0
class DrawingCanvas(Container):
    """
    A DrawingCanvas has some buttons which toggle what kind of drawing tools
    are active on the canvas, then allow arbitrary painting on the canvas.
    """

    # The active tool is the primary interactor on the canvas.  It gets
    # a chance to handle events before they are passed on to other components
    # and listener tools.
    active_tool = Any

    # Listening tools are always enabled and get all events (unless the active
    # tool has vetoed it), but they cannot prevent other tools from getting events.
    listening_tools = List

    # The background color of the canvas
    bgcolor = ColorTrait("white")

    toolbar = Instance(DrawingCanvasToolbar, args=())

    fit_window = True

    def dispatch(self, event, suffix):
        # See if the event happened on the toolbar:

        event.offset_xy(*self.position)
        if self.toolbar.is_in(event.x, event.y):
            self.toolbar.dispatch(event, suffix)
        event.pop()
        if event.handled:
            return

        if self.active_tool is not None:
            self.active_tool.dispatch(event, suffix)

        if event.handled:
            return

        for tool in self.listening_tools:
            tool.dispatch(event, suffix)

        super(DrawingCanvas, self).dispatch(event, suffix)
        return

    def activate(self, tool):
        """
        Makes the indicated tool the active tool on the canvas and moves the
        current active tool back into the list of tools.
        """
        self.active_tool = tool
        return

    def _draw_container_mainlayer(self, gc, view_bounds=None, mode="default"):
        active_tool = self.active_tool
        if active_tool and active_tool.draw_mode == "exclusive":
            active_tool.draw(gc, view_bounds, mode)
        else:
            #super(DrawingCanvas, self)._draw(gc, view_bounds, mode)
            for tool in self.listening_tools:
                tool.draw(gc, view_bounds, mode)
            if active_tool:
                active_tool.draw(gc, view_bounds, mode)

            self.toolbar.draw(gc, view_bounds, mode)
        return

    def _draw_container_background(self, gc, view_bounds=None, mode="default"):
        if self.bgcolor not in ("clear", "transparent", "none"):
            with gc:
                gc.set_antialias(False)
                gc.set_fill_color(self.bgcolor_)
                gc.draw_rect((int(self.x), int(
                    self.y), int(self.width) - 1, int(self.height) - 1), FILL)
        return

    #------------------------------------------------------------------------
    # Event listeners
    #------------------------------------------------------------------------

    def _tools_items_changed(self):
        self.request_redraw()
        return
Exemple #22
0
class Glyph(Component):

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

    # Type of Glyph: 'tensor' or 'vector'
    glyph_type = Enum('vector',
                      'tensor',
                      desc='if the glyph is vector or tensor')

    # The scaling mode to use when scaling the glyphs.  We could have
    # used the glyph's own scale mode but it allows users to set the
    # mode to use vector components for the scaling which I'd like to
    # disallow.
    scale_mode = Trait(
        'scale_by_scalar',
        TraitRevPrefixMap({
            'scale_by_vector': 1,
            'scale_by_vector_components': 2,
            'data_scaling_off': 3,
            'scale_by_scalar': 0
        }),
        desc="if scaling is done using scalar or vector/normal magnitude")

    # The color mode to use when coloring the glyphs.  We could have
    # used the glyph's own color_mode trait but it allows users to set
    # the mode to use vector components for the scaling which I'd
    # like to disallow.
    color_mode = Trait(
        'color_by_scalar',
        TraitRevPrefixMap({
            'color_by_vector': 2,
            'color_by_scalar': 1,
            'no_coloring': 0
        }),
        desc="if coloring is done by scalar or vector/normal magnitude")
    color_mode_tensor = Trait(
        'scalar',
        TraitRevPrefixMap({
            'scalars': 1,
            'eigenvalues': 2,
            'no_coloring': 0
        }),
        desc="if coloring is done by scalar or eigenvalues")

    # Specify if the input points must be masked.  By mask we mean
    # that only a subset of the input points must be displayed.
    mask_input_points = Bool(False, desc="if input points are masked")

    # The MaskPoints filter.
    mask_points = Instance(tvtk.MaskPoints,
                           args=(),
                           kw={'random_mode': True},
                           record=True)

    # The Glyph3D instance.
    glyph = Instance(tvtk.Object, allow_none=False, record=True)

    # The Source to use for the glyph.  This is chosen from
    # `self._glyph_list` or `self.glyph_dict`.
    glyph_source = Instance(glyph_source.GlyphSource,
                            allow_none=False,
                            record=True)

    # The module associated with this component.  This is used to get
    # the data range of the glyph when the scale mode changes.  This
    # *must* be set if this module is to work correctly.
    module = Instance(Module)

    # Should we show the GUI option for changing the scalar mode or
    # not?  This is useful for vector glyphing modules where there it
    # does not make sense to scale the data based on scalars.
    show_scale_mode = Bool(True)

    ########################################
    # Private traits.

    # Used for optimization.
    _updating = Bool(False)

    ########################################
    # View related traits.

    view = View(Group(
        Item(name='mask_input_points'),
        Group(
            Item(name='mask_points',
                 enabled_when='object.mask_input_points',
                 style='custom',
                 resizable=True),
            show_labels=False,
        ),
        label='Masking',
    ),
                Group(
                    Group(
                        Item(name='scale_mode',
                             enabled_when='show_scale_mode',
                             visible_when='show_scale_mode'),
                        Item(name='color_mode',
                             enabled_when='glyph_type == "vector"',
                             visible_when='glyph_type == "vector"'),
                        Item(name='color_mode_tensor',
                             enabled_when='glyph_type == "tensor"',
                             visible_when='glyph_type == "tensor"'),
                    ),
                    Group(Item(name='glyph', style='custom', resizable=True),
                          show_labels=False),
                    label='Glyph',
                    selected=True,
                ),
                Group(
                    Item(name='glyph_source', style='custom', resizable=True),
                    show_labels=False,
                    label='Glyph Source',
                ),
                resizable=True)

    ######################################################################
    # `object` interface
    ######################################################################
    def __get_pure_state__(self):
        d = super(Glyph, self).__get_pure_state__()
        for attr in ('module', '_updating'):
            d.pop(attr, None)
        return d

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

        This method is invoked when the object is initialized via
        `__init__`.  Note that at the time this method is called, the
        tvtk data pipeline will *not* yet be setup.  So upstream data
        will not be available.  The idea is that you simply create the
        basic objects and setup those parts of the pipeline not
        dependent on upstream sources and filters.  You should also
        set the `actors` attribute up at this point.
        """
        self._glyph_type_changed(self.glyph_type)
        self.glyph_source = glyph_source.GlyphSource()

        # Handlers to setup our source when the sources pipeline changes.
        self.glyph_source.on_trait_change(self._update_source,
                                          'pipeline_changed')
        self.mask_points.on_trait_change(self.render)

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

        This method is invoked (automatically) when any of the inputs
        sends a `pipeline_changed` event.
        """
        if ((len(self.inputs) == 0) or (len(self.inputs[0].outputs) == 0)):
            return

        self._mask_input_points_changed(self.mask_input_points)
        if self.glyph_type == 'vector':
            self._color_mode_changed(self.color_mode)
        else:
            self._color_mode_tensor_changed(self.color_mode_tensor)
        self._scale_mode_changed(self.scale_mode)

        # Set our output.
        tvtk_common.configure_outputs(self, self.glyph)
        self.pipeline_changed = True

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

        This method is invoked (automatically) when any of the inputs
        sends a `data_changed` event.
        """
        self._scale_mode_changed(self.scale_mode)
        self.data_changed = True

    def render(self):
        if not self._updating:
            super(Glyph, self).render()

    def start(self):
        """Overridden method.
        """
        if self.running:
            return
        self.glyph_source.start()
        super(Glyph, self).start()

    def stop(self):
        if not self.running:
            return
        self.glyph_source.stop()
        super(Glyph, self).stop()

    def has_output_port(self):
        """ The filter has an output port."""
        return True

    def get_output_object(self):
        """ Returns the output port."""
        return self.glyph.output_port

    ######################################################################
    # Non-public methods.
    ######################################################################
    def _update_source(self):
        self.configure_source_data(self.glyph, self.glyph_source.outputs[0])

    def _glyph_source_changed(self, value):
        self.configure_source_data(self.glyph, value.outputs[0])

    def _color_mode_changed(self, value):
        if len(self.inputs) == 0:
            return
        if value != 'no_coloring':
            self.glyph.color_mode = value

    def _color_mode_tensor_changed(self, value):
        if len(self.inputs) == 0:
            return
        self._updating = True
        if value != 'no_coloring':
            self.glyph.color_mode = value
            self.glyph.color_glyphs = True
        else:
            self.glyph.color_glyphs = False
        self._updating = False
        self.render()

    def _scale_mode_changed(self, value):
        if (self.module is None) or (len(self.inputs) == 0)\
                                 or self.glyph_type == 'tensor':
            return

        self._updating = True
        try:
            glyph = self.glyph
            glyph.scale_mode = value

            mm = self.module.module_manager
            if glyph.scale_mode == 'scale_by_scalar':
                glyph.range = tuple(mm.scalar_lut_manager.data_range)
            else:
                glyph.range = tuple(mm.vector_lut_manager.data_range)
        finally:
            self._updating = False
            self.render()

    def _mask_input_points_changed(self, value):
        inputs = self.inputs
        if len(inputs) == 0:
            return
        if value:
            mask = self.mask_points
            tvtk_common.configure_input(mask, inputs[0].outputs[0])
            self.configure_connection(self.glyph, mask)
        else:
            self.configure_connection(self.glyph, inputs[0])
        self.glyph.update()

    def _glyph_type_changed(self, value):
        if self.glyph_type == 'vector':
            self.glyph = tvtk.Glyph3D(clamping=True)
        else:
            self.glyph = tvtk.TensorGlyph(scale_factor=0.1)
            self.show_scale_mode = False
        self.glyph.on_trait_change(self.render)

    def _scene_changed(self, old, new):
        super(Glyph, self)._scene_changed(old, new)
        self.glyph_source.scene = new
Exemple #23
0
class DashboardServer(Loggable):
    devices = List
    selected_device = Instance(DashboardDevice)
    db_manager = Instance(DashboardDBManager, ())
    notifier = Instance(Notifier, ())

    url = Str

    _alive = False

    def activate(self):
        self._load_devices()
        if self.devices:
            self.setup_database()
            self.setup_notifier()
            self.start_poll()

    def deactivate(self):
        self.db_manager.stop()

    def setup_database(self):
        self.db_manager.start()

    def setup_notifier(self):
        parser = self._get_parser()

        port = 8100
        elem = parser.get_elements('port')
        if elem is not None:
            try:
                port = int(elem[0].text.strip())
            except ValueError:
                pass

        self.notifier.port = port
        host = gethostbyname(gethostname())
        self.url = '{}:{}'.format(host, port)
        #add a config request handler
        self.notifier.add_request_handler('config', self._handle_config)

    def start_poll(self):
        self.info('starting dashboard poll')
        self._alive = True
        t = Thread(name='poll', target=self._poll)

        t.setDaemon(1)
        t.start()

    def _load_devices(self):
        #read devices from config

        #get device from app
        app = self.application

        parser = self._get_parser()
        ds = []
        for dev in parser.get_elements('device'):
            name = dev.text.strip()

            dname = dev.find('name')
            if dname is None:
                self.warning(
                    'no device name for {}. use a <name> tag'.format(name))
                continue

            dev_name = dname.text.strip()
            device = None
            if app:
                device = app.get_service(ICoreDevice,
                                         query='name=="{}"'.format(dev_name))
                if device is None:
                    self.warning(
                        'no device named "{}" available'.format(dev_name))
                    continue

            enabled = dev.find('use')
            if enabled is not None:
                enabled = to_bool(enabled.text.strip())

            d = DashboardDevice(name=name, use=bool(enabled), _device=device)

            for v in dev.findall('value'):

                n = v.text.strip()
                tag = '<{},{}>'.format(name, n)

                func_name = self._get_xml_value(v, 'func', 'get')
                period = self._get_xml_value(v, 'period', 60)

                if not period == 'on_change':
                    try:
                        period = int(period)
                    except ValueError:
                        period = 60

                enabled = to_bool(self._get_xml_value(v, 'enabled', False))
                timeout = self._get_xml_value(v, 'timeout', 0)
                d.add_value(n, tag, func_name, period, enabled, timeout)

            ds.append(d)

        self.devices = ds

    def _get_xml_value(self, elem, tag, default):
        ret = default

        tt = elem.find(tag)
        if not tt is None:
            ret = tt.text.strip()

        return ret

    def _handle_config(self):
        """
            return a pickled dictionary string
        """
        config = [pv for dev in self.devices for pv in dev.values]

        return pickle.dumps(config)

    def _poll(self):

        mperiod = min([v.period for dev in self.devices for v in dev.values])
        if mperiod == 'on_change':
            mperiod = 1

        self.debug('min period {}'.format(mperiod))
        while self._alive:
            for dev in self.devices:
                if not dev.use:
                    continue

                dev.trigger()
            time.sleep(mperiod)

    def _get_parser(self):
        p = os.path.join(paths.setup_dir, 'dashboard.xml')
        parser = XMLParser(p)
        return parser

    @on_trait_change('devices:publish_event')
    def _handle_publish(self, obj, name, old, new):
        self.notifier.send_message(new)
        self.db_manager.publish_device(obj)

    @on_trait_change('devices:values:+')
    def _value_changed(self, obj, name, old, new):
        if name.startswith('last_'):
            return

        print obj, name, old, new
Exemple #24
0
class KeithleyControllerUI(KeithleyController,BaseInstrumentUI):
    """Same as :class:`~.controller.KeithleyController`, but with added gui layer.
    See also :class:`.BaseInstrumentUI` for details. 
    """
    _time = List(Float)
    _data = List()
    _value_str = Str()
    _alarm = Bool
    
    #: here IO settings are defined
    io_settings = Instance(IOSettings,())
    initcmd = DelegatesTo('io_settings')
    
    #: inteerval at which data is collected
    interval = Range(0.01,10.,1.)
    #: actual data objoect used for measurements
    data = Instance(StructArrayData,()) 
    #: determines if loggging (data readout) is initiated.
    do_log = Bool(True, desc = 'whether to log data or not')
    
    view = View(Group(instrument_group,value_group, 
    Group(HGroup(Item('do_log'),'interval'),
    Item('data',style = 'custom', show_label = False), show_border = True, label = 'Log'),status_group))
    
    def init(self):
        """Opens connection to a device. IO parameters are defined in the :attr:`.io_settings`. 
        """
        if self.io_settings.instr != '':
            return super(KeithleyControllerUI, self).init(instr = self.io_settings.instr, timeout = self.io_settings.timeout, initcmd = self.io_settings.initcmd)
        else:
            return super(KeithleyControllerUI, self).init(timeout = self.io_settings.timeout,initcmd = self.io_settings.initcmd)
        
    def update(self):
        report = self.report()
        self.update_status_message(report)
        self.update_LED(report)
        self.update_alarm(report)
        self.update_value(report)
        
    def update_alarm(self, report):
        self._alarm = report.get('alarm', False) 
        
    def update_value(self, report):
        try:
            self._value_str = '%.6f' % report['value']
        except KeyError:
            pass
        if len(self._data) != 0 and self.do_log == True:
            data, self._data = self._data, []
            self.data.append(data)          
    
    def report(self):
        r = super(KeithleyControllerUI, self).report()
        try:
            #if logging is on.. get data from the storred data, else measure 1
            if self.do_log == True:
                r['time'], r['value'] = self._data[-1]
            else:
                r['time'], r['value']  = self.measure(1) 
        except IndexError:
            pass
        return r

    def __initialized_changed(self, value):
        if value == False:
            self._LED = 0
            self._status_message = ''
            self._value_str = ''  
            
    @display_exception('Could not initialize.')
    def _init_button_fired(self):
        if self.initialized:
            self.stop_readout()
            self.stop_timer()
            self.close()
        else:
            self.init()
            self.start_readout()
            self.start_timer() 
            
    def _io_button_fired(self):
        try:
            instr = visa.get_instruments_list()[0]
        except IndexError:
            instr = ''
        if self.io_settings.instr == '':
            self.io_settings.instr = instr
        if self.io_settings.edit_traits(kind = 'livemodal'):
            print('OK')
            
    def start_readout(self):
        t = ReadThread(self)
        t.daemon = True
        t.start()
        self._thread = t
        
    def stop_readout(self):
        try:
            self._thread.wants_to_stop.set()
        except AttributeError:
            pass
        self._data = []
                                                                             
    def _data_default(self):
        return StructArrayData(width = 420, height = 230, dtype = [('time','float'),('voltage','float')])
class DocAction(WorkbenchAction):
    """ (Pyface) Action for displaying a help doc.
    """


    ### Action interface ##############################################

    # Image associated with this action instance.
    image = Property

    ### IExtensionPointUser interface

    extension_registry = Property(Instance(IExtensionRegistry))

    def _get_extension_registry(self):
        return self.window.application.extension_registry

    def _get_image(self):
        """ Returns the image to be used for this DocAction instance.
        """
        # The current implementation searches for an image file matching
        # 'name' in all of the image paths. If such a file is not to be found,
        # the '_image_not_found' file for the DocImageResourceClass is used.
        return DocImageResource(self.name)

    ### HelpDocAction interface

    # Help doc associated with this action.
    my_help_doc = Instance(HelpDoc)

    def _my_help_doc_default(self):
        exns = self.extension_registry.get_extensions(PARENT + '.help_docs')
        for hd in exns:
            if hd.label == self.name:
                return hd
        return None

    def _get_filename(self, doc):
        filename = None
        if doc is not None:
            if doc.url:
                filename = doc.filename
            else:
                filename = get_sys_prefix_relative_filename(doc.filename)
        return filename

    def perform(self, event):
        """ Perform the action by displaying the document.
        """

        filename = self._get_filename(self.my_help_doc)
        if filename is not None:
            if self.my_help_doc.url or self.my_help_doc.viewer == 'browser':
                import webbrowser
                try:
                    webbrowser.open(filename)
                except (OSError, webbrowser.Error), msg:
                    logger.error('Could not open page in browser for '+ \
                        'Document "%s":\n\n' % self.my_help_doc.label + \
                        str(msg) + '\n\nTry changing Dcoument Preferences.')
            elif self.my_help_doc.viewer is not None:
                # Run the viewer, passing it the filename
                try:
                    Popen([self.my_help_doc.viewer, filename])
                except OSError, msg:
                    logger.error('Could not execute program for Document' + \
                        ' "%s":\n\n ' % self.my_help_doc.label + str(msg) + \
                        '\n\nTry changing Document Preferences.')
Exemple #26
0
class Visualize2dTask(Task):
    """ A task for visualizing attractors in 2D.
    """

    #### 'Task' interface #####################################################

    id = "example.attractors.task_2d"
    name = "2D Visualization"

    menu_bar = SMenuBar(
        SMenu(id="File", name="&File"),
        SMenu(id="Edit", name="&Edit"),
        SMenu(TaskToggleGroup(), id="View", name="&View"),
    )

    #### 'Visualize2dTask' interface ##########################################

    # The attractor model that is currently active (visible in the center
    # pane).
    active_model = Any

    # The list of available attractor models.
    models = List(Instance(IPlottable2d))

    ###########################################################################
    # 'Task' interface.
    ###########################################################################

    def create_central_pane(self):
        """ Create a plot pane with a list of models. Keep track of which model
            is active so that dock panes can introspect it.
        """
        pane = Plot2dPane(models=self.models)

        self.active_model = pane.active_model
        pane.on_trait_change(self._update_active_model, "active_model")

        return pane

    def create_dock_panes(self):
        return [
            ModelConfigPane(model=self.active_model),
            ModelHelpPane(model=self.active_model),
        ]

    ###########################################################################
    # Protected interface.
    ###########################################################################

    #### Trait initializers ###################################################

    def _default_layout_default(self):
        return TaskLayout(left=Tabbed(
            PaneItem("example.attractors.model_config_pane"),
            PaneItem("example.attractors.model_help_pane"),
        ))

    def _models_default(self):
        from model.henon import Henon
        from model.lorenz import Lorenz
        from model.rossler import Rossler

        models = [Henon(), Lorenz(), Rossler()]

        return [adapt(model, IPlottable2d) for model in models]

    #### Trait change handlers ################################################

    def _update_active_model(self):
        self.active_model = self.window.central_pane.active_model
        for dock_pane in self.window.dock_panes:
            dock_pane.model = self.active_model
class IsoDBTransfer(Loggable):
    """
    transfer analyses from an isotope_db database to a dvc database
    """
    dvc = Instance(DVC)
    processor = Instance(IsotopeDatabaseManager)
    persister = Instance(DVCPersister)

    quiet = False

    def init(self):
        conn = dict(host=os.environ.get('ARGONSERVER_HOST'),
                    username=os.environ.get('ARGONSERVER_DB_USER'),
                    password=os.environ.get('ARGONSERVER_DB_PWD'),
                    kind='mysql')

        self.dvc = DVC(bind=False,
                       organization='NMGRLData',
                       meta_repo_name='MetaData')
        paths.meta_root = os.path.join(paths.dvc_dir, self.dvc.meta_repo_name)

        use_local = True
        if use_local:
            dest_conn = dict(host='localhost',
                             username=os.environ.get('LOCALHOST_DB_USER'),
                             password=os.environ.get('LOCALHOST_DB_PWD'),
                             kind='mysql',
                             # echo=True,
                             name='pychrondvc_dev')
        else:
            dest_conn = conn.copy()
            dest_conn['name'] = 'pychrondvc'

        self.dvc.db.trait_set(**dest_conn)
        if not self.dvc.initialize():
            self.warning_dialog('Failed to initialize DVC')
            return

        self.dvc.meta_repo.smart_pull(quiet=self.quiet)
        self.persister = DVCPersister(dvc=self.dvc, stage_files=False)

        proc = IsotopeDatabaseManager(bind=False, connect=False)

        use_local_src = True
        if use_local_src:
            conn = dict(host='localhost',
                        username=os.environ.get('LOCALHOST_DB_USER'),
                        password=os.environ.get('LOCALHOST_DB_PWD'),
                        kind='mysql',
                        # echo=True,
                        name='pychrondata')
        else:
            conn['name'] = 'pychrondata'

        proc.db.trait_set(**conn)
        src = proc.db
        src.connect()
        self.processor = proc

    def copy_productions(self):
        src = self.processor.db
        dvc = self.dvc
        dest = dvc.db
        with src.session_ctx():
            pr = src.get_irradiation_productions()
            for p in pr:
                # copy to database
                pname = p.name.replace(' ', '_')
                if not dest.get_production(pname):
                    dest.add_production(pname)

                # copy to meta
                dvc.copy_production(p)

    def set_spectrometer_files(self, repository):
        set_spectrometer_files(self.processor.db,
                               self.dvc.db,
                               repository,
                               os.path.join(paths.repository_dataset_dir, repository))

    def bulk_import_irradiations(self, irradiations, creator, dry=True):

        # for i in xrange(251, 277):
        # for i in xrange(258, 259):
        # for i in (258, 259, 260, 261,):
        # for i in (262, 263, 264, 265):
        # for i in (266, 267, 268, 269):
        # for i in (270, 271, 272, 273):
        for i in irradiations:
            irradname = 'NM-{}'.format(i)
            runs = self.bulk_import_irradiation(irradname, creator, dry=dry)
            # if runs:
            #     with open('/Users/ross/Sandbox/bulkimport/irradiation_runs.txt', 'a') as wfile:
            #         for o in runs:
            #             wfile.write('{}\n'.format(o))

    def bulk_import_irradiation(self, irradname, creator, dry=True):

        src = self.processor.db
        tol_hrs = 6
        self.debug('bulk import irradiation {}'.format(irradname))
        oruns = []
        ts, idxs = self._get_irradiation_timestamps(irradname, tol_hrs=tol_hrs)
        print ts
        repository_identifier = 'Irradiation-{}'.format(irradname)

        # add project
        with self.dvc.db.session_ctx():
            self.dvc.db.add_project(repository_identifier, creator)

        def filterfunc(x):
            a = x.labnumber.irradiation_position is None
            b = False
            if not a:
                b = x.labnumber.irradiation_position.level.irradiation.name == irradname

            d = False
            if x.extraction:
                ed = x.extraction.extraction_device
                if not ed:
                    d = True
                else:
                    d = ed.name == 'Fusions CO2'

            return (a or b) and d

        # for ms in ('jan', 'obama'):

        # monitors not run on obama
        for ms in ('jan',):
            for i, ais in enumerate(array_split(ts, idxs + 1)):
                if not ais.shape[0]:
                    self.debug('skipping {}'.format(i))
                    continue

                low = get_datetime(ais[0]) - timedelta(hours=tol_hrs / 2.)
                high = get_datetime(ais[-1]) + timedelta(hours=tol_hrs / 2.)
                with src.session_ctx():
                    ans = src.get_analyses_date_range(low, high,
                                                      mass_spectrometers=(ms,),
                                                      samples=('FC-2',
                                                               'blank_unknown', 'blank_air', 'blank_cocktail', 'air',
                                                               'cocktail'))

                    # runs = filter(lambda x: x.labnumber.irradiation_position is None or
                    #                         x.labnumber.irradiation_position.level.irradiation.name == irradname, ans)

                    runs = filter(filterfunc, ans)
                    if dry:
                        for ai in runs:
                            oruns.append(ai.record_id)
                            print ms, ai.record_id
                    else:
                        self.debug('================= Do Export i: {} low: {} high: {}'.format(i, low, high))
                        self.debug('N runs: {}'.format(len(runs)))
                        self.do_export([ai.record_id for ai in runs],
                                       repository_identifier, creator,
                                       monitor_mapping=('FC-2', 'Sanidine', repository_identifier))

        return oruns

    def bulk_import_project(self, project, principal_investigator, dry=True):
        src = self.processor.db
        tol_hrs = 6
        self.debug('bulk import project={}, pi={}'.format(project, principal_investigator))
        oruns = []

        repository_identifier = project

        # def filterfunc(x):
        #     a = x.labnumber.irradiation_position is None
        #     b = False
        #     if not a:
        #         b = x.labnumber.irradiation_position.level.irradiation.name == irradname
        #
        #     d = False
        #     if x.extraction:
        #         ed = x.extraction.extraction_device
        #         if not ed:
        #             d = True
        #         else:
        #             d = ed.name == 'Fusions CO2'
        #
        #     return (a or b) and d
        #
        for ms in ('jan', 'obama'):
            ts, idxs = self._get_project_timestamps(project, ms, tol_hrs=tol_hrs)
            for i, ais in enumerate(array_split(ts, idxs + 1)):
                if not ais.shape[0]:
                    self.debug('skipping {}'.format(i))
                    continue

                low = get_datetime(ais[0]) - timedelta(hours=tol_hrs / 2.)
                high = get_datetime(ais[-1]) + timedelta(hours=tol_hrs / 2.)

                print '========{}, {}, {}'.format(ms, low, high)
                with src.session_ctx():
                    runs = src.get_analyses_date_range(low, high,
                                                       projects=('REFERENCES', project),
                                                       mass_spectrometers=(ms,))

                    if dry:
                        for ai in runs:
                            oruns.append(ai.record_id)
                            print ai.measurement.mass_spectrometer.name, ai.record_id, ai.labnumber.sample.name, \
                                ai.analysis_timestamp
                    else:
                        self.debug('================= Do Export i: {} low: {} high: {}'.format(i, low, high))
                        self.debug('N runs: {}'.format(len(runs)))
                        self.do_export([ai.record_id for ai in runs], repository_identifier, principal_investigator)

        return oruns

    def find_project_overlaps(self, projects):
        tol_hrs = 6
        src = self.processor.db

        pdict = {}
        for p in projects:
            for ms in ('jan', 'obama'):
                ts, idxs = self._get_project_timestamps(p, ms, tol_hrs=tol_hrs)
                for i, ais in enumerate(array_split(ts, idxs + 1)):
                    if not ais.shape[0]:
                        self.debug('skipping {}'.format(i))
                        continue

                    low = get_datetime(ais[0]) - timedelta(hours=tol_hrs / 2.)
                    high = get_datetime(ais[-1]) + timedelta(hours=tol_hrs / 2.)

                    print '========{}, {}, {}'.format(ms, low, high)
                    with src.session_ctx():
                        runs = src.get_analyses_date_range(low, high,
                                                           projects=('REFERENCES',),
                                                           mass_spectrometers=(ms,))

                        pdict[p] = [ai.record_id for ai in runs]

        for p in projects:
            for o in projects:
                if p == o:
                    continue
                pruns = pdict[p]
                oruns = pdict[o]
                for ai in pruns:
                    if ai in oruns:
                        print p, o, ai

    def import_date_range(self, low, high, spectrometer, repository_identifier, creator):
        src = self.processor.db
        with src.session_ctx():
            runs = src.get_analyses_date_range(low, high, mass_spectrometers=spectrometer)

            ais = [ai.record_id for ai in runs]
        self.do_export(ais, repository_identifier, creator)

    def do_export(self, runs, repository_identifier, creator, create_repo=False, monitor_mapping=None):

        # self._init_src_dest()
        src = self.processor.db
        dest = self.dvc.db

        with src.session_ctx():
            key = lambda x: x.split('-')[0]
            runs = sorted(runs, key=key)
            with dest.session_ctx():
                repo = self._add_repository(dest, repository_identifier, creator, create_repo)

            self.persister.active_repository = repo
            self.dvc.current_repository = repo

            total = len(runs)
            j = 0

            for ln, ans in groupby(runs, key=key):
                ans = list(ans)
                n = len(ans)
                for i, a in enumerate(ans):
                    with dest.session_ctx() as sess:
                        st = time.time()
                        try:
                            if self._transfer_analysis(a, repository_identifier, monitor_mapping=monitor_mapping):
                                j += 1
                                self.debug('{}/{} transfer time {:0.3f}'.format(j, total, time.time() - st))
                        except BaseException, e:
                            import traceback
                            traceback.print_exc()
                            self.warning('failed transfering {}. {}'.format(a, e))
Exemple #28
0
class CameraWindow(HasTraits):
    """ CameraWindow class contains the relevant information and functions for a single camera window: image, zoom, pan
    important members:
        _plot_data  - contains image data to display (used by update_image)
        _plot - instance of Plot class to use with _plot_data
        _click_tool - instance of Clicker tool for the single camera window, to handle mouse processing
    """
    _plot_data = Instance(ArrayPlotData)
    _plot = Instance(Plot)
    _click_tool = Instance(Clicker)
    rclicked = Int(0)

    cam_color = ''

    name = Str
    view = View(Item(name='_plot', editor=ComponentEditor(), show_label=False))

    # view = View( Item(name='_plot',show_label=False) )

    def __init__(self, color):
        """ Initialization of plot system
        """
        padd = 25
        self._plot_data = ArrayPlotData()
        self._plot = Plot(self._plot_data, default_origin="top left")
        self._plot.padding_left = padd
        self._plot.padding_right = padd
        self._plot.padding_top = padd
        self._plot.padding_bottom = padd
        self.right_p_x0,self.right_p_y0,self.right_p_x1,self.right_p_y1,self._quiverplots=[],[],[],[],[]
        self.cam_color = color

    def attach_tools(self):
        """ attach_tools(self) contains the relevant tools: clicker, pan, zoom
        """
        self._click_tool = Clicker(self._img_plot)
        self._click_tool.on_trait_change(
            self.left_clicked_event,
            'left_changed')  #set processing events for Clicker
        self._click_tool.on_trait_change(self.right_clicked_event,
                                         'right_changed')
        self._img_plot.tools.append(self._click_tool)
        pan = PanTool(self._plot, drag_button='middle')
        zoom_tool = ZoomTool(self._plot, tool_mode="box", always_on=False)
        #       zoom_tool = BetterZoom(component=self._plot, tool_mode="box", always_on=False)
        zoom_tool.max_zoom_out_factor = 1.0  # Disable "bird view" zoom out
        self._img_plot.overlays.append(zoom_tool)
        self._img_plot.tools.append(pan)

    def left_clicked_event(
        self
    ):  #TODO: why do we need the clicker_tool if we can handle mouse clicks here?
        """ left_clicked_event - processes left click mouse avents and displays coordinate and grey value information
        on the screen
        """
        print("x = %d, y= %d, grey= %d " %
              (self._click_tool.x, self._click_tool.y,
               self._click_tool.data_value))
        #need to priny gray value

    def right_clicked_event(self):
        self.rclicked = 1  #flag that is tracked by main_gui, for right_click_process function of main_gui
        #self._click_tool.y,self.name])
        #self.drawcross("coord_x","coord_y",self._click_tool.x,self._click_tool.y,"red",5)
        #print ("right clicked, "+self.name)
        #need to print cross and manage other camera's crosses

    def update_image(self, image, is_float=False):
        """ update_image - displays/updates image in the curren camera window
        parameters:
            image - image data
            is_float - if true, displays an image as float array,
            else displays as byte array (B&W or gray)
        example usage:
            update_image(image,is_float=False)
        """
        print('image shape = ', image.shape, 'is_float =', is_float)
        if image.ndim > 2:
            image = img_as_ubyte(rgb2gray(image))
            is_float = False

        if is_float:
            self._plot_data.set_data('imagedata', image.astype(np.float))
        else:
            self._plot_data.set_data('imagedata', image.astype(np.byte))

        if not hasattr(
                self,
                '_img_plot'):  #make a new plot if there is nothing to update
            self._img_plot = Instance(ImagePlot)
            self._img_plot = self._plot.img_plot('imagedata', colormap=gray)[0]
            self.attach_tools()

        # self._plot.request_redraw()

    def drawcross(self, str_x, str_y, x, y, color1, mrk_size, marker1="plus"):
        """ drawcross draws crosses at a given location (x,y) using color and marker in the current camera window
        parameters:
            str_x - label for x coordinates
            str_y - label for y coordinates
            x - array of x coordinates
            y - array of y coordinates
            mrk_size - marker size
            makrer1 - type of marker, e.g "plus","circle"
        example usage:
            drawcross("coord_x","coord_y",[100,200,300],[100,200,300],2)
            draws plus markers of size 2 at points (100,100),(200,200),(200,300)
        """
        self._plot_data.set_data(str_x, x)
        self._plot_data.set_data(str_y, y)
        self._plot.plot((str_x, str_y),
                        type="scatter",
                        color=color1,
                        marker=marker1,
                        marker_size=mrk_size)
        #self._plot.request_redraw()

    def drawquiver(self, x1c, y1c, x2c, y2c, color, linewidth=1.0):
        """ drawquiver draws multiple lines at once on the screen x1,y1->x2,y2 in the current camera window
        parameters:
            x1c - array of x1 coordinates
            y1c - array of y1 coordinates
            x2c - array of x2 coordinates
            y2c - array of y2 coordinates
            color - color of the line
            linewidth - linewidth of the line
        example usage:
            drawquiver ([100,200],[100,100],[400,400],[300,200],'red',linewidth=2.0)
            draws 2 red lines with thickness = 2 :  100,100->400,300 and 200,100->400,200

        """
        x1, y1, x2, y2 = self.remove_short_lines(x1c, y1c, x2c, y2c)
        if len(x1) > 0:
            xs = ArrayDataSource(x1)
            ys = ArrayDataSource(y1)

            quiverplot=QuiverPlot(index=xs,value=ys,\
                                  index_mapper=LinearMapper(range=self._plot.index_mapper.range),\
                                  value_mapper=LinearMapper(range=self._plot.value_mapper.range),\
                                  origin = self._plot.origin,arrow_size=0,\
                                  line_color=color,line_width=linewidth,ep_index=np.array(x2),ep_value=np.array(y2)
                                  )
            self._plot.add(quiverplot)
            self._quiverplots.append(
                quiverplot
            )  #we need this to track how many quiverplots are in the current plot
            # import pdb; pdb.set_trace()

    def remove_short_lines(self, x1, y1, x2, y2):
        """ removes short lines from the array of lines
        parameters:
            x1,y1,x2,y2 - start and end coordinates of the lines
        returns:
            x1f,y1f,x2f,y2f - start and end coordinates of the lines, with short lines removed
        example usage:
            x1,y1,x2,y2=remove_short_lines([100,200,300],[100,200,300],[100,200,300],[102,210,320])
            3 input lines, 1 short line will be removed (100,100->100,102)
            returned coordinates:
            x1=[200,300]; y1=[200,300]; x2=[200,300]; y2=[210,320]
        """
        dx, dy = 2, 2  #minimum allowable dx,dy
        x1f, y1f, x2f, y2f = [], [], [], []
        for i in range(len(x1)):
            if abs(x1[i] - x2[i]) > dx or abs(y1[i] - y2[i]) > dy:
                x1f.append(x1[i])
                y1f.append(y1[i])
                x2f.append(x2[i])
                y2f.append(y2[i])
        return x1f, y1f, x2f, y2f

    def drawline(self, str_x, str_y, x1, y1, x2, y2, color1):
        """ drawline draws 1 line on the screen by using lineplot x1,y1->x2,y2
        parameters:
            str_x - label of x coordinate
            str_y - label of y coordinate
            x1,y1,x2,y2 - start and end coordinates of the line
            color1 - color of the line
        example usage:
            drawline("x_coord","y_coord",100,100,200,200,red)
            draws a red line 100,100->200,200
        """
        self._plot_data.set_data(str_x, [x1, x2])
        self._plot_data.set_data(str_y, [y1, y2])
        self._plot.plot((str_x, str_y), type="line", color=color1)
Exemple #29
0
class DesmoratModel(Simulator, Vis2D):

    node_name = Str('Bond slip model')

    tree_node_list = List([])

    def _tree_node_list_default(self):
        return [self.tline, self.mats_eval, self.loading_scenario]

    @on_trait_change('MAT,+BC')
    def _update_node_list(self):
        self.tree_node_list = [
            self.tline, self.mats_eval, self.loading_scenario
        ]

    interaction_type = Trait('predefined', {
        'interactive': TFunPWLInteractive,
        'predefined': LoadingScenario
    },
                             BC=True,
                             symbol='option',
                             unit='-',
                             desc=r'type of loading scenario, possible values:'
                             r'[predefined, interactive]')
    '''Type of control - either interactive or predefined.
    '''

    def _interaction_type_changed(self):
        self.loading_scenario = self.interaction_type_()

    loading_scenario = Instance(
        MFnLineArray,
        report=True,
        desc=r'object describing the loading scenario as a function')
    '''Loading scenario in form of a function that maps the time variable
    to the control slip.
    '''

    def _loading_scenario_default(self):
        return self.interaction_type_()

    material_model = Trait('plasticity', {
        'damage-plasticity': MATS1DDesmorat,
    },
                           enter_set=True,
                           auto_set=False,
                           MAT=True,
                           symbol='option',
                           unit='-',
                           desc=r'type of material model - possible values:'
                           r'[damage-plasticity]')
    '''Available material models.
    '''

    @on_trait_change('material_model')
    def _set_mats_eval(self):
        self.mats_eval = self.material_model_()
        self._update_node_list()

    mats_eval = Instance(IMATSEval,
                         report=True,
                         desc='object defining the material behavior')
    '''Material model'''

    def _mats_eval_default(self):
        return self.material_model_()

    material = Property

    def _get_material(self):
        return self.mats_eval

    sv_names = Property(List(Str), depends_on='material_model')
    '''Names of state variables of the current material model.
    '''

    @cached_property
    def _get_sv_names(self):
        return ['t', 's'] + self.mats_eval.sv_names

    sv_hist = Dict((Str, List))
    '''Record of state variables. The number of names of the variables
    depend on the active material model. See s_names.
    '''

    def _sv_hist_default(self):
        sv_hist = {}
        for sv_name in self.sv_names:
            sv_hist[sv_name] = []
        return sv_hist

    @on_trait_change('MAT,BC')
    def _sv_hist_reset(self):
        for sv_name in self.sv_names:
            self.sv_hist[sv_name] = []

    def paused(self):
        self._paused = True

    def stop(self):
        self._sv_hist_reset()
        self._restart = True
        self.loading_scenario.reset()

    _paused = Bool(False)
    _restart = Bool(True)

    n_steps = Int(1000,
                  ALG=True,
                  symbol='n_\mathrm{s}',
                  unit='-',
                  desc=r'number of increments',
                  enter_set=True,
                  auto_set=False)

    state_vars = Tuple

    def init(self):
        if self._paused:
            self._paused = False
        if self._restart:
            self.tline.val = self.tline.min
            self.state_vars = self.mats_eval.init_state_vars()
            self._restart = False

    def eval(self):
        '''this method is just called by the tloop_thread'''

        t_max = self.loading_scenario.xdata[-1]
        self.set_tmax(t_max)
        t_min = self.tline.val
        n_steps = self.n_steps
        tarray = np.linspace(t_min, t_max, n_steps)
        sv_names = self.sv_names
        sv_records = [[] for s_n in sv_names]
        s_last = 0
        for idx, t in enumerate(tarray):
            if self._restart or self._paused:
                break
            s = self.loading_scenario(t)
            d_s = s - s_last
            self.state_vars = \
                self.mats_eval.get_next_state(s, d_s, self.state_vars)
            # record the current simulation step
            # @todo: fix this to avoid array
            t_ = np.array([t], dtype=np.float_)
            for sv_record, state in zip(sv_records, (t_, s) + self.state_vars):
                sv_record.append(np.copy(state))

        # append the data to the previous simulation steps
        for sv_name, sv_record in zip(sv_names, sv_records):
            self.sv_hist[sv_name].append(np.array(sv_record))

        self.tline.val = min(t, self.tline.max)

    def get_sv_hist(self, sv_name):
        return np.vstack(self.sv_hist[sv_name])

    viz2d_classes = {
        'bond history': Viz2DBondHistory,
        'load function': Viz2DLoadControlFunction,
    }

    tree_view = View(Item('material_model'), Item('interaction_type'))
class ExpandablePanel(Widget):
    """ An expandable panel. """

    # The default style.
    STYLE = wx.CLIP_CHILDREN

    collapsed_image = Instance(ImageResource, ImageResource('mycarat1'))
    expanded_image = Instance(ImageResource, ImageResource('mycarat2'))

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

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

        # Base class constructor.
        super(ExpandablePanel, self).__init__(**traits)

        # Create the toolkit-specific control that represents the widget.
        self.control = self._create_control(parent)

        # The layers in the panel.
        #
        # { str name : wx.Window layer }
        self._layers = {}
        self._headers = {}

        return

    ###########################################################################
    # 'Expandale' interface.
    ###########################################################################

    def add_panel(self, name, layer):
        """ Adds a layer with the specified name.

        All layers are hidden when they are added.  Use 'show_layer' to make a
        layer visible.

        """

        parent = self.control
        sizer = self.control.GetSizer()

        # Add the heading text.
        header = self._create_header(parent, text=name)
        sizer.Add(header, 0, wx.EXPAND)

        # Add the layer to our sizer.
        sizer.Add(layer, 1, wx.EXPAND)

        # All layers are hidden when they are added.  Use 'show_layer' to make
        # a layer visible.
        sizer.Show(layer, False)

        # fixme: Should we warn if a layer is being overridden?
        self._layers[name] = layer

        return layer

    def remove_panel(self, name):
        """ Removes a layer and its header from the container."""

        #if not self._layers.has_key(name):
        if not name in list(self._layers.keys()):
            return

        sizer = self.control.GetSizer()
        panel = self._layers[name]
        header = self._headers[name]
        #sizer.Remove(panel)
        panel.Destroy()
        #sizer.Remove(header)
        header.Destroy()

        sizer.Layout()

        return

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

    def _create_control(self, parent):
        """ Create the toolkit-specific control that represents the widget. """

        panel = wx.Panel(parent, -1, style=self.STYLE)
        sizer = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(sizer)
        panel.SetAutoLayout(True)

        return panel

    def _create_header(self, parent, text):
        """ Creates a panel header. """

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN)
        panel.SetSizer(sizer)
        panel.SetAutoLayout(True)

        # Add the panel header.
        heading = ExpandableHeader(panel, self, title=text)
        sizer.Add(heading.control, 1, wx.EXPAND)

        heading.on_trait_change(self._on_button, 'panel_expanded')

        # Resize the panel to match the sizer's minimum size.
        sizer.Fit(panel)

        # hang onto it for when we destroy
        self._headers[text] = panel

        return panel

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

    def _on_button(self, event):
        """ called when one of the expand/contract buttons is pressed. """

        header = event
        name = header.title
        visible = header.state

        sizer = self.control.GetSizer()
        sizer.Show(self._layers[name], visible)
        sizer.Layout()

        # fixme: Errrr, maybe we can NOT do this!
        w, h = self.control.GetSize().Get()
        self.control.SetSize((w + 1, h + 1))
        self.control.SetSize((w, h))