MITK Tutorial--Step10: Adding New Interaction

来源:互联网 发布:js金沙.com 编辑:程序博客网 时间:2024/06/01 10:12

Step1:怎么使用现有的Datainteractor

MITK有些已经写好的DataInteractor,可以直接在插件或模块中使用。它们可以在MITK源文件的Core/Code/Interactions中可以找到。

mitk::DataInteractor 包含四个部分:描述功能的类以及两个XML文件(statemachine和config);每个mitk::DataInteractor 都必须作用于一个mitk::DataNode。这几个部分必不可少。

例子
参考mitk::PointSetDataInteractor插件。PointSetDataInteractor是DataInteractor的子类。
为使用PointSetDataInteractor,首先需要一个DataNode来保存PointSets,node必须添加到mitk::DataStorage。

mitk::DataNode::Pointer dataNode = mitk::DataNode::New();GetDataStorage()->Add(dataNode.GetPointer());

然后创建mitk::PointSetDataInteractor对象,并载入已经写好的statemachine和configuration文件

m_CurrentInteractor = mitk::PointSetDataInteractor::New();m_CurrentInteractor->LoadStateMachine("PointSet.xml");m_CurrentInteractor->SetEventConfig("PointSetConfig.xml");

最后DataNode添加到mitk::DataInteractor中:

m_CurrentInteractor->SetDataNode(dataNode);

这样,DataInteractor就能发挥作用了。

Step 2: 如何修改DataInteractor的行为

mitk::DataInteractor的行为由两方面决定。state machine决定动作执行的顺序。configuration决定用户交互触发了什么动作。

如何修改display interactor的行为

mitk::DisplayInteractor控制zooming、panning和scrolling等。当我们想改变它的行为时,可以调用InteractionEventObserver,给DisplayInteractor指派一个新的configuration。如下例所示:

