VTKWidgets

From KitwarePublic
Revision as of 14:56, 24 August 2005 by Wschroed (talk | contribs) (→‎Issues)
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 would be instantiated in a superclass (vtkInteractorObserver ?) 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 is a templated class (templated on the widget class). This is because the callback mapper eventually invokes a slider instance method (the callback mapper is a friend to the vtkSliderWidget). The SetCallbackMethod() is a convenience method that sets up the relationship between the two maps (VTK event->widget event and widget event -> method callback invocation).

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

 sliderWidget->GetEventTranslator()->SetTranslation(vtkCommand::RightButtonPressEvent, 
                                                    modifier, keyCode, repeatCount, keySym,
                                                    vtkWidgetEvents::Select);

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

   vtkRenderWindowInteractor *i = this->Interactor;
   this->EventTranslator->AddEventsToInteractor(i,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:

 void vtkSliderWidget::ProcessEvents(vtkObject* object, unsigned long vtkEvent,
                                     void* clientdata, void* calldata)
 {
 vtkSliderWidget* self = reinterpret_cast<vtkSliderWidget *>( 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 callback methods should be renamed (i.e., rather than OnLeftButtonDown() the better name would be SelectAction(), etc.).

Decouple Event Processing from Widget Geometry

Code Refactoring

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)