|
|
(One intermediate revision by the same user not shown) |
Line 1: |
Line 1: |
| ==Introduction==
| |
|
| |
|
| The following sections describe the wizard workflow that is currently used in [http://kwwidgets.org KWWidgets]. This worfklow framework was created to help porting the EMSegmentation module (Polina Golland, Kilian Pohl) to [http://wiki.na-mic.org/Wiki/index.php/Slicer3 Slicer3]. This work is also part of the National Alliance for Medical Image Computing ([http://wiki.na-mic.org 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 controls to navigate from one step to the other, we decided that a state machine or a Petri net would present a reasonable low-level basis for this framework. After some internal discussion at Kitware, we agreed that even a stab at a simple Petri net engine would not be a trivial task 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 [http://www.igstk.org/index.htm IGSTK], while porting it to a more KWWidgets/VTK friendly environment. The resulting framework does not provide ITK-style C++ templating or extensive compile-time checks, but is compatible with VTK's coding style and wrapping technology.
| |
| {| border="0" align="center" cellpadding="20"
| |
| |-
| |
| | [[Image:KWWizardDialogExample.png|center|thumb|304px|KWWizardDialog Example]] || [[Image:EMSegmentationModuleWizard.png|center|thumb|240px|EMSegmentation Module]]
| |
| |}
| |
|
| |
| ==State Machine Engine==
| |
|
| |
| Nothing groundbreaking here. A few simple classes to represent states, inputs, and transitions that are triggered when the machine is in a particuliar state and receive a specific input, etc.
| |
|
| |
| * {{:kwwd|vtkKWStateMachineState}}: a state
| |
| * {{:kwwd|vtkKWStateMachineInput}}: an input (which triggers transitions)
| |
| * {{:kwwd|vtkKWStateMachineTransition}}: a transition between 2 states
| |
| * {{:kwwd|vtkKWStateMachine}}: a state machine
| |
|
| |
| A few additional classes are available.
| |
|
| |
| * {{:kwwd|vtkKWStateMachineCluster}}: a cluster (group) of states
| |
| * {{:kwwd|vtkKWStateMachineDOTWriter}}: a writer to Graphviz's DOT format
| |
| * {{:kwwd|vtkKWStateMachineWriter}}: a writer superclass
| |
|
| |
| ===vtkKWStateMachineState===
| |
|
| |
| The {{:kwwd|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.
| |
|
| |
| ===vtkKWStateMachineInput===
| |
|
| |
| The {{:kwwd|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
| |
|
| |
| ===vtkKWStateMachineTransition===
| |
|
| |
| The {{:kwwd|vtkKWStateMachineTransition}} class provides the representation for transition between two states given a specific input. The following members are available:
| |
| * '''Id''': unique transition ID
| |
| * '''OriginState''': vtkKWStateMachineState
| |
| * '''Input''': vtkKWStateMachineInput
| |
| * '''DestinationState''': vtkKWStateMachineState
| |
| * '''Start'''/'''End''': callbacks and events
| |
|
| |
| 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. This 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.
| |
|
| |
| Associating a command/callback to a transition is a pretty common state machine pattern, but the question remains as when to trigger this callback: ''before'' leaving the originating state, or ''after'' reaching the destination state. We chose to accomodate both solutions by allowing the user to specify a command that can be triggered either at the beginning of the transiton (Start), or at the end.
| |
|
| |
| ===vtkKWStateMachine===
| |
|
| |
| The {{:kwwd|vtkKWStateMachine}} class provides the representation for a 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. 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): retrive the current state
| |
|
| |
| The engine itself is set into motion by pushing inputs and periodically asking the machine to process them:
| |
| * '''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. 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).
| |
|
| |
| ===Example===
| |
|
| |
| The [http://public.kitware.com/cgi-bin/viewcvs.cgi/Testing/Cxx/TestStateMachine.cxx?root=KWWidgets&view=markup TestStateMachine.cxx] file provides a detailed example on how to use the state machine engine. Here are a few excerpts:
| |
|
| |
| <pre>
| |
| vtkKWStateMachine *state_machine = vtkKWStateMachine::New();
| |
| </pre>
| |
| [...]
| |
| <pre>
| |
| vtkKWStateMachineState *state_1 = vtkKWStateMachineState::New();
| |
| state_1->SetName("Start");
| |
| state_machine->AddState(state_1);
| |
| </pre>
| |
| [...]
| |
| <pre>
| |
| vtkKWStateMachineInput *input_next = vtkKWStateMachineInput::New();
| |
| input_next->SetName("next");
| |
| state_machine->AddInput(input_next);
| |
| </pre>
| |
| [...]
| |
| <pre>
| |
| // 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);
| |
| </pre>
| |
| [...]
| |
| <pre>
| |
| // 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();
| |
| </pre>
| |
|
| |
| The {{:kwwd|vtkKWStateMachineDOTWriter}} class can be used to output a graphical representation of the state machine to the Graphviz's DOT format (see below). The resulting file can either be turned into an image using the ''dot'' executable, or rendered dynamically/on-the-fly by any MediaWiki supporting the [http://meta.wikimedia.org/wiki/GraphViz GraphViz extension].
| |
|
| |
| <graphviz>
| |
| digraph G {
| |
| rankdir=LR;
| |
| node [fontcolor="#000000", fontsize=9, fontname="Helvetica"];
| |
| edge [fontcolor="#0000ff", fontsize=8, fontname="Helvetica"];
| |
|
| |
| 1 [label="Start", peripheries=2];
| |
| 2 [label="2"];
| |
| 3 [label="3"];
| |
|
| |
| 1 -> 2 [label="next"];
| |
| 1 -> 3 [label="skip"];
| |
| 2 -> 3 [label="next"];
| |
| 3 -> 1 [label="next"];
| |
| 2 -> 2 [label="invalid"];
| |
|
| |
| subgraph cluster1 {
| |
| fontcolor="#000000"; fontsize=10; fontname="Helvetica";
| |
| style=dashed;
| |
| label="1"
| |
| 2; 3;
| |
| }
| |
|
| |
| fontcolor="#000000";
| |
| fontsize=12;
| |
| fontname="Helvetica";
| |
| label="State Machine Example";
| |
| }
| |
| </graphviz>
| |
|
| |
| Note that both state 2 and 3 were grouped together logically using a cluster.
| |
| <pre>
| |
| 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);
| |
| </pre>
| |
|
| |
| ==Wizard Workflow==
| |
|
| |
| The wizar 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,
| |
| * one can navigate from one step to the next one using a 'Next' and 'Back' button,
| |
| * one can navigate from one step to the previous one using a 'Back' button,
| |
| * one can navigate directly to the last step using a 'Finish' button,
| |
| * one need 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 step, a workflow, a wizard widget, a wizard dialog.
| |
|
| |
| * {{:kwwd|vtkKWWizardStep}}: a step
| |
| * {{:kwwd|vtkKWWizardWorkflow}}: a wizard workflow
| |
| * {{:kwwd|vtkKWWizardWidget}}: a wizard widget, embedding UI (buttons) and a workflow engine
| |
| * {{:kwwd|vtkKWWizardDialog}}: a wizard dialog, embedding a wizard widget in a toplevel
| |
|
| |
| ===vtkKWWizardStep===
| |
|
| |
| Note that GoToSelfInput is just a convenience member variable, an input that is automatically made available to you so that you can specify that you want to go to a specific step. Encapsulating the input inside the step you want to go to make it pretty clear from a notation/code point of view that you are referring to a specific step. This does not mean, however, that transitions have been created for you at this point, this is still up to you to explicitly create transitions that respond to that input.
| |
|
| |
| ===vtkKWWizardWorkflow===
| |
|
| |
| A wizard workflow, subclass of vtkKWStateMachine. The following class, vtkKWWizardWorkflow, is a specialization of a vtkKWStateMachine that offers a few convenience classes relying on the strong assumption that there exist a "linear" path of steps that were added as vtkKWWizardStep instances.
| |
| [to be completed]
| |
| Remember, those are convenience methods that are useful to drive the state machine from a UI. You can ignore them and still rely on the generic state machine model to program your wizard. The "linearity" of transition between the states is from a "step" point of view, you can add more states to the machine as instances of vtkKWStateMachineState, as long as only instances of vtkKWWizardStep are used to specificy a linear "path" among the states. This is a constraint/assumption.
| |
|
| |
| From a UI point of view, we probably also need something that can tell us *if* we can go to a given step, so that we can disable or grey out the corresponding "Next"/"Back" button, I'll leave that for another time.
| |