1.Slicer MRML数据类型综述
- MRML提供API(应用程序接口)管理医学图像数据类型(Volume、Model、Transform、Fiducial、Camera等)和它们的可视化;
- 每种数据类型都用一个特定的MRML节点表示;
- MRML场景就是MRML节点的集合;
- Slicer的MRML数据模型(data Model)独立于Slicer系统的可视化组件Visualization和算法组件Algorithm;
- 其他的Slicer组件,Logic和Qt Widget观测MRML场景和每个节点的变化。
2.MRML 场景
- MRML场景管理MRML节点:添加、删除、寻找等;
- MRML Scene provides persistence of MRML nodes (reading/writing to/from XML file)
3.MRML 节点
- MRML节点用于存贮Slicer应用的状态,包括原始数据和可视化、参数。一组存储核心Slicer Module状态的的核心MRML节点如下所示:
- MRML节点以C++类等级的形式被组织,所有的子类均继承vtkMRMLNode;
- 所有的MRML节点必须应用某种标准的API:ReadAttributes、WriteAttributes、Copy等;
- 相同的数据可以用不同的方式进行可视化,并储存成不同的类型。因此,信息分别以三种不同节点类型进行存储:当一个数据节点被创建的时候,我们必须创建相应的显示节点与数据存储节点,方便进行合理的可视化和保存文件。MRMLLogic会提供函数——创建一个节点并与显示节点&数据节点关联。注意:在一些情况下,Slicer会检测是否显示节点与存储节点遗失,并尝试创建默认节点,但是作为开发人员我们不应该依赖这种错误校正机制。
- 数据节点:存储原始数据,例如vtkMRMLScalarVolumeNode存储体素、spacing、位置、方向
- 显示节点:描述了数据怎么样被可视化,对于相同的原始数据可能会有很多的显示节点,例如一个节点进行体绘制、另外一个节点用于显示一张图像
- 存储节点:描述数据应该如何永久性存储到磁盘中,文件格式、文件名
4.MRML节点属性
MRML节点可以成对存储自定义属性:属性名+属性值。
To avoid name clashes custom modules that adds attributes to nodes should prefix the attribute name with the module's name and the '.' character. Example: the DoseVolumeHistogram module can use attribute names such as DoseVolumeHistogram.StructureSetName, DoseVolumeHistogram.Unit, DoseVolumeHistogram.LineStyle.
5.MRML节点引用
这里有两种机制来指定节点间是相关的:层次结构节点&节点引用。相比之下,节点引用更简单。
MRML节点可以利用节点引用API引用或者观测其他的MRML节点。这个框架会自动处理:
- 节点引用的读、写、复制
- 场景导入更新引用
- 增加或删除节点
一个节点可能引用很多节点,每个节点都扮演了不同的角色,并且每个节点都有唯一的字符串进行处理。
数据节点可以选择性地应用接口:如Displayable、Storable、Transformable。每一个接口都有自己的需求。例如,变换模块接口期待与变换节点相关联。这种关联就是通过创建MRML Reference完成的。
5.1 MRML Reference:API描述
上面的节点应用API功能是在vtkMRMLNote基类中实现的。
The only thing that needs to happen in thederived MRML node class(usually in the constructor of the class) is a call tovtkMRMLNode::AddNodeReferenceRole(const char *referenceRole, const char *mrmlAttributeName) that takes a unique string defining the reference role between this node and the referenced node, and a MRML attribute name for storing the reference in the .mrml file.
The only other call that is needed is either:
vtkMRMLNode* SetAndObserveNodeReferenceID(const char* referenceRole , const char* referencedNodeID, vtkIntArray *events=0);
or
vtkMRMLNode* AddAndObserveNodeReferenceID(const char* referenceRole , const char* referencedNodeID, vtkIntArray *events=0);
vtkMRMLNode也提供了虚回调函数,可以在继承类中拓展:
1 OnNodeReferenceAdded(vtkMRMLNodeReference *reference)
2 OnNodeReferenceRemoved(vtkMRMLNodeReference *reference)
3 OnNodeReferenceModified(vtkMRMLNodeReference *reference)
默认情况下,这些方法生成一下事件:
1 vtkMRMLNode::ReferenceAddedEvent
2 vtkMRMLNode::ReferenceRemovedEvent
3 vtkMRMLNode::ReferenceModifiedEvent
在继承类中,这些方法可以采用VTKMRMLNode拓展,以允许查询节点的引用。
vtkMRMLNode成员的完整列表参考如下网址,包括所有继承的成员:
http://apidocs.slicer.org/master/classvtkMRMLNode-members.html
目前,下述的几个MRML节点是采用MRML节点引用API实现的:
- vtkMRMLStorableNode
- vtkMRMLTransformableNode
- vtkMRMLDisplayableNode
其他引用MRML节点,例如vtkMRMLColorTableNode, vtkMRMLDiffusionTensorDisplayPropertiesNode,目前还没有只用这种API。
5.2 MRML Reference:示例
vtkMRMLTransformableNode implementation:
http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Libs/MRML/Core/vtkMRMLTransformableNode.h?view=log
vtkMRMLDisplayableNodeimplementation:
http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Libs/MRML/Core/vtkMRMLDisplayableNode.h?view=log
vtkMRMLStorableNodeimplementation:
http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Libs/MRML/Core/vtkMRMLStorableNode.h?view=log
6.MRML事件与观测者Event-Observe
- MRML场景和各个节点的改变会传递给其他观测者节点,GUI和Logic对象通过vtk事件、命令-观测者机制。
- 使用vtkAddObserver()和InvokeEvent()方法。vtkSetMicro生成ModifiedEvent。
- 对于MRML,命令-观测者机制采用助手vtkObserverManager、类、MRML观测者宏、ProcessMRMLEvent方法实现。
- 观测者应该存储一个注册的指针,该指针指向MRML节点,防止一个已经删除对象的回调函数。
- MRML 观察者宏定义位于 Libs/MRML/vtkMRMLNode.h
- vtkSetMRMLObjectMacro - 注册 MRML节点
- vtkSetAndObserveMRMLObjectMacro - 注册 MRML 节点,并为vtkCommand::ModifyEvent增加一个观察者
- vtkSetAndObserveMRMLObjectEventsMacro - 注册MRML节点并为一个指定的事件集合设置观测者
- SetAndObserveMRMLScene[Events]() 方法在GUI/Logic中使用,用于观察变化、新场景、增加节点等事件
- ProcessMRMLEvents method在MRML节点、Logic、GUI类中实现,在观察节点中处理事件
7.创建自定义的MRML节点类
- 自定义MRML节点为模块参数提供了永久性存储
- 自定义MRML节点应该使用RegistraterNodeClass()在MRML场景中注册,因此他们可以从一个场景文件中恢复或者保存
- 这个类应该实现以下方法:
- CreatNodeInstance()-与VTK的New()相似,只不过不是静态的
- GetNodeTagName()-为这个节点返回一个独一无二的XML标签
- ReadXMLAttributes()-从XML文件中读取节点属性,如name-value
- WriteXML()-将节点属性写入到输出流
- Copy()-拷贝节点属性
- 如果该节点已经引用其他的节点,那么下面的方法也应该实现:向MRML场景中添加新的节点
- UpdateReferenceID()-更新对另一个节点已经存储的应用
- UpdataScene()-依赖这个节点更新场景中的其他节点
一个实现自定义MRML节点的例子:
vtkMRMLGradientAnisotropicDiffusionFilterNode inModules/GradientAnisotropicDiffusionFilter directory.
8.MRML层次结构节点
http://apidocs.slicer.org/master/classvtkMRMLHierarchyNode.html
9.MRML:Developer FAQ
9.1 如何在场景中增加一个MRML节点?
1 vtkNew<vtkMRML???Node> nodeToAdd;
2 ...
3 mrmlScene->AddNode(nodeToAdd.GetPointer());
1 vtkNew<vtkMRMLModelNode> modelNode;
2 modelNode->SetPolyData(polyData);
3 mrmlScene->AddNode(modelNode.GetPointer());
1 vtkSlicerModelsLogic* modelsLogic = ...;
2 //modelsLogic->SetMRMLScene(mrmlScene);
3 modelsLogic->AddModel(polyDataFileName);
9.2 加载一个MRML场景时,如果碰到以下信息该怎么办?
1 ERROR: In /path/to/VTK/Imaging/vtkImageMapToColors.cxx, line 153
2 vtkImageMapToColors (0x268f190): RequestInformation: No LookupTable was set but number of components in input doesn't match OutputFormat, therefore input can't be passed through!
3
4 ERROR: In /path/to/VTK/Imaging/vtkImageExtractComponents.cxx, line 239
5 vtkImageExtractComponents (0x26947e0): Execute: Component 1 is not in input.
6
7 ERROR: In /path/to/VTK/Imaging/vtkImageExtractComponents.cxx, line 239
8 vtkImageExtractComponents (0x26947e0): Execute: Component 1 is not in input.
9
10 [...]
确保每一个VolumeDisplay节点都设置了ColorNodeRef属性。
9.3 如何在2D视窗中改变立体?
1 appLogic = slicer.app.applicationLogic()
2 selectionNode = appLogic.GetSelectionNode()
3 selectionNode.SetReferenceActiveVolumeID(bg)
4 selectionNode.SetReferenceSecondaryVolumeID(fg)
5 appLogic.PropagateVolumeSelection()