VTKWidgets

From KitwarePublic
Jump to navigationJump to search

Overview

The VTK widgets are undergoing a redesign. This is mainly to add functionality and clean up some inconsistencies with the existing implementation. We also have limited funding (see acknowledgements) to add new widgets in support of medical image processing (i.e., segmentation and registration).

There are four major goals of this work:

  1. Support the ability to customize event bindings
  2. Decouple the widget representation (geometry) from the event processing
  3. Reduce redundant code; centralize common operations
  4. Add new widgets in support of medical image processing

The following sections discuss eash of these goals and the proposed implementation.

Customized Event Binding

The current VTK widgets have hardwired event bindings; e.g., a "LeftButtonPressEvent" is hardwired to execute a particular action, and this action/event binding cannot be changed. This is a problem when users desire different bindings due to personal preference, use of different interaction devices, etc. A prototype design has been created that addresses this issue and is described in the following.

Basic Concepts

The basic idea is to create a separation between VTK events and widget events (see figure). An event translator is used to translate a VTK event into a widget event. In turn, the widget event is mapped into a method invocation. WidgetMap.jpg

In practice this approach is overly simplified. The problem is that an event is really more than a VTK EventId, it includes various modifiers that go along with it (e.g., we'd like to bind Shift-LeftButtonPress to a particular callback). The current VTK implementation is based around only the EventId, and then leaves the callback to check for the appropriate modifiers. This poses difficulties if we want configurable event bindings, since the source code must be modified to process the modifiers separately. Hence the right way to do this is to create separate, simple callback methods that are independent of modifiers, and then insist that widget events include modifiers as part of their definition. In order to do this, the vtkWidgetEventTranslator map shown above is modified as follows. The key to the map remains the VTK event id, but the value is now a list of vtkEvents and associated widget events (see figure below). When a VTK event is received, the event id is mapped into the list, and then the list is traversed to find the matching event (the comparison takes into account the VTK event qualifiers). If found, the associated widget event is invoked. Note that the modifiers must include the notion of "ignore any modifiers" in case the user does not care whether or not the shift key is pressed at the same time the LeftButtonPress is. WidgetMap2.jpg

Implementation Details

Here are some code snippets showing the major features of the design. Refer to the vtkSliderWidget code. Thanks to Brad for helping out here.

In the constructor of a widget, instances of vtkWidgetEventTranslator and vtkWidgetCallbackMapper are created. (In actual implementation, the vtkWidgetEventTranslator is instantiated in the superclass (vtkAbstractWidget) Once these classes are implemented, then the default event bindings are set up.

 this->EventTranslator = vtkWidgetEventTranslator::New(); //should be in superclass
 this->CallbackMapper = new vtkWidgetCallbackMapper<vtkSliderWidget>(this->EventTranslator);
 this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent,
                                         vtkWidgetEvents::Select,
                                         this, vtkSliderWidget::SelectAction);

This version of SetCallbackMethod() sets things up so that modifiers to the event are ignored. Alternatively, the following method allows specification of all of the modifiers:

 this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent,
                                         modifiers, keyCode, repeatCount, keySym,
                                         vtkWidgetEvents::Select,
                                         this, vtkSliderWidget::SelectAction);

Note that the vtkWidgeCallbackMapper must invoke methods on a particular class. The original design called for a templated class (templated on the widget class type). However this was discarded because either 1) the vtkWidgetCallbackMapper<T> would have to instantiated in each and every class (meaning duplicated code) or 2) very fancy template tricks would have to be used to centralize the instantiation and use of vtkWidgetCallbackMapper<T>. Instead of the fancy templated approach, a simpler approach based on static class methods is used.

Once the widget is constructed, the user can change the VTK event bindings as shown from TestSliderWidget.cxx:

 vtkSliderWidget *sliderWidget = vtkSliderWidget::New();
 sliderWidget->GetEventTranslator()->SetTranslation(vtkCommand::RightButtonPressEvent,
                                                    vtkWidgetEvent::Select);
 sliderWidget->GetEventTranslator()->SetTranslation(vtkCommand::RightButtonReleaseEvent,
                                                    vtkWidgetEvent::EndSelect);

The use of static class methods allows the code that sets up the cllbacks to be cetralized. In vtkSliderWidget.h we find the declaration of the callback methods:

 // These are the events that are handled
 static void SelectAction(vtkAbstractWidget*);
 static void EndSelectAction(vtkAbstractWidget*);
 static void MoveAction(vtkAbstractWidget*);

These are referred to in the constructor of the vtkSliderWidget:

vtkSliderWidget::vtkSliderWidget() {

 // Set the initial state
 this->WidgetState = vtkSliderWidget::Start;
 // Assign initial values
 this->AnimationMode = vtkSliderWidget::Jump;
 this->NumberOfAnimationSteps = 24;
 // Okay, define the events
 this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent,
                                         vtkWidgetEvent::Select,
                                         this, vtkSliderWidget::SelectAction);
 this->CallbackMapper->SetCallbackMethod(vtkCommand::MouseMoveEvent,
                                         vtkWidgetEvent::Move,
                                         this, vtkSliderWidget::MoveAction);
 this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent,
                                         vtkWidgetEvent::EndSelect,
                                         this, vtkSliderWidget::EndSelectAction);

}


To set up the callbacks in the widget (i.e., add observers), the centralized code in vtkAbstractWidget::SetEnabled() is executed:

   // listen for the events found in the EventTranslator
   this->EventTranslator->AddEventsToInteractor(this->Interactor,
                                                this->EventCallbackCommand,this->Priority);

