CornerstoneTools那些你会遇到的问题(一)
前言
暑假被导师叫去公司干活,咱们实验室做的是医院信息化,这次来公司我主要负责PACS平台的优化,我们原有的PACS平台比较老,而且只有简单的看片的功能,没有标注功能。这次的需求就是添加测量标注功能。
既然咱们是PACS平台,那自然离不开Cornerstone.js这个当前可以说是B/S架构下使用的最多的医学影像处理工具库。但是问题在于这个库早已经不维护了,而且这个库的文档也非常潦草,并没有深入讲解,对深度使用这个库的开发者制造了不少困难。官方示例也只是对工具的最基础的使用,交互上的使用是一点都没哈。
Cornerstone官方团队开发了一款基于Cornerstone.js的PACS平台的最佳实践OHIF很多同学在这个平台上看到的关于标注的功能都不知道怎么实现,例如在画布中鼠标悬浮激活测量标注、点击测量的回调函数等等。我一开始也是一头雾水,找不到文档,于是我就只去阅读源码寻找答案。下面记录一些大家可能会遇到的问题,并附上我的实现方案。
如果同学有疑问,欢迎交流。
一、基本使用
为了方便,我以长度测量工具为例,其他的工具都是类似的。这里默认大家已经渲染了当前的image。
// Add our tool, and set it's mode
const LengthTool = cornerstoneTools.LengthTool;
// 将工具添加到全局
cornerstoneTools.addTool(LengthTool)
// 激活工具
// 配置 mouseButtonMask:1: 左键; 2: 滚轮点击; 3:右键
cornerstoneTools.setToolActive('Length', { mouseButtonMask: 1 })
这里的addTool推荐初始化一步到位。
let tools = [
{
name: "Length",
label: "长度",
props: {
configuration: {
drawHandlesOnHover: true, //是否显示圆点
hideHandlesIfMoving: false, //是否显示圆点移动轨迹
renderDashed: false, //是否虚线渲染
}
}
},
]
toolInit() {
this.tools.forEach((tool) => {
this.addTool(tool.name, tool.props);
});
},
addTool(toolName, props) {
const cornerstoneTools = this.$cornerstoneTools
const ApiTool = cornerstoneTools[`${toolName}Tool`];
cornerstoneTools.addTool(ApiTool, props);
},
二、关于测量操作的回调函数
这里先贴一部分关于测量操作事件枚举的源码
//
// CUSTOM
//
/**
* @type {String}
* 新增测量时会触发的事件(e.g. Length测量工具刚在画布上画的时候就会触发)
*/
MEASUREMENT_ADDED: 'cornerstonetoolsmeasurementadded',
/**
* @type {String}
* 对测量记录进行修改时触发
*/
MEASUREMENT_MODIFIED: 'cornerstonetoolsmeasurementmodified',
/**
* @type {String}
* 完成测量操作时触发
*/
MEASUREMENT_COMPLETED: 'cornerstonetoolsmeasurementcompleted',
/**
* @type {String}
* 移除测量记录时触发
*/
MEASUREMENT_REMOVED: 'cornerstonetoolsmeasurementremoved',
如何使用?
const completedEvtType = this.$cornerstoneTools.EVENTS.MEASUREMENT_COMPLETED;
element.addEventListener(completedEvtType, function (evt) {
const eventData = evt.detail;
_this.handleMeasurementComplete(eventData)
});
三、鼠标悬浮匹配测量标注
这个文档中没有提示,源码注释也没写清楚。。
老规矩,还是先贴上cornerstoneTools的源码部分:
/**
*
*
* @param {*} element
* @param {*} data
* @param {*} coords
* @returns {Boolean}
*/
pointNearTool(element, data, coords) {
const hasStartAndEndHandles =
data && data.handles && data.handles.start && data.handles.end;
const validParameters = hasStartAndEndHandles;
if (!validParameters) {
logger.warn(
`invalid parameters supplied to tool ${this.name}'s pointNearTool`
);
return false;
}
if (data.visible === false) {
return false;
}
return (
lineSegDistance(element, data.handles.start, data.handles.end, coords) <
25
);
}
这个方法是在每个具体测量工具类里面的,对不同的测量工具,因为作的图不同,他都提供了不同的逻辑,但是还是暴露了统一的接口pointNearTool(element, data, coords)
,这里的element就是你当前激活的element,data是在toolState里的测量数据对象,coords是你当前的鼠标的坐标。
注意避坑:这个坐标要用鼠标在canvas的坐标,而不是鼠标在image的坐标。
因为cornerstone内部已经做了转换,会自动把canvas坐标转为image坐标,不需要你提前转换。
实践方法:
Object.keys(currentToolState).forEach((key) => {
currentToolState[key].data?.forEach((ele, i) => {
const flag = cornerstoneTools[`${key}Tool`].prototype.pointNearTool(element, ele, currentPoint)
// 返回一个布尔值
if ( flag ) {
// flag为真则表明鼠标坐标临近这个测量标注,反之则没有临近
}
})
})
这里可能有同学有疑问了,我本来就是要去匹配测量数据的,那我如何得到那个测量数据对象作为pointNearTool
的参数呢?其实,cornerstoneTools对关于测量标注的增删操作全部进行了统一管理,都在toolStateManager中,你首先要在toolStateManager中获取工具状态,toolState记录了所有测量的数据对象,我这里是遍历了测量数据对象进行匹配坐标的。通过不变的coords和变化的data,来得出flag。
四、如何删除测量标注
这里就是需要通过我们上文提到的toolStateManager
来进行统一化管理。
你可以通过cornerstoneTools.getToolState(element, toolName)
来获取当前激活的element的toolState来进行操作,也可以通过cornerstoneTools.globalImageIdSpecificToolStateManager
来获取全局的标注状态管理器来进行操作。
实践方法:
const globalToolState = cornerstoneTools.globalImageIdSpecificToolStateManager
const currentToolState = globalToolState.toolState[currentImageId]
Object.keys(currentToolState).forEach((key) => {
currentToolState[key].data.forEach((ele, i) => {
const flag = cornerstoneTools[`${key}Tool`].prototype.pointNearTool(element, ele, currentPoint)
if ( flag ) {
this.updateMeasurementsList(currentToolState[key].data[i].uuid)
// 删除当前匹配到的测量标注
currentToolState[key].data.splice(i, 1)
// 别忘了更新一下image
cornerstone.updateImage(element);
}
})
})
暂时先写一部分,后续我有时间会继续更新。 如果你觉得有用,点个赞不过分吧,欢迎大家交流哈 ^_^ 如果有错误欢迎指出!!
转载自:https://juejin.cn/post/7260034286278524984