Colors and Animations

These last two weeks have been really uneventful. I have mainly been focusing on fixing bugs in my existing elements. I did however, start work on 2 new elements: Preloader and Color Picker.

Preloader

A Preloader is a visual indication of the process of ‘loading’, used to display to tell the user that the program is running in the background and has not stopped. These include those rotating circles and moving bars we all hate to see on our screens.

These are some examples:

This was a challenging element for me since it was the first time I was playing with animations and timers in VTK. I referred to the example code here to get started and slowly worked my way towards creating a class in the viz module.

This is what I created:

You can find the code for this at https://github.com/karandeepSJ/dipy/commit/0240ba3e301fa6a3407d5e434ac4141efc0c311c

To run it, use this script:

import dipy.viz.ui as ui
import dipy.viz.window as window
loader=ui.Preloader(100)
sm=window.ShowManager(size=(600,600))
sm.initialize()
sm.iren.CreateRepeatingTimer(10)
loader.add_callback(sm.iren, 'TimerEvent', loader.rotate)
sm.ren.add(loader)
sm.start()

 

Color Picker

HSL and HSV Color Models

HSL (hue, saturation, lightness) and HSV (hue, saturation, value) are two alternative representations of the RGB color model, designed to more closely align with the way human vision perceives color-making attributes.

HSL and HSV are both cylindrical geometries, with hue, their angular dimension, starting at the red primary at 0°, passing through the green primary at 120° and the blue primary at 240°, and then wrapping back to red at 360°. In each geometry, the central vertical axis comprises the neutral, achromatic, or gray colors, ranging from black at lightness 0 or value 0, the bottom, to white at lightness 1 or value 1, the top.

The HSV representation models the way paints of different colors mix together, with the saturation dimension resembling various shades of brightly colored paint, and the value dimension resembling the mixture of those paints with varying amounts of black or white paint.

The HSL model places fully saturated colors around a circle at a lightness value of 1/2, where a lightness value of 0 or 1 is fully black or white, respectively.

Common Color Pickers

All color pickers seen in end-user softwares use the HSL and HSV color models. Most show a two-dimensional slice through the model, along with a slider controlling which particular slice is shown, i.e., the third dimension. These exhibit a great variety, because of the various geometric representations of the models – cylinders, hexagonal prisms and cones/bicones. Some common examples are shown below:

All these color pickers use the mouse click coordinates to determine the HSL/HSV values, which are then converted to RGB using simple formulae you can find here.

Currently, I plan on implementing the following two color pickers:

Square Color Picker

 

This color picker has two components, the vertical Hue Bar and the square next to it. The vertical bar allows the user to select the Hue of the color they want, and the square allows to select the Saturation and Value(or Lightness) for that corresponding hue.

This is a good choice since a common human, when choosing a color, first decides upon a ‘pure color’ or hue, and then chooses the shade for that hue, or the saturation and value.

The vtkScalarBarActor  makes it extremely easy for users to add the hue bar. The example code here illustrates how it can be used. The selected hue value can be measured by the distance from the ‘red’ or bottom of the bar. Once a hue is selected, the square next to it can be colored by assigning corresponding colors to the four vertices. All points inside the square will then be interpolated accordingly. When a point is clicked, the corresponding saturation and value can be determined using the horizontal and vertical distance from the lower-left corner, which corresponds to 0 saturation and 0 value.

Circular Color Picker

This color picker combines hue and saturation inside a circle. The distance from center indicates saturation and the radial distance from red indicates the hue. After a H and S are selected, a Line Slider is used to get the Value dimension.

Implementation-wise, this one is really difficult since I could not find any way to create a multicolored circle.

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.