⛳️ 前言
在我们日常需求中,除了需要对影像进行可视化展示外,大多数场景下还需要对影像进行调整、注释、分割等操作。Cornerstone3DTools库则支持大多数需要的交互功能。CornerstoneTools支持的工具类型主要分为以下4类:
-
基础交互类工具:可以在视口中对影像进行操作
-
注释类工具: 为影像进行注释、测量等操作
-
分割类工具: 对影像进行分割、标记等操作
-
同步器工具: 使多个视图进行同步
针对以上4大类型,我们分为上中下3篇文章来展开介绍。上篇则主要对基础交互类工具和同步器工具进行介绍,围绕常用工具的种类、状态及如何注册使用等知识点展开。
🚀 效果演示
点击查看完整代码,欢迎star
🪜 交互工具
工具组
工具
我们来看一下官网对于工具的定义,工具是至少实现了BaseTool
接口的未实例化类。这样看来,一个工具其实就是一个类。我们除了可以使用官网中提供的这些工具之外,我们也可以根据它的定义去自定义一些我们需要的一些工具,关于自定义工具后面会详细说明,在这里我们主要是去介绍它内置的一些工具。如果我们想要使用一个工具的话,首先我们要使用库的顶级addTool
函数来添加未实例化的工具,另外需要添加到工具组中。
工具组
以一种简单的方式去定义一个视图/一组工具的表现行为,具有相同工具组的视图将共享工具的配置项、模式等
常用工具
关于交互类工具,CornerstoneTools主要提供了以下功能, 🔥 标志为常用工具
🔥 PanTool, // 平移工具
🔥 TrackballRotateTool, // 3D旋转工具
🔥 DragProbeTool, // 探针工具
🔥 WindowLevelTool, // 窗宽窗距调整工具
WindowLevelRegionTool, // 窗宽窗距调整工具2
🔥 ZoomTool, // 平移工具
🔥 StackScrollTool, // 鼠标点击及拖动切换层级
🔥 PlanarRotateTool, // 2D旋转工具
🔥 StackScrollMouseWheelTool, // 鼠标滚动切换层级工具
VolumeRotateMouseWheelTool,
MIPJumpToClickTool,
工具状态
在了解完有哪些工具后,我们再来了解一下工具的状态有哪些?
-
Active: 当前工具被激活,可以响应鼠标交互;如果当前工具是注释类工具,在不选中已有的注释时,点击将创建新的注释。
-
Passive: 默认状态,工具非激活状态,不响应交互;如果当前工具是注释类工具,无法创建新的注释,但是如果选中已有注释的话可以拖拽移动。
-
Enabled: 启用状态,不响应交互。如果是注释类或分割类工具,将会渲染已有的操作内容。
-
Disabled: 禁用状态,既不响应交互也不渲染工具
关于操作类工具,我们用到的主要是Active
和 Disabled
两个状态,其他两种状态将在中篇注释工具中演示。
使用流程
在了解完基础的概念后,我们来看一下如何在代码中注册并激活工具。
注册工具
// 第一步:向全局状态中添加工具类
addTool(StackScrollMouseWheelTool);
// 第二步:向工具组中添加工具名
const toolGroup = ToolGroupManager.createToolGroup(groupId);
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
// 第三步:将工具组与视图绑定(注意!!:同一时间,一个viewport只能绑定一个ToolGroup)
toolGroup.addViewport(viewportId1, renderingEngineId);
// 第四步:禁用鼠标上下文菜单(如果需要定义右键功能的话)
['element1', 'element2', 'element3'].forEach(id => {
const dom = document.querySelector(`#${id}`);
dom.oncontextmenu = () => false;
})
激活工具
关于工具的四种状态,都可以通过setToolxxxx()
函数进行设置,以下为设置工具状态为激活状态
// 01 - 获取工具组
const toolGroup = ToolGroupManager.getToolGroup(groupId);
// 02 - 激活工具组的某个工具,并应该与左键【通过设置 mouseButton 属性,设置当前激活的工具是绑定与哪个按键/鼠标键的】
toolGroup.setToolActive(PanTool.toolName, {
bindings: [{mouseButton: cstEnums.MouseBindings.Primary}],
});
切换工具
通过上面两步就完成了工具的注册与激活,此时就可以直接使用当前激活的工具。但是一个视图中可能有多个工具需要切换,那在切换的时候需要注意以下事项:
-
当前工具是否已被激活,避免重复激活某个工具
-
当前键是否有已激活的工具,如果有则禁用掉,避免出现同时激活多个工具而导致的报错问题
const toolGroup = ToolGroupManager.getToolGroup(groupId);
// 第一步:获取当前左键已激活的工具
const activePrimaryToolName = toolGroup.getActivePrimaryMouseButtonTool();
// 第二步:避免重复激活某个工具
if (activePrimaryToolName === toolName) {
ElMessage({
message: '当前工具处已于激活状态,点击左键尝试操作',
type: 'warning',
})
return;
}
// 第三步:禁用掉已激活的工具
if (activePrimaryToolName) {
toolGroup.setToolDisabled(activePrimaryToolName);
}
// 第四步:启用当前选中的工具
toolGroup.setToolActive(toolName, {
bindings: [{mouseButton: cstEnums.MouseBindings.Primary}],
});
至此,就完成了一套工具的注册、激活、切换的流程,一般我们可以将需要注册的工具作为一个数组或配置项,遍历循环注册添加即可。
🔎 同步器
介绍
我们在使用工具时,可能会遇到在视图A调整缩放或调整窗宽窗位时,在视图B中也要同步响应的需求。这时就需要用到了同步器工具。同步器接受一个源视图、一个目标视图,监听的事件及事件发生在源视图后执行的回调事件。我们使用同步器的目的就是要在多个视图之间进行交互同步,Cornerstone支持自定义同步器,同时也内置了一些常用同步器,本文主要介绍一下内置同步器的使用。
内置同步器
-
createCameraPositionSynchronizer:相机同步,即两个视图的camera参数同步
-
createVOISynchronizer:窗宽窗位同步,在使用
windowLevel
工具调整后作为于所有同步视图 -
createZoomPanSynchronizer: 缩放平移同步,使用
ZoomTool/PanTool
工具后作用于所有同步视图 -
createImageSliceSynchronizer: 滚动同步,注意!!!:只能是同三维方向的视图滚动,例如同方向的Stack视图和Volume视图
使用流程
注册同步器
在cornerstoneTool中内置了以上4种功能的同步器,我们可以直接引入后创建使用
// 第一步:从同步器对象中声明已定义的同步器
const {
createCameraPositionSynchronizer,
createVOISynchronizer,
createZoomPanSynchronizer,
createImageSliceSynchronizer,
} = synchronizers;
// 第二步:创建需要的同步器实例
createCameraPositionSynchronizer(cameraSynchronizerId);
createVOISynchronizer(voiSynchronizerId, {
syncInvertState: false,
});
createZoomPanSynchronizer(zoomSynchronizerId);
createImageSliceSynchronizer(imageSliceSynchronizerId);
添加视图
在创建完同步器后,只需要为同步器添加视图即可实现同步
add()
方法表示将当前视图既添加到源视图数组中也添加到目标视图数组中,表现为:当前视图为源视图时(在当前视图进行操作),其他目标视图响应当前视图的操作结果。当前视图为目标视图时(在其他视图上操作时),当前视图相应其他视图的操作结果。作用等同于同时执行addSource
和addTarget
const synchronizer = SynchronizerManager.getSynchronizer(cameraSynchronizerId);
synchronizer.add({
renderingEngineId,
viewportId: viewportId,
});
- 如果只是想添加视图为源视图(不响应其他视图的交互操作)
synchronizer.addSource({
renderingEngineId,
viewportId: viewportId,
});
- 只添加视图为目标视图(不在当前视图操作)
synchronizer.addTarget({
renderingEngineId,
viewportId: viewportId,
});
切换视图
如果想要添加同步视图,使用 add()
函数,但是如果想要删除同步视图,相同参数下使用 remove()
函数
if (toggle) {
synchronizer.add({
renderingEngineId,
viewportId: event.target.value,
});
} else {
synchronizer.remove({
renderingEngineId,
viewportId: event.target.value,
});
}
🎉 结束语
至此,我们就介绍完了关于交互工具和同步器的一些基础使用。通过介绍工具的种类、状态和如何去使用一个工具、如何去使用一个同步器等相关内容,完成了上述Demo中的全部功能点的实现。涵盖了日常需求中影像缩放、移动等交互操作、多视图同步等需求场景。文中涉及到的代码已全部更新至 github,欢迎交流讨论 👏👏