Refactoring and Menus

Generalizing all 2D elements

Currently, all the elements implemented in dipy.viz.ui only work in a 2D setting, since they use vtkActor2D instead of vtkActor. This poses the problem of not being able to set the depth of these elements in a 3D setting. Also, the AddPart function of Assemblies(discussed in the previous post) only accepts vtkActors.

One possible solution to this is changing all vtkActor2D to vtkActor. But this has two problems:

  • The user will have to unnecessarily provide a third argument while positioning the elements in 2D settings. This can be solved by making a wrapper on the _set_position function.
  • Another problem is that in a 3D setting, the user can move the camera¬† around with left mouse button. With this, the 2D elements ‘rotate’ as the camera moves, which should not be the case, as is shown below.
import dipy.viz.ui as ui
import dipy.viz.window as window

renderer = window.ren()
current_size = [600, 600]
showm = window.ShowManager(renderer, size=current_size, title="Sci-Fi UI")
cube=ui.Cuboid(size=(10,10,10))
rect=ui.Rectangle2D(size=(7,7),color=(1,0,0),position=(10,10))
showm.ren.add(cube)
showm.ren.add(rect)
showm.start()

This cannot be solved using vtkFollower because that only follows the panning of the camera, not zooming.

The only solution that my mentors and I could come up with is to modify all the existing elements by writing two wrapper classes over the UI element class – 2D and 3D and the user decides whether they wish to render an element as 2D or 3D.

Orbital Menu

I successfully ported the old orbital menu for the current UI class.

import dipy.viz.ui as ui
import dipy.viz.window as window

cube=ui.Cuboid(size=(10,10,10))
rect1=ui.Rectangle2D(size=(2,2),color=(1,0,0))
rect2=ui.Rectangle2D(size=(2,2),color=(0,1,0))
rect3=ui.Rectangle2D(size=(2,2),color=(0,0,1))

def toggle(i_ren, obj, follower):
    menu.set_visibility(not menu.visibility)
    menu.visibility = not menu.visibility
    i_ren.force_render()
cube.add_callback(cube.actor, "RightButtonPressEvent", toggle)

renderer = window.ren()
current_size = [600, 600]
showm = window.ShowManager(renderer, size=current_size, title="Sci-Fi UI")
menu=ui.FollowerMenu((0,0,0),5*1.741,renderer.GetActiveCamera(),[rect1,rect2,rect3])

showm.ren.add(cube)
showm.ren.add(menu)
showm.start()

Radial Text

One thing my mentors wanted me to try was create a disk around which some amount of text can be position that can be aligned radially.

The text element for this purpose uses vtkVectorText instead of vtkTextActor to avoid pixelation on zooming.

class TextFollower(UI):
    """ 3D text that follows the camera.
    """

    def __init__(self, text, color, scale, position=(0,0,0)):
        """

        Parameters
        ----------
        text: string
        color: (float, float, float)
        """
        self.text = text
        super(TextFollower, self).__init__(position)
        self.actor.SetScale(scale)
        self.actor.GetProperty().SetColor(color)

    def _get_actors(self):
        return [self.actor]

    def _setup(self):
        """

        Parameters
        ----------
        text: string
        color: (float, float, float)
        """
        actor_text = vtk.vtkVectorText()
        actor_text.SetText(self.text)

        mapper = vtk.vtkPolyDataMapper()
        mapper.SetInputConnection(actor_text.GetOutputPort())
        self.actor = vtk.vtkActor()
        self.actor.SetMapper(mapper)

    def _add_to_renderer(self):
        ren.add(self.actor)

    def _set_position(self,position):
        position = (position[0],position[1],0)
        self.actor.SetPosition(position)

    def _get_size(self):
        size = 2*(self.actor.GetCenter()-self.position)
        return size

The positioning of various elements uniformly around the menu disk is achieved by the add_components method of the FollowerMenu class.

def add_components(self, components):
    """ Adds parts to the orbit.

    Parameters
    ----------
    components: list(UI)
    """
    num_components = len(components)
    angular_difference = 360/num_components
    self.components = []
    for i in range(num_components):
        self.components.append(components[i])
        theta = math.radians(angular_difference*(i+1))
        x = self.position[0] + ((self.radius+0.5) * math.cos(theta))
        y = self.position[1] + ((self.radius+0.5) * math.sin(theta))
        components[i].center = (x,y,0)

        if isinstance(components[i],TextFollower):
            x = x + 0.5 * math.cos(theta)
            y = y + 0.5 * math.sin(theta)
            text_rotation = angular_difference*(i+1)
            components[i].position = (x,y,0)
            components[i].actor.RotateZ(text_rotation)

        for actor in components[i].actors:
            self.actor.AddPart(actor)

The ouput obtained for the radial text is quite appealing.

Leave a Reply

Your email address will not be published. Required fields are marked *