🏄♀️ 前言
这里是关于Cornerstone3D Tools对影像进行交互(中篇 )- 注释类工具的使用介绍。在 Cornerstone3D Tools对影像进行交互(上篇)-基础交互工具及同步器 一文中主要介绍了一下基础交互类工具的使用,感兴趣的小伙伴可以去回顾一下呦。接下来主要介绍的就是注释类的工具,当我们拿到一个影像时,总是会有各种各种需要对影像进行注释标注类操作的需求,比如测量下长短、计算下Mean和面积、做个文字标注、或者圈定一下边缘等等。不过不用着急,上述说的这些操作Cornerstone3D Tools中基本都已涵盖,本文从支持的注释类工具种类及使用场景、编程式操作注释两大方面展开介绍。
🚀 效果演示
依旧是先来看一下案例演示效果,点击查看完整代码
- 注释工具交互效果演示
- 注释类相关API交互效果演示
🏝 工具介绍
对于目前Cornerstone3D Tools中提供的注释类工具,将它们按照功能分为了3大类:基础测量类工具、轮廓&自由绘制类工具及特定场景类绘制工具,分别对应了影像的基础注释功能、自由绘制和轮廓绘制功能及特定场景下的绘制功能。单就代码层面来说,激活使用不同的工具的实现方式基本一致。
本小节主要是对目前版本(~1.70)已支持的工具进行一个罗列介绍,目的在于当遇到某些场景时能够了解到当前版本是否具有相关的交互工具,不感兴趣的小伙伴可以直接跳过或者只针对某一寻找的工具具体查看。
基础测量工具
简单介绍
-
LengthTool(长度测量工具): 最基础的测量工具,在一个切片上计算两点之间的距离,既可以用于volume视图也可以用于stack视图
-
ProbeTool(探针工具):获取影像上某一点的位置和CT值
-
RectangleROITool(长方形ROI测量):方形测量工具,可以计算当前区域的面积、平均值、最大最小值,也可以根据业务需求由圈定的范围自行计算某些数据。
-
EllipticalROITool(椭圆形ROI测量):椭圆形测量工具,同样可以计算区域面积、平均值、最大最小值等
-
CircleROITool(圆形ROI测量):圆形测量工具
-
ArrowAnnotateTool(箭头标注):箭头指示工具,📣 📣 📣 特别说明:很多小伙伴问目前有没有文字标注工具,目前没有单独一个用于文本标注的工具,但是可以使用箭头指示工具替代,在绘制箭头指向需要标注的位置后可以自定义输入标注的文本内容。
-
AngleTool(通用角度测量):测量两条线之间的角度
使用工具
如何在项目中激活使用工具的完整代码可以参考 windowLevel 中的完整代码
JavaScript
// 注册工具
cornerstoneTools.addTool(ProbeTool)
const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
toolGroup.addTool(ProbeTool.toolName)
toolGroup.addViewport('viewportId', 'renderingEngineId')
// 激活工具
toolGroup.setToolActive(ProbeTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary, // Left Click
},
],
})
工具演示
轮廓&自由绘制工具
轮廓绘制工具与基础测量工具不同,针对不同的场景需要灵活选择对应的工具
动态轮廓测量工具(LivewireContourTool
)
-
用于进行半自动化轮廓绘制。工具可以通过用户提供的种子点来进行动态计算影像中的最优路径,从而生成相对比较精确的轮廓线。
-
适用场景:高精度的区域识别识别标注任务,例如病灶的边界识别任务
自由曲线测量工具(PlanarFreehandROITool
)
-
自由手绘ROI标注工具,允许用户在图像上自由勾勒出不规则形状的区域轮廓,可以计算选中长度及面积,支持二次编辑调整。
-
适用场景:相对比较复杂且不规则的区域标注及分割、自由工具无法精准捕获边界需要手动标记时
样条曲线绘制工具(SplineROITool
)
-
创建平滑曲线形式的ROI绘制工具,支持用户通过一系列控制点生成光滑的曲线轮廓,可以更好的捕获到复杂的结构轮廓。
-
适用场景:适用于血管、神经等弯曲边界结构的标注;高平滑度的标注任务等
-
样条种类:
-
线性样条(Linear Spline):在控制点之间直接连接直线段,不进行平滑处理。适合简单直线标注,快速但不平滑
-
张量样条(Cardinal Spline):一种平滑曲线,适度紧贴控制点,并在控制点之间生成柔和过渡的曲线。平滑度适中,适合适度弯曲的区域标注
-
卡特姆-罗姆样条(Catmull-Rom Spline):一种特殊的 Cardinal 样条,具有自动设置的张力值。它生成的曲线会严格穿过所有控制点,使曲线平滑贴合于每个控制点。严格通过控制点,适合精确标注,不适合过度弯曲。
-
B样条(Cubic B-Spline):一种高阶样条曲线,不必严格穿过控制点,而是生成的平滑曲线会跟随控制点的位置。与 Catmull-Rom 样条不同,B-Spline 曲线控制点对曲线有影响,但不会强制曲线通过控制点。平滑度最高,不严格贴合控制点,适合自然曲线标注。
-
-
使用样条工具
📣 跟直接全局添加及工具组添加工具不同,不同种类的样条工具是以工具实例的方式添加到工具管理器中
JavaScripttoolGroup.addToolInstance(instanceToolName, SplineROITool.toolName, { spline: { type: CARDINAL, configuration: { [CARDINAL]: { scale: DEFAULT_CARDINAL_SCALE, }, }, }, })
特定场景绘制工具
脊柱侧弯角度测量工具(CobbAngleTool)
-
专门用于评估脊柱的弯曲角度(Cobb角)
-
适用场景:主要用于脊柱侧弯的诊断和评估,可以帮助医生确认脊柱侧弯的严重程度
超声波束的方向测量工具(UltrasoundDirectionalTool)
- 专门为超声影像设计的一种测量工具,用于在超声图像中标注方向信息,帮助医护人员更好地理解和分析超声成像中的位置和方向
🚢 操作API演示
终于啰里啰嗦的完成了这一大堆工具的简单介绍(这个过程简直比直接写演示Demo还要痛苦🥱),接下来终于就是代码环节了,我们可以通过激活工具的方式绘制注释,但是往往又需要对绘制的工具进行一些编程式操作,接下来介绍一些编程式交互API,主要为数据管理、选中状态管理、锁定状态管理、显示隐藏状态管理、代码添加删除注释等等方面。
JavaScript
// tools 中 的注释管理对象
import { annotation } from "@cornerstonejs/tools";
数据管理
数据管理相关的操作主要基于
annotation
对象的state
属性,在state
中可以对当前注释进行查看、新增、删除等等操作
- 获取当前工具在某个视图中的所有注释
JavaScript
annotation.state.getAnnotations(toolName, annotationGroupSelector)
- 获取当前工具的注释数量
JavaScript
annotation.state.getNumberOfAnnotations(toolName, annotationGroupSelector),
- 获取所有工具的全部注释
JavaScript
annotation.state.getAllAnnotations()
- 删除所有工具的全部注释
JavaScript
annotation.state.removeAllAnnotations()
- 获取指定注释
JavaScript
annotation.state.getAnnotation(uid)
- 删除指定注释
JavaScript
annotation.state.removeAnnotation(uid)
选中状态管理
是否选中状态管理的操作主要基于
annotation
对象的selection
属性,想要选中某个注释一个方式是通过鼠标直接点击选中,另外一个便是使用编程式的方式进行选中。同一时间有且仅有一个注释处于选中状态。
- 获取所有选中的注释
JavaScript
annotation.selection.getAnnotationsSelected()
- 获取某个工具下的注释
JavaScript
annotation.selection.getAnnotationsSelectedByToolName(toolName)
- 获取选中的个数
JavaScript
annotation.selection.getAnnotationsSelectedCount()
- 设置某个注释为选中
JavaScript
annotation.selection.setAnnotationSelected(uid);
- 判断某个注释是否是选中状态
JavaScript
annotation.selection.isAnnotationSelected(uid)
- 取消第一个注释的选中状态
JavaScript
annotation.selection.deselectAnnotation(uid);
锁定状态管理
是否选中状态管理的操作主要基于
annotation
对象的locking
属性。值得注意的是:注释中的选中状态与锁定状态是在内部管理的,不会在annotation
的对象属性中发生改变。即使是在执行操作成功后,也不能通过state中的isLocked属性对注释进行过滤获取,而是应该通过selection
和locking
属性的对应api获取。
- 获取当前锁定的所有注释
JavaScript
annotation.locking.getAnnotationsLocked()
- 获取当前锁定的个数
JavaScript
annotation.locking.getAnnotationsLockedCount()
- 设置某个注释的锁定状态
JavaScript
annotation.locking.setAnnotationLocked(Annotation, true);
- 取消第一个注释的锁定状态
JavaScript
annotation.locking.setAnnotationLocked(Annotation, false);
- 取消所有注释的锁定状态
JavaScript
nnotation.locking.unlockAllAnnotations();
- 判断第一个注释是否被锁定
JavaScript
annotation.locking.isAnnotationLocked(Annotation)
显示隐藏状态管理
是否选中状态管理的操作主要基于
annotation
对象的visibility
属性。与选中和锁定状态不同,当我们试图使用API的方式控制一个注释的显示与否时,确实会更改annotation
对象的isVisible
属性值。在必要的场景下我们可以通过过滤annotation
的isVisible
属性值来获取到全部隐藏或显示的注释
- 显示所有的注释
JavaScript
annotation.visibility.showAllAnnotations();
- 隐藏第一个注释
JavaScript
annotation.visibility.setAnnotationVisibility(uid, false);
- 显示第一个注释
JavaScript
annotation.visibility.setAnnotationVisibility(uid, true);
- 判断第一个注释的显示状态
JavaScript
annotation.visibility.isAnnotationVisible(uid)特别说明
‼️ ‼️ ‼️ 特别说明
与其他状态的实时性改变不同,我们可以会发现其他API执行后对应的状态会立刻改变,但是现实隐藏相关的API在执行后没有相对应的执行效果,在执行完后我们需要视图重新渲染才可以获取对应的效果
JavaScript
getRenderingEngine(renderingEngine_id).getViewport(volumeId).render();
新增注释
当我们通过算法或者其他方式获取到了一个ROI区域时,可能需要在影像初始化加载完后直接就标注在了影像上,而不是让用户交互标注,这种情况下我们就需要编程式新增注释
- 处理坐标点
编程式新增注释时,最重要的就是注释的坐标点,根据要添加的注释形式来传参对应的世界坐标
JavaScript
// 假设我们需要向以下二维坐标点中添加一个注释,首先我们需要知道当前二维坐标点是基于什么定位的
// canvas定位:位置不会跟着影像的不同而不同,绘制后与像素位置保持一致
// 影像定位:位置与影像强绑定,同一坐标点不同的影像转换成的世界坐标不同,如果是进行ROI标注,一般都是影像定位
const canvasPos = [[0,150], [300,150]];
// 二维坐标转世界坐标 => 当前坐标点的位置是基于canvas定位的
const worldPos = canvasPos.map(point => viewport.canvasToWorld(point))
// 二维坐标转时间坐标 => 当前坐标点的位置是基于影像定位的
const worldPos1 = canvasPos.map(point => csUtils.imageToWorldCoords(viewport.getCurrentImageId(), point))
- 创建一个新注释
注释是按照一定的规则声明的,基本属性如下,针对不同的工具的注释对象,最大的区别点在于point属性值。
JavaScript
const viewport = getRenderingEngine(renderingEngine_id)?.getViewport(volumeId);
const camera = viewport?.getCamera();
const newAnnotation1 = {
annotationUID: csUtils.uuidv4(), // 随机生成的注释id
invalidated: true,
isLocked: false,
isVisible: true,
metadata: {
FrameOfReferenceUID: viewport.getFrameOfReferenceUID,
cameraFocalPoint: camera.focalPoint,
cameraPosition: camera.position,
volumeId: ctVolumeId,
viewPlaneNormal: camera.viewPlaneNormal,
viewUp: camera.viewUp,
referencedImageId: '',
toolName: 'Length',
},
data: {
cachedStats: {},
handles: {
activeHandleIndex: null,
points: points, // 重点:注释需要的世界坐标
textBox: {
hasMoved: false,
worldPosition: [0, 0, 0],
worldBoundingBox: {
topLeft: [0, 0, 0],
topRight: [0, 0, 0],
bottomLeft: [0, 0, 0],
bottomRight: [0, 0, 0]
}
}
},
},
};
- 添加到管理器中
JavaScript
annotationManager.addAnnotation(newAnnotation1,annotationManager.getGroupKey(document.querySelector(`#${volumeDom[0]}`)));
viewport.render(); // 注意:在新增完之后必须再次进行刷新渲染
样式配置
除了对已有的注释状态进行管理外,可能会有要同时显示很多注释,但是注释的含义不一样,需要通过颜色、粗细等外观性的可视化属性去区分,这就涉及到了对注释的样式进行配置。
我们可以通过4个不同的等级去配置注释的样式,配置后作用的范围也有所不同,按以下顺序优先级逐渐提升:
- 全局级别配置:全局性配置,作用于全部工具组及视图
JavaScript
const newStyle = {
global: {
color: 'rgb(0, 100, 0)',
colorSelected: 'rgb(255, 0, 0)',
colorLocked: 'rgb(0, 0, 255)',
}
}
// 用于进行全局配置的API
annotation.config.style.setDefaultToolStyles(newStyle);
- 工具组级别配置:作用于指定工具组及工具组作用的所有视图
JavaScript
const styles = {
Length: {
colorHighlighted: 'rgb(255, 0, 0)',
},
global: {
lineWidth: '2',
},
}
// 对工具组进行配置API
annotation.config.style.setToolGroupToolStyles(toolGroupId, styles);
- 视图级别配置:作用于指定的视图
JavaScript
const styles = {
Length: {
colorHighlighted: 'rgb(255, 0, 255)',
},
global: {
lineWidth: '2',
},
}
// 对视图进行配置API
annotation.config.style.setViewportToolStyles(volumeId, styles);
- 注释级别配置:仅对当前配置的注释有效
JavaScript
const styles = {
colorHighlighted: 'rgb(255, 0, 0)',
};
// 对某个注释配置API
annotation.config.style.setAnnotationStyles(uid, styles);
除了了解如何进行配置外,简单介绍下我们可以对注释的哪些样式进行配置,在上述示例中的colorHighlighted
属性是如何得来的。 一个注释的样式主要由3部分组成 样式的基础属性 + 注释状态 + 注释模式
- 基本属性支持的配置项:
JavaScript
type Properties =
| 'color'
| 'colorAutoGenerated'
| 'lineWidth'
| 'lineWidthAutoGenerated'
| 'lineDash'
| 'textBoxFontFamily'
| 'textBoxFontSize'
| 'textBoxColor'
| 'textBoxBackground'
| 'textBoxLinkLineWidth'
| 'textBoxLinkLineDash';
- 注释状态
JavaScript
type States = '' | 'Highlighted' | 'Selected' | 'Locked' | 'AutoGenerated';
- 注释模式
JavaScript
type Modes = '' | 'Active' | 'Passive' | 'Enabled';
所以上述示例中的 colorHighlighted
表示注释高亮状态下的颜色。
不同状态的表现
最后的最后,简单接上文再演示一下不同状态下注释的行为区别
-
Active:激活状态,很容易理解,就是我们激活一个工具时可以执行的操作,可以新增、移动、选中等等所有操作
-
Passive:被动状态,在注释工具中来说,已存在注释行为与Active状态一致,但是不能通过工具新增注释
-
Enabled:可用状态,仅仅是渲染注释内容,不可以进行移动、选中、更改等操作
-
Disable:禁用状态,禁用当前工具,不显示注释内容
所以我们可以根据日常的业务需求选择合适的注释状态、如果仅仅是对已存在的注释进行操作,则可以设置状态为Passive,如果只是对注释进行展示禁止用户二次编辑,可以设置状态为Enabled。
🌶 结束语
至此,我们的交互工具(中篇)- 注释类工具的介绍就结束啦,文中主要就目前已支持的注释种类、注释管理及注释不同状态的管理等做了详细介绍。大家可以通过运行Demo示例自行感受一下不同工具的展示效果及API执行结果,本文是对Demo代码的一个说明及总结。关于大家比较关注的注释导出、自定义注释工具会在后续扩展篇中单独出一篇,Cornerstone3D学习中, 文中涉及到的代码已全部更新至 github,欢迎评论交流 👏👏👏