一、模块简介
该模块是用来管理slicer中的标签(markups)的,基于Slicer 3.6中Fiducials module的功能,其中的元素从Slicer 4.2的Annotations module转移而来。目前该模块中仅支持基准点(fiducials)元素的使用。
模块的功能主要是创建、编辑场景中的标签以及一些附带信息,当前支持的标签是基准点列表(lists of fiducial points),基准点可以使用鼠标模式工具条在场景中交互式的放置。当进入到基准点放置模式时,也可以通过键盘上的P键来在鼠标位置放置一个新的基准点,单机鼠标右键可以离开连续放置模式。
二、应用方向
该模块允许你在一个激活的标签基准点列表中添加新的基准点标签,以及对它进行编辑,删除等操作,以及更改基准点的显示属性
三、界面面板
该模块的界面面板有如下及部分组成:
1.Markups List
模块的顶层设置,包含一些常用设置选项:
- List:用来选择场景中的标签基准点节点(Markups fiducial node)即表单list,并更新主面板使其处于能修改的状态,也可以用其来创建新的节点。
- Visibility:切换表单中标签基准点的可视状态,会覆盖掉下面标签基准点自己的可视性。
- Locked:切换表单中标签基准点的上锁状态,同样覆盖掉下面标签基准点自己的上锁性,上锁后,将不能在下面的表格中修改基准点的属性。
- Scale:设置表单中标签基准点的图标和文字到相同的大小。
- Click to Jump Slices:选中这个选项后,单击下面表格中列出的标签基准点使其高亮,那么会在slice二维视图窗口将切面跳转到该基准点处,下面的单选按钮可以控制在跳转后是否基准点显示在中央。即使没选中这个选项,也可以右键单击表格中基准点使切面跳转到该基准点处,选中一个基准点后也可以使用上下键来改变高亮基准点也会使切面跳转。
2.Buttons And Checkboxes
Buttons 可以对这个表单中的标签基准点做一些修改,Checkboxes会改变表单中标签基准点的的显示模式。
- Toggle visibility flag:切换表单中所有标签基准点的可视性,有下拉菜单。
- Toggle selected flag:切换表单中所有标签基准点的选择标志,有下拉菜单。只有选中的标签才可以被传递到命令行模块(command line modules)。
- Toggle lock flag:切换表单中所有标签基准点的上锁状态,有下拉菜单。
- Delete the highlighted markups from the active list:将被高亮选择的标签基准点的行删除。
- Remove all markups from the active list:移除该表单中的所有标签基准点。
- Transformed:选中的话将会在表格中显示转换坐标,这将会把转化节点应用到表单中的基准点上并显示结果。没选中则显示实际的RAS坐标值。
- Hide RAS:选择在表格中是否隐藏坐标列,隐藏状态下也可以通过在表格的基准点上右击来获取坐标。
3.Markups Table
在该表格的行上右击,会弹出一个快捷菜单,其中会显示精确地坐标信息,以及同时选中多个高亮点的话显示点之间距离,菜单中还有一些功能操作包括删除高亮点,跳转二维视图中的切面,聚焦三维视图,以及在表单之间移动标签基准点。
- Selected:这列有个选择框,它的选择状态即该标志基准点的旋转状态。可以切换基准点的选择状态,只有选中的标签才可以被传递到命令行模块(command line modules)。
- Locked:这列的锁的状态决定了基准点的上锁状态。
- Visibility:这列眼睛的状态决定了基准点的可视化状态。
- Name:标志基准点的名字,较短,在视图中作为文本显示,放在图标之后。
- Description:关于该标签的较长的描述,不会再视图中显示。
- X, Y, Z:该标签的RAS坐标,三位有效数字。
4.Advanced: Buttons、Naming、Conversion
- Buttons:Move Up: 将表单中高亮的标签上一一个位置。
- Buttons:Move Down: 下一一个位置
- Buttons:Add Markup:在选择的表单中添加一个标签基准点,默认在原点。
- Naming:Name Format:为新创建的标签设置名字格式,使用C语言中sprintf 的格式类型,%N被表单名字替代,%d用整数替代。
- Naming:Apply:根据当前的设置的名字格式为表单中的所有标签重新命名,一般会保留数字。若想快速将标签根据它们的索引重新命名,可以先将命名格式中数字去掉,apply重命名,然后在命名格式中加上数字格式%d,再一次apply重命名,就可以快速完成命名。
- Conversion:Convert Annotation Fiducials to Markups Fiducial Lists:将注释基准点分层作为标签表单建立。
5.Advanced: Display Properties
用来设置图标类型、大小、颜色、透明度,以及文本的颜色、大小透明度等,还包括默认设置的操作。
6.Advanced: Display Properties: Fiducuial Projection
用来控制基准点的投影,高亮某个基准点,在2D视图中显示该基准点所在的切面,这部分就是用来控制其余的基准点在该切面上投影显示的。
- 2D Projection:控制是否显示投影。
- Use Fiducial Color:控制是否使用基准点原来的颜色。
- Projection Color:自定义投影颜色。
- Outlined Behind Slice Plane:切面下基准点的投影采用轮廓方式显示,切面上的基准点投影采用实心显示,且两种情况的投影均采用下面设置的透明度,基准点在切面上时,满透明度。一些图标样式无法用轮廓表示。
- Projection Opacity:投影的透明度。
四、开发者必晓
1.Python开发
- 使用python打开Markups Module,将Markups Module用户界面设置为当前界面:
slicer.util.mainWindow().moduleSelector().selectModule('Markups')
- 从文件中加载标志基准点表单:
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv') #当前路径为slicer.exe所在的路径
- 使用代码在当前表单中添加标志基准点:
slicer.modules.markups.logic().AddFiducial() #默认放在原点处
- 上面的命令添加参数初始化新建基准点的位置:
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)
- 通过鼠标点击来添加基准点,将鼠标模式设置为标志基准点放置模式:
placeModePersistence = 1
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)
也可以较低级的方式来实现,通过对selection和interaction nodes的操作:
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
placeModePersistence = 1
interactionNode.SetPlaceModePersistence(placeModePersistence)
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place
interactionNode.SetCurrentInteractionMode(1)
放置完基准点后,切换鼠标模式到view transform模式:
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
interactionNode.SwitchToViewTransformMode()
# also turn off place mode persistence if required
interactionNode.SetPlaceModePersistence(0)
- 获取基准点的属性,标志基准点表单在代码中用类vtkMRMLMarkupsFiducialNode表示,并且每一个vtkMRMLMarkupsFiducialNode的实例都有一个指针,可以用python来获取:
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")
n = fidNode.AddFiducial(4.0, 5.5, -6.0) #n获得新添加的基准点在表单中的索引。
fidNode.SetNthFiducialLabel(n, "new label") #设置基准点的名字
# each markup is given a unique id which can be accessed from the superclass level
id1 = fidNode.GetNthMarkupID(n)
# manually set the position
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)
# set the label
fidNode.SetNthFiducialLabel(n, "New label")
# set the selected flag, only selected = 1 fiducials will be passed to CLIs
fidNode.SetNthFiducialSelected(n, 1)
# set the visibility flag
fidNode.SetNthFiducialVisibility(n, 0)
- 可以循环表单中的基准点并获取它们的坐标
fidList = slicer.util.getNode('F')
numFids = fidList.GetNumberOfFiducials()
for i in range(numFids):
ras = [0,0,0]
fidList.GetNthFiducialPosition(i,ras)
# the world position is the RAS position with any transform matrices applied
world = [0,0,0,0]
fidList.GetNthFiducialWorldCoordinates(i,world)
print i,": RAS =",ras,", world =",world
可以查看 Endoscopy module的代码来了解怎样用python在脚本模块(scripted module)获取基准点
- VTK Widget 的获取:
与标志基准点相关的3D显示管理器可以通过python获取,允许对vtkSeedWidget的调试:
fidNode = slicer.mrmlScene.GetNodeByID("vtkMRMLMarkupsFiducialNode1") #获取指定的标志基准点表单节点对象指针
lm = slicer.app.layoutManager() #获取slicer的布局管理器
td = lm.threeDWidget(0) #由布局管理器获取三维显示部件
ms = vtk.vtkCollection() #创建容器
td.getDisplayableManagers(ms) #将三维显示部件中的所有显示管理器放入容器,三维显示部件中
for i in range(ms.GetNumberOfItems()): #有多个类的显示管理器,如AnnotationRuler和AnnotationROI等
m = ms.GetItemAsObject(i)
if m.GetClassName() == "vtkMRMLMarkupsFiducialDisplayableManager3D": #当遍历到的显示管理器是标志基准点的3D显示管理器时
print i, m.GetClassName()
h = m.GetHelper()
seedWidget = h.GetWidget(fidNode) #获取对应于不同标志基准点表单节点的种子部件
seedRepresentation = seedWidget.GetSeedRepresentation()
handleRep2 = seedRepresentation.GetHandleRepresentation(2) #标志基准点表单中索引为2的基准点显示信息
print handleRep2.GetDisplayPosition()
print handleRep2.GetWorldPosition()
三维部件中的显示管理器:
同样的,与标志基准点相关的2D显示管理器可以通过python获取,也可以调试2D中的seed widget:
fidNode = slicer.mrmlScene.GetNodeByID("vtkMRMLMarkupsFiducialNode1")
lm = slicer.app.layoutManager()
redWidget = lm.sliceWidget("Red")
redView = redWidget.sliceView()
ms = vtk.vtkCollection()
redView.getDisplayableManagers(ms)
for i in range(ms.GetNumberOfItems()):
m = ms.GetItemAsObject(i)
if m.GetClassName() == "vtkMRMLMarkupsFiducialDisplayableManager2D":
print i, m.GetClassName()
h = m.GetHelper()
seedWidget = h.GetWidget(fidNode)
seedRepresentation = seedWidget.GetSeedRepresentation()
handleRep = seedRepresentation.GetHandleRepresentation(0)
print handleRep.GetDisplayPosition()
二维部件中的显示管理器
2.C++开发
- Selection and Interaction
对于Selection and Interaction节点,Selection节点一般用于设置改变操作节点的类型,如注释标尺、注释ROI等。Interaction节点用于改变鼠标模式和一些交互操作。首先确保你获取到了在场景中存在的单节点,而不是自己创建,类型为vtkMRMLInteractionNode和vtkMRMLSelectionNode,ID为vtkMRMLInteractionNodeSingleton和vtkMRMLSelectionNodeSingleton:
vtkMRMLApplicationLogic *mrmlAppLogic = this->GetMRMLApplicationLogic();
vtkMRMLInteractionNode *inode = mrmlAppLogic->GetInteractionNode();
vtkMRMLSelectionNode *snode = mrmlAppLogic->GetSelectionNode();
如果在mrml application logic中得不到这两种节点,可以从场景中获取:
vtkMRMLInteractionNode *interactionNode = vtkMRMLInteractionNode::SafeDownCast(this->GetMRMLScene()->GetNodeByID("vtkMRMLInteractionNodeSingleton"));
接下来你就可以调用节点中的方法来实现你想要的效果,或者添加自己的场景事件观察者:
vtkSlicerMarkupsModuleLogic::ObserveMRMLScene
会监视场景中的节点变化,接下来可以在vtkSlicerMarkupsModuleLogic::ProcessMRMLNodesEvents中查看怎样对interaction node的改变做出反应。
Slicer4/Base/QTGUI/qSlicerMouseModeToolBar.cxx中也有一些代码采用简便的方式在 mrml app logic 中获取两类节点。
调用下面的方法将鼠标模式转变为基准点放置模式:
selectionNode->SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode");
interactionNode->SetCurrentInteractionMode(vtkMRMLInteractionNode::Place);
如果你没有调用interaction节点中的方法SetPlaceModePersistence()来设置PlaceModePersistence,那么在你进行完一次放置后,当前的交互模式将返回到原来的view transform,你讲需要重新设置交互模式来进行下一步的放置(前面设置的PlaceNodeClassName不会改变)。简单来讲就是你并没有设置注释的连续放置,那么在防止玩一次之后还要重新将交互模式设置为放置模式,但放置的节点类型做了保留,不用重新设置。
- 编程获取基准点
这部分解释怎样获取用户添加到场景中的基准点节点。
第一部分:在你的模块的Logic类中,重写SetMRMLSceneInternal函数,来观察场景中的节点添加事件(NodeAddedEvent):
void vtkSlicerMYMODULEModuleLogic::SetMRMLSceneInternal(vtkMRMLScene * newScene)
{
vtkNew<vtkIntArray> events;
events->InsertNextValue(vtkMRMLScene::NodeAddedEvent);
// Optionally you can add more events here,
// such as vtkMRMLScene::NodeRemovedEvent
this->SetAndObserveMRMLSceneEventsInternal(newScene, events.GetPointer());
}
第二部分:通过上面的设置和观察mrml场景宏,任何时候在场景中添加节点,将会触发调用Logic基类的方法 method vtkMRMLAbstractLogic::ProcessMRMLNodesEvents,在这个方法里又会调用虚方法 vtkMRMLAbstractLogic::OnMRMLSceneNodeAdded(vtkMRMLNode* node),你可以在自己的Logic中重写该方法完成对节点的获取:
void vtkSlicerMYMODULEModuleLogic::OnMRMLSceneNodeAdded(vtkMRMLNode* addedNode)
{
vtkMRMLMarkupsFiducialNode* fidNode =
vtkMRMLMarkupsFiducialNode::SafeDownCast(addedNode);
if (fidNode)
{
// here you write what you need to do when a
// fiducial node is added into the scene
}
}
2.该类文件.fcsv的格式
.fcsv文件中保存的就是在场景中添加的标志基准点表单节点的相关数据,打开这类文件:
使用逗号分隔值文件来存储节点:
第一行注释行给出slicer版本信息:
# Markups fiducial file version = 4.7
第二行注释行给出使用的坐标系统(RAS = 0, LPS = 1, IJK = 2 ):
# CoordinateSystem = 0
第三行注释行给出下面的列信息:
# columns = id,x,y,z,ow,ox,oy,oz,vis,sel,lock,label,desc,associatedNodeID
接下来每一行代表一个基准点信息,按上面的标注排序:
vtkMRMLMarkupsFiducialNode_4,19.8892,3.10894,-10.2143,0,0,0,1,1,1,0,F_1-1,,vtkMRMLScalarVolumeNode1
- id = a string giving a unique id for this fiducial, usually based on the class name
- x,y,z = the floating point coordinate of the fiducial point
- ow,ox,oy,oz = the orientation quaternion of this fiducial, angle and axis, default 0,0,0,1 (or 0,0,0,0,0,0,1.0)
- vis = the visibility flag for this fiducial, 0 or 1, default 1
- sel = the selected flag for this fiducial, 0 or 1, default 1
- lock = the locked flag for this fiducial, 0 or 1, default 0
- label = the name for this fiducial, displayed beside the glyph, with quotes around it if there is a comma in the field
- desc = a string description for this fiducial, optional
- associatedNodeID = an id of a node in the scene with which the fiducial is associated, for example the volume or model on which the fiducial was placed, optional
这类文件可以使用字符串流和getline来解析:
std::fstream fstr;
fstr.open(fileName.c_str(), std::fstream::in);
char line[1024];
while (fstr.good())
{
fstr.getline(line, 1024);
std::stringstream ss(line);
std::string component;
getline(ss, component, ',');
[...]
}
参考:
https://www.slicer.org/wiki/Documentation/4.8/Modules/Markups#References