KWWidgets/Wizard Workflow

From KitwarePublic
Jump to: navigation, search

Introduction

This page describes the wizard workflow that is currently used in KWWidgets and demonstrated by the WizardDialog example and Slicer3. This framework was designed to help porting the EMSegmentation module (Polina Golland, Kilian Pohl) to Slicer3 by shaping its functionalities and hiding its complexity inside a step-by-step interface. This work is also part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.

Since a wizard is fundamentally a set of steps as well as additional logic and constraints defining how to navigate from one step to the other, we decided that a state machine or a Petri net would provide a reasonable low-level basis for this framework. After some internal discussions at Kitware, we agreed that albeit more powerful, a Petri net engine could not be implemented given the time frame and the resources allocated for this project. However, we chose to leverage and borrow a large part of the state machine engine design found in IGSTK, and port the corresponding set of classes to a more KWWidgets/VTK -friendly environment. While the resulting framework does not provide state-of-the-art ITK-style C++ templating and extensive compile-time checks, it remains compatible with VTK's coding style and wrapping technology. This GUI-independent state machine engine is the focus of the first few sections. The wizard workflow engine builds upon that framework to provide higher-level task-oriented classes and methods, and is presented in more details in the last few sections.

WizardDialog Example
EMSegmentation Module (animated)

Presentations

State Machine Engine

A state machine is defined by a set of states, a set of inputs and a transition matrix that defines for each pair of (state,input) what is the next state to assume. A few simple GUI-independent classes are used to represent states, inputs, and transitions.

A few helper classes are also available.

vtkKWStateMachineState

The vtkKWStateMachineState class provides the representation for a state. The following members are available:

  • Id: unique state ID
  • Name: string
  • Description: string
  • Enter/Leave: callbacks and events

The Enter/Leave callbacks and events are fired automatically by the state machine when it enters (respectively leaves) that state, independent of the transition to (respectively from) that state. This can be used, for example, to bring up the GUI of the wizard step associated to that state, or cleanup any resources that were used by that GUI.

Note that a callback is a KWWidgets construct that can be used to reference a public method. This is not done using a function pointer, but using the method name instead. This also allows developpers to define callbacks from interpreted languages like Tcl or Python (see the KWCallback example for more details). Events, on the other hand, rely on VTK's command/observer pattern (see vtkObject for more details). While they are limited to C++ code, they are more flexible than callbacks in many ways since they allow any number of observers to be notified by a single object, whereas only one callback can be defined per object for that same event.

The Id is provided automatically. The name and description can be left empty, but will be used by the vtkKWStateMachineWriter subclasses or the wizard widget UI if they are specified, for illustration purposes.

vtkKWStateMachineInput

The vtkKWStateMachineInput class provides the representation for an input, i.e. the single token that triggers a transition from one state to the other. The following members are available:

  • Id: unique input ID
  • Name: string

The Id is provided automatically. The name can be left empty, but will be used by the vtkKWStateMachineWriter subclasses if it is specified, for illustration purposes.

vtkKWStateMachineTransition

The vtkKWStateMachineTransition class provides the representation for a transition between two states (origin and destination) given a specific input. The following members are available:

  • Id: unique transition ID
  • OriginState: vtkKWStateMachineState
  • Input: vtkKWStateMachineInput
  • DestinationState: vtkKWStateMachineState
  • Start/End: callbacks and events

Associating a callback to a transition is a pretty common design pattern, but the question remains as when to trigger this callback: before leaving the originating state, or after reaching the destination state? This is a relevant concern since the callback may be the one piece of code that will push more inputs to the state machine and trigger more transitions, assuming that the state machine is in a specific state (but which one?). We chose to accomodate both solutions by allowing the user to specify a command (or listen to an event) that is triggered either at the beginning of the transiton, while sitting at the originating state, or at the end, while sitting at the destination state.

The Start callback and event are fired automatically when the transition is "started" by the state machine: the state machine is about to leave the OriginState but has not yet entered the DestinationState. The End callback and event are fired in a similar fashion when the transition is "ended" by the state machine: the state machine has left the OriginState and entered the DestinationState.

vtkKWStateMachine

The vtkKWStateMachine class provides the representation for a state machine engine. The following members are available:

  • AddState(state): add a state to the state machine
  • AddInput(input): add an input to the state machine
  • AddTransition(transition): add a transition to the state machine

Transitions instances can also be created automatically given the corresponding states and input:

  • CreateTransition(origin_state, input, destination_state): create and add a transition

The initial and current state of the machine can be set and (respectively retrieved) using:

  • SetInitialState(state): set the initial state (bootstrap the machine)
  • GetCurrentState(state): retrieve the current state

The engine itself is set into motion by pushing inputs to a queue and periodically asking the machine to process that queue:

  • PushInput(state): push a new input to the queue of inputs to be processed
  • ProcessInputs: perform state transitions for every pending input stored in the input queue

Additional methods are provided to retrieve inputs/states/transitions from the machine, query the number of inputs/states/transition, search for a specific transition, etc. Transitions triggered by the state machine are saved in a transition history than can be queried (and eventually saved or played back future implementations). Clusters (groups) of states can be formed, providing a way to relate some states logically (this is used for display and I/O purposes, for example, as well as grouping several states inside a single "step" in the wizard workflow).