which is a very nice reduction in the code that was there previously.

(Removing the callbacks does not change:)

   // don't listen for events any more
   this->Interactor->RemoveObserver(this->EventCallbackCommand);

Finally, the ProcessEvents() code also simplifies dramatically and is centralized in vtkAbstractWidget:

//------------------------------------------------------------------------- void vtkAbstractWidget::ProcessEvents(vtkObject* vtkNotUsed(object),

                                      unsigned long vtkEvent,
                                      void* clientdata, 
                                      void* vtkNotUsed(calldata))

{

 vtkAbstractWidget* self = 
   reinterpret_cast<vtkAbstractWidget *>( clientdata );
 
 unsigned long widgetEvent = 
   self->EventTranslator->GetTranslation(vtkEvent,
                                         vtkEvent::GetModifier(self->Interactor),
                                         self->Interactor->GetKeyCode(),
                                         self->Interactor->GetRepeatCount(),
                                         self->Interactor->GetKeySym());
 if ( widgetEvent != vtkWidgetEvent::NoEvent)
   {
   self->CallbackMapper->InvokeCallback(widgetEvent);
   }

}


That's really all there is to it, with the exception that the widget callback methods should be renamed (i.e., rather than OnLeftButtonDown() the better name would be SelectAction(), etc.).

Decouple Event Processing from Widget Geometry

One of the major goals of the widget redesign was to separate widgets into two major parts:

  1. One part that performs the event processing (including event translation, etc.), and
  2. another part that defines a geometric representation for the widget.

From a high level, this has been implemented by creating two abstract superclasses:

  1. vtkAbstractWidget and
  2. vtkWidgetRepresentation

Practically, this means that vtkAbstractWidget adds observers, processes events, invokes callbacks and so on; while vtkWidgetRepresentation is a type of vtkProp with a specialized API for communicating with the widget, as well as built-in methods for manipulating and picking the representation. (Note that while the representation is thought of as a vtkProp, in reality it may define many vtkProps used to represent a widget.)

Code Refactoring

The code base has been simplified in several ways (see previous discussions).

  • ProcessEvents(), adding observers to interactor, event mapping are all implemented in the abstract superclass vtkAbstractWidget
  • SetEnabled() is implemented in vtkAbstractWidget and generally suffices for most widgets (composite widgets sometimes must extent or override the method).
  • An API between the widget and its representation has been generally flushed out. Common methods are implemented in the two classes vtkAbstractWidget and vtkWidgetRepresentation

New Widgets

Various widgets have been created to test out the design. Here they are first listed in alphabetical order with a brief description of what they do. Note that the widgets are grouped with their representation(s) (or geometry). Thus the number of widgets is greater than it appears in the list below: different representations result in different user experiences.

  • vtkAbstractWidget - abstract superclass of all widgets; enforces the separation of widget/representation and cetralizes much of the event processing.
  • vtkBorderWidget (with vtkBorderRepresentation) - a rectangular border in the overlay plane. Serves as the superclass for many subclasses (vtkTextWidget,vtkCameraWidget,vtkCaptionWidget)
  • vtkCameraWidget (with vtkCameraRepresentation) - a simple widget to keyframe the camera
  • vtkCaptionWidget (with vtkCaptionRepresentation) - text in a box with an arrow to a position (i.e., the anchor) in 3D space. The box and the anchor point can be manipulated separately.
  • vtkCheckerboardWidget (with vtkCheckerboardRepresentation) - interacts with a vtkImageCheckerboard. Great for comparing two images.
  • vtkEvent - a general representation for a vtkEvent
  • vtkHandleWidget (with vtkHandleRepresentation, vtkPointHandleRepresentation2D, vtkPointHandleRepresentation3D) - manipulation a position (vtkCoordinate)
  • vtkRectilinearWipeWidget (with vtkRectilinearWipeRepresentation) - similar to a vtkCheckerboardWidget, it allows interactive comparison of two images.
  • vtkSliderWidget (with vtkSliderRepresentation, vtkSliderRepresentation3D) - set a scalar value with a slider (a slider is like a 2D GUI scale).
  • vtkTextWidget (with vtkTextRepresentation) - size and position text. A select callback is available so you can pop up a 2D GUI to set fonts, checnge text, etc.
  • vtkWidgetCallbackMapper - infrastructure for mapping events to callbacks
  • vtkWidgetEvent - a list of all the internal events that widgets generate. VTK events are translated into widget events.
  • vtkWidgetEventTranslator - translate VTK events into widget events.
  • vtkWidgetRepresentation - abstract superclass of widget representations. It's essentially a vtkProp with some added methods to interface to the widgets (i.e., subclasses of vtkAbstractWidget).

Issues

There are several issues with this design that need addressing.

  1. What should the names of the widget events be? It you look at vtkWidgetEvents.h you will see one possibility.
  2. Currently the implementation is implemented in two principle classes: vtkWidgetEventTranslator and vtkWidgetCallbackMapper. vtkWidgetCallbackMapper is templated over the widget class type, this is to allow simple declaration and invocation of the callback widget methods. Brad tells me there is are ways around this, but the C++ tricks are not pretty.
  3. Mediating widget conflicts (picking, cursors)
  4. Defining what an event is
  5. Capturing the API between a widget and its representation

Acknowledgements

This work has been supported through the efforts of several organizations and individuals.

  • The National Library of Medicine (NLM A2D2 Award, 3D Widgets for Segmentation and Registration, PI: William J. Schroeder
  • Kitware, Inc. (Will Schroeder, Sebastien Barre)