std::ifstream* configStream = new std::ifstream( #path to alternative configuration file# );mitk::EventConfig newConfig(configStream);// Requesting all registered EventObserversstd::list<mitk::ServiceReference> listEventObserver = GetModuleContext()->GetServiceReferences<InteractionEventObserver>();for (std::list<mitk::ServiceReference>::iterator it = listEventObserver.begin(); it != listEventObserver.end(); ++it){    DisplayInteractor* displayInteractor = dynamic_cast<DisplayInteractor*>(GetModuleContext()->GetService<InteractionEventObserver>(*it));    // filtering: only adjust the DisplayInteractor    if (displayInteractor != NULL)    {        displayInteractor->SetEventConfig(newConfig);    }}

如何编写新的DataInteractor

本章介绍如何根据自己的需要编写DataInteractor,实现对node数据的操作。MITK交互机制初步知识请参考MITK交互概念。你可能还想知道交互概念的实施。

下面第一节介绍config文件中所有用到的参数以及使用方法。第二节介绍编写
state machine文件;最后一节介绍怎么组合config和state machine文件,同时给出例子,继承mitk::DataInteractor编写自己的DataInteractor。

如何写Config文件

Event

事件由其参数描述。每个事件都有自己的一组参数。如果有的参数被省略,意味着它被设为默认值。下面列出了所有可能的参数。

事件的参数以属性的方式存在。事件的描述由 event class和event variant完成。如

<event_variant class="InteractionKeyEvent" name="StdA">

注:event class的介绍见下面

1. Mouse Buttons

mitk::InteractionEvent::MouseButtons表示鼠标按钮。它可以作为两个属性被使用:EventButton属性表示触发事件的按钮,通常是单个按钮;ButtonState属性表示事件生成时有哪些按钮被按下。比如,假设鼠标右键和中间键已经按下,现在左键也被按下再次触发一个事件,这个状态可以被描述为:

<attribute name="EventButton" value="LeftMouseButton"/><attribute name="ButtonState" value="RightMouseButton,MiddleMouseButton"/>

注意: 技术上来讲,LeftMouseButton也被按下了,应该列在ButtonState中,但这已经被mitk::EventFactory处理好了。

2. Key Events

mitk::InteractionKeyEvent表示一个被按下的键。哪个键被按下可以按照下面表示:

<attribute name="Key" value="A"/>

又比如

<attribute name="Key" value="Escape"/>

注意: 键盘事件不需要显式configuration,因为所有键盘事件已经有预定义好的event variant,名字为 ‘Std’ + value,比如键盘a被命名为’StdA’

特别键盘名字列出如下:

// Special Keys    static const std::string KeyEsc; // = "Escape";    static const std::string KeyEnter; // = "Enter";    static const std::string KeyReturn; // = "Return";    static const std::string KeyDelete; // = "Delete";    static const std::string KeyArrowUp; // = "ArrowUp";    static const std::string KeyArrowDown; // = "ArrowDown";    static const std::string KeyArrowLeft; // = "ArrowLeft";    static const std::string KeyArrowRight; // = "ArrowRight";    static const std::string KeyF1; // = "F1";    static const std::string KeyF2; // = "F2";    static const std::string KeyF3; // = "F3";    static const std::string KeyF4; // = "F4";    static const std::string KeyF5; // = "F5";    static const std::string KeyF6; // = "F6";    static const std::string KeyF7; // = "F7";    static const std::string KeyF8; // = "F8";    static const std::string KeyF9; // = "F9";    static const std::string KeyF10; // = "F10";    static const std::string KeyF11; // = "F11";    static const std::string KeyF12; // = "F12";    static const std::string KeyPos1; // = "Pos1";    static const std::string KeyEnd; // = "End";    static const std::string KeyInsert; // = "Insert";    static const std::string KeyPageUp; // = "PageUp";    static const std::string KeyPageDown; // = "PageDown";    static const std::string KeySpace; // = "Space";    // End special keys

3. Modifier Keys

mitk::InteractionEvent::ModifierKeys表示按下辅助按键,多个辅助按键同时被按下,中间用逗号隔开:

<!-- shift and control key are pressed --><attribute name="Modifiers" value="shift,ctrl"/>

4. 滚动方向

mitk::MouseWheelEvent事件,表示鼠标滚轮滚动的方向。

<attribute name="ScrollDirection" value="up"/><!-- or --><attribute name="ScrollDirection" value="down"/>

5. 例子

键盘事件例子:

<config>  <!-- Event of key 'a' pressed -->  <event_variant class="InteractionKeyEvent" name="StdA">    <attribute name="Key" value="A"/>  </event_variant>  <!-- Event of key 'b' pressed  while modifiers ctrl and shift are pressed-->  <event_variant class="InteractionKeyEvent" name="StdB">    <attribute name="Key" value="B"/>    <attribute name="Modifiers" value="shift,ctrl"/>  </event_variant></config>

鼠标按键事件:

<!-- Standard left click --><config>  <event_variant class="MousePressEvent" name="StdMousePressPrimaryButton">    <attribute name="EventButton" value="LeftMouseButton"/>  </event_variant><!-- right click with control key pressed-->  <event_variant class="MousePressEvent" name="RightWithCTRL">    <attribute name="EventButton" value="RightMouseButton"/>    <attribute name="Modifiers" value="ctrl"/>  </event_variant></config>

MITK中已经有一个写好的关于大多数事件的标准configuration文件,叫GlobalConfig.xml。可以作为默认config文件,或者是在此基础上扩展。

6. 参数描述

config文件中还可以存储参数,比如:

<config name="example2"> <param name="property1" value="yes"/> <param name="scrollModus" value="leftright"/></config>

怎么写State Machine

状态机以XML文件的格式存储。

1. states

States用state标签标示。每个状态都有一个名字。每个状态机中都必须有一个state作为开始状态,表示状态机构造完成后处于这个初始状态。一个有效的状态机例子如下:

<statemachine> <state name="start" startstate="true"/></statemachine>

有时候,可以选择性地给state增加一个特殊mode,影响事件的转发。这几个mode为:GRAB_INPUT , PREFER_INPUT 和 REGULAR。REGULAR为默认值,无需明确指出。只在必要时使用这些special mode,因为它们让其他DataInteractors都无法接收到事件。

2. Transitions

Transitions表示状态的过渡,对于交互至关重要。Transition包含触发过渡的事件以及所要过渡到的状态。 event class描述事件类型(去mitk::InteractionEvent查看不同的类),event variant代表具体的事件,这两者共同决定哪个事件能够触发过渡。比如StdMousePressPrimaryButton事件发生时(左键按下),状态机将从状态A过渡到状态B。

Event Class

给定如下继承结构的类:

这里写图片描述

在状态机里,mitk::InteractionPositionEvent 可以被声明为 event class,代表带有位置信息的事件。而它真正是哪个鼠标按键事件可以在config文件中给出。这样, mitk::InteractionPositionEvent itself, 或者mitk::MousePressEvent, mitk::MouseReleaseEvent, mitk::TouchEvent都能在状态机里以同一个身份出现,不管输入设备是什么,只要在相同的类继承结构中,状态机就能以不变应万变。

<statemachine> <state name="A" startstate="true">   <transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="B"/> <state/> <state name="B" /></statemachine>

3. Actions

Actions要被添加到transitions中,表示过渡中需要执行的函数。下面这个状态机监听鼠标左键事件并执行两个动作(循环往复,永不停止)。

<statemachine>    <state name="start" startstate="true">        <transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="start">            <action name="addPoint"/>            <action name="countClicks"/>        </transition>    </state></statemachine>

为了告诉mitk::DataInteractor需要执行哪个函数, mitk::DataInteractor 使用CONNECT_FUNCTION将action和函数连接起来。下面表示一个继承了DataInteractor的ExampleInteractor类,该类实现了AddPoint和CountClicks两个函数。CONNECT_FUNCTION必须在重写的ConnectActionsAndFunctions()虚拟函数中使用。

void mitk::ExampleInteractor::ConnectActionsAndFunctions(){  CONNECT_FUNCTION("addPoint", AddPoint);  CONNECT_FUNCTION("countClicks", CountClicks);}

4. Conditions

Conditions可以添加在transitions中,mitk::DataInteractor要进行状态过渡时会调用它们。condition决定是否能够状态过渡以及是否能够执行action。

例子

<statemachine>    <state name="start" startstate="true">        <transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="start">            <condition name="checkPoint"/><!-- 如果条件满足,执行下面两个动作 -->            <action name="addPoint"/>            <action name="countClicks"/>        </transition>        <transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="start">            <condition name="checkPoint" inverted="true"/><!-- 如果条件不满足,执行下面动作 -->            <action name="doSomethingElse"/>        </transition>    </state></statemachine>

需要在ConnectActionsAndFunctions()函数中进行连接:

void mitk::ExampleInteractor::ConnectActionsAndFunctions(){  CONNECT_CONDITION("checkPoint", CheckPoint);  CONNECT_FUNCTION("addPoint", AddPoint);  CONNECT_FUNCTION("countClicks", CountClicks);  CONNECT_FUNCTION("doSomethingElse", DoSomethingElse);}

注意:上面例子中我们可以看到针对同一condition,我们可以定义这个条件在满足以及不满足(inverted是否设为true)情况下的行为。

连接config和状态机

自定义的config和state machine文件需要存储在自定义插件或模块中的/Resources/Interactions文件夹内。并将文件路径添加到对应的files.cmake文件中。像这样:

set(RESOURCE_FILES
Interactions/CustomStateMachinePattern.xml
Interactions/CustomConfig.xml
)

当从模块中载入此二文件时,需要把模块作为参数:

#include "usModule.h"#include "usGetModuleContext.h"... ...m_CurrentInteractor = mitk::CustomDataInteractor::New();m_CurrentInteractor->LoadStateMachine("CustomStateMachinePattern.xml", us::GetModuleContext()->GetModule());m_CurrentInteractor->SetEventConfig("CustomConfig.xml", us::GetModuleContext()->GetModule());... ...

参考注册状态机和config。

重写mitk::DataInteractor

自定义DataInteractor需要继承mitk::DataInteractor,函数的写法如下:

对于actions:
bool SomeFunctionality(StateMachineAction* , InteractionEvent*);

对于conditions:
bool SomeFunctionality(const InteractionEvent*);

使用ConnectActionsAndFunctions()函数将对应函数连接到actions、conditions:

void mitk::ExampleInteractor::ConnectActionsAndFunctions(){ CONNECT_CONDITION("checkPoint", CheckPoint); CONNECT_FUNCTION("addPoint", AddPoint); CONNECT_FUNCTION("enoughPoints", EnoughPoints);}

现在只要写好state machine 和config就行了。

示例可参考 mitk::PointSetDataInteractor。里面有完整的注释。

使用InternalEvent的Interactor例子

创建DataInteractors时一个有用的工具是mitk::InternalEvent,它能够让mitk::DataInteractor自己发送信号。

下面举例说明如何写一个mitk::DataInteractor。ExampleInteractor类不断地添加点,直到接收到一个 mitk::InternalEvent事件,告诉它点已经达到指定数量。事先指定的数量可以保存在config文件中。下面先写状态机:

<statemachine>    <state name="start" startstate="true" >        <transition event_class="MousePressEvent" event_variant="AddPointClick" target="start">            <condition name="checkPoint"/>            <action name="addPoint"/>        </transition>        <transition event_class="InternalEvent" event_variant="enoughPointsAdded" target="final">            <action name="enoughPoints"/>        </transition>    </state>    <state name="final">    <--! dead state, nothing happens any more, once we reached this -->    </state></statemachine>

在config文件中,将点的最大数量设为10,并将AddPointClick定义为鼠标右键和ctrl键同时按下。

<config>  <param name="NumberOfPoints" value="10">   <event_variant class="MousePressEvent" name="AddPointClick">    <attribute name="EventButton" value="RightMouseButton"/>    <attribute name="Modifiers" value="ctrl"/>  </event_variant></config>

本例可见
Step10.h
Step10.cpp

在类中,重写函数:

protected:    ExampleInteractor();    virtual ~ExampleInteractor();    virtual void ConnectActionsAndFunctions();    virtual void ConfigurationChanged();

连接函数:

void mitk::ExampleInteractor::ConnectActionsAndFunctions(){  // connect the action and condition names of the state machine pattern with function within  // this DataInteractor  CONNECT_CONDITION("checkPoint", CheckPoint);  CONNECT_FUNCTION("addPoint", AddPoint);  CONNECT_FUNCTION("enoughPoints", EnoughPoints);}

ConfigurationChanged函数将在新的configuration文件载入时被调用(由mitk::InteractionEventHandler调用)。这个函数允许执行初始化语句,本例中我们想设定点数上限。

void mitk::ExampleInteractor::ConfigurationChanged(){  // read how many points we accept from the config properties  mitk::PropertyList::Pointer properties = GetPropertyList();  std::string maxNumber;  properties->GetStringProperty("NumberOfPoints",maxNumber);  m_MaximalNumberOfPoints = atoi(maxNumber.c_str());}

接下来,需要写action函数了,它们的声明如下:

private:    bool AddPoint(StateMachineAction* , InteractionEvent*); // function to add new points    bool EnoughPoints(StateMachineAction* , InteractionEvent*); // function changes color of pointset to indicate, it is full    bool CheckPoint(const InteractionEvent* interactionEvent); // function checks if the clicked point is valid
bool mitk::ExampleInteractor::AddPoint(StateMachineAction*, InteractionEvent* interactionEvent){  // cast InteractionEvent to a position event in order to read out the mouse position  // we stay here as general as possible so that a different state machine pattern  // can reuse this code with MouseRelease or MouseMoveEvents.  InteractionPositionEvent* positionEvent = dynamic_cast<InteractionPositionEvent*>(interactionEvent);  if (positionEvent != NULL)  {    // query the position of the mouse in the world geometry    mitk::Point3D point = positionEvent->GetPositionInWorld();    m_PointSet->InsertPoint(m_NumberOfPoints, point, 0);    m_NumberOfPoints++;    GetDataNode()->SetData(m_PointSet);    GetDataNode()->Modified();    if (m_NumberOfPoints != 0 && m_NumberOfPoints >= m_MaximalNumberOfPoints)    {      // create internal event that signal that the maximal number of points is reached      InternalEvent::Pointer event = InternalEvent::New(NULL,this, "enoughPointsAdded");      // add the internal event to the event queue of the Dispatcher      positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer());    }    // update the RenderWindow to show new points    mitk::RenderingManager::GetInstance()->RequestUpdateAll();    return true;  }  else  {    return false;  }}bool mitk::ExampleInteractor::EnoughPoints(StateMachineAction*, InteractionEvent*){  GetDataNode()->SetProperty("contourcolor", ColorProperty::New(1.0, 1.0, 0.0));  mitk::RenderingManager::GetInstance()->RequestUpdateAll();  return true;} //-

如果条件函数返回false,那么过渡以及它的action将不执行。如果条件不通过,这个事件就被认为是未处理的,将被转交给其它Interactors。

bool mitk::ExampleInteractor::CheckPoint(const InteractionEvent *interactionEvent){  // check if a point close to the clicked position already exists  float epsilon = 0.3; // do not accept new points within 3mm range of existing points  InteractionPositionEvent* positionEvent = dynamic_cast<InteractionPositionEvent*>(interactionEvent);  if (positionEvent != NULL)  {    // query the position of the mouse in the world geometry    mitk::Point3D point = positionEvent->GetPositionInWorld();    int retVal = m_PointSet->SearchPoint(point, epsilon);    if ( retVal == -1 ) // SearchPoint returns -1 if no point was found within given range      return true;  }  return false; // if the positionEvent is NULL or a point was found return false. AddPoint will not be executed  //end

这里,一个internal event将在点数达到上限后发送消息。该事件在创建后需要添加到dispatchers事件队列中:

// create internal event that signal that the maximal number of points is reached      InternalEvent::Pointer event = InternalEvent::New(NULL,this, "enoughPointsAdded");      // add the internal event to the event queue of the Dispatcher      positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer());

nternal events不需要映射到event variants。它们的信号名字与event variant相同。

了解更多可参考
mitk::DataInteractor和mitk::InteractionEventObserver
mitk::PointSetDataInteractor
mitk::DisplayInteractor

0 0
原创粉丝点击