Example

The TestStateMachine.cxx file provides a quick walkthrough of the state machine engine. Here are a few excerpts (some objects are missing):

  vtkKWStateMachine *state_machine = vtkKWStateMachine::New();

[...]

  vtkKWStateMachineState *state_1 = vtkKWStateMachineState::New();
  state_1->SetName("Start");
  state_machine->AddState(state_1);

[...]

  vtkKWStateMachineInput *input_next = vtkKWStateMachineInput::New();
  input_next->SetName("next");
  state_machine->AddInput(input_next);

[...]

  // Transition: state_1 / input_next => state_2

  vtkKWStateMachineTransition *trans_a = vtkKWStateMachineTransition::New();
  trans_a->SetOriginState(state_1);
  trans_a->SetInput(input_next);
  trans_a->SetDestinationState(state_2);
  state_machine->AddTransition(trans_a);

  // Transition: state_1 / skip => state_3

  state_machine->CreateTransition(state_1, input_skip, state_3);

[...]

  // Run the state machine

  state_machine->SetInitialState(state_1);
  state_machine->PushInput(input_next);    // state_1 to state_2
  state_machine->PushInput(input_invalid); // state_2 to state_2
  state_machine->ProcessInputs();

The vtkKWStateMachineDOTWriter class can be used to output a representation of the state machine to the Graphviz's DOT format (see below). Many aspects of the corresponding graph can be controlled, such as the font and color used by each elements. The resulting file can either be turned into an image using the dot executable, or rendered dynamically/on-the-fly by any MediaWiki (such as this one) supporting the GraphViz extension.

This is a graph with borders and nodes. Maybe there is an Imagemap used so the nodes may be linking to some Pages.

Note that both state 2 and 3 were grouped together logically using a cluster (numbered '1' in the figure).

  vtkKWStateMachineCluster *cluster = vtkKWStateMachineCluster::New();
  cluster->AddState(state_2);
  cluster->AddState(state_3);
  state_machine->AddCluster(cluster);

  vtkKWStateMachineDOTWriter *writer = vtkKWStateMachineDOTWriter::New();
  writer->SetInput(state_machine);
  writer->SetGraphLabel("State Machine Example");
  writer->WriteToStream(cout);

Wizard Workflow

The wizard workflow framework was designed to use the state machine engine and provide higher-level methods and classes to meet the following requirements:

  • a wizard is most of the time a linear succession of steps,
  • each step may need to display specific GUI elements,
  • one may navigate from one step to the next one using a 'Next' button,
  • one may navigate from one step to the previous one using a 'Back' button,
  • one may navigate directly to the last step using a 'Finish' button,
  • one needs to make sure a step is valid before navigating to the next one,
  • one do *not* need to make sure a step is valid before navigating to the previous one.

A few classes are used to a represent step, a workflow, a wizard widget and a wizard dialog.

Check the header files of the corresponding classes for extensive documentation. Instead of listing all functionalities, members and attributes for each class, we will describe using some examples how the classes interact with each other *per* functionality.

WizardDialog Example

All code snippets are lifted from the WizardDialog example, specifically the vtkKWMyWizardDialog class. It is recommended you open the corresponding files while you read this document. This very simple wizard walks you through several steps in order to carry out a mathematical operation:
  1. Select a mathematical operator (addition, division, square root),
  2. Select the first operand,
  3. Select the second operand *if* the operator was addition or division,
  4. Display the result.

Going back and forth between each steps is demonstrated ("Back" and "Next" button), as well as going directly to the last step ("Finish" button). The operands were selected to exercise several features:

  • Skipping a step (if the operator is square root, the second operand is not needed),
  • Validation (if the operand is division, the second operand should be != 0, if the operand is square root, it should be >= 0).
WizardDialog Example

Aggregation

The first concept to keep in mind is that a wizard step (vtkKWWizardStep) is not a subclass of a state machine state (vtkKWStateMachineState) but an aggregation of several states, transitions and inputs that are relevant to that specific step. It provides a convenient way to group and reference the elements that make up the step. Not all elements need to be used or setup, and some elements are also provided as static members for all other steps to use.

The wizard workflow class (vtkKWWizardWorkflow), however, is a subclass of a state machine engine (vtkKWStateMachine). It provides additional methods to accept a wizard step as argument, setup some of its internal elements, initialize its states, connect its transitions, and distribute all of them to the state machine superclass accordingly. What this means is that the wizard workflow can still be used as a regular state machine, but it provides convenience methods to process ("de-shuffle") wizard steps and create transitions that are specific to a wizard workflow.

TO BE CONTINUED

Make sure you check: the WizardDialog example.

Future Considerations

  • Provide a XML reader and writer to save and restore a state machine or wizard workflow.
  • Provide a C++ writer to create the C++ code out corresponding to a state machine.
  • Create an interactive graphical state machine editor or visualizer using VTK's InfoViz package
  • Use the state machine history to retrace the evolution of a state machine graphically using VTK's InfoViz package
  • Make the state machine / wizard workflow an independent library/package.



KWWidgets: [Welcome | Site Map]