CornerstoneTools那些你会遇到的问题(一)

前言

暑假被导师叫去公司干活,咱们实验室做的是医院信息化,这次来公司我主要负责PACS平台的优化,我们原有的PACS平台比较老,而且只有简单的看片的功能,没有标注功能。这次的需求就是添加测量标注功能。

既然咱们是PACS平台,那自然离不开Cornerstone.js这个当前可以说是B/S架构下使用的最多的医学影像处理工具库。但是问题在于这个库早已经不维护了,而且这个库的文档也非常潦草,并没有深入讲解,对深度使用这个库的开发者制造了不少困难。官方示例也只是对工具的最基础的使用,交互上的使用是一点都没哈。

Cornerstone官方团队开发了一款基于Cornerstone.js的PACS平台的最佳实践OHIF很多同学在这个平台上看到的关于标注的功能都不知道怎么实现,例如在画布中鼠标悬浮激活测量标注、点击测量的回调函数等等。我一开始也是一头雾水,找不到文档,于是我就只去阅读源码寻找答案。下面记录一些大家可能会遇到的问题,并附上我的实现方案。

如果同学有疑问,欢迎交流。

一、基本使用

为了方便,我以长度测量工具为例,其他的工具都是类似的。这里默认大家已经渲染了当前的image。

js 复制代码
// 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推荐初始化一步到位。

js 复制代码
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);
},

二、关于测量操作的回调函数

这里先贴一部分关于测量操作事件枚举的源码

js 复制代码
//
// CUSTOM
//

/**
*  @type {String}
*  新增测量时会触发的事件(e.g. Length测量工具刚在画布上画的时候就会触发)
*/
MEASUREMENT_ADDED: 'cornerstonetoolsmeasurementadded',

/**
*  @type {String}
*  对测量记录进行修改时触发
*/
MEASUREMENT_MODIFIED: 'cornerstonetoolsmeasurementmodified',

/**
*  @type {String}
*  完成测量操作时触发
*/
MEASUREMENT_COMPLETED: 'cornerstonetoolsmeasurementcompleted',

/**
*  @type {String}
*  移除测量记录时触发
*/
MEASUREMENT_REMOVED: 'cornerstonetoolsmeasurementremoved',

如何使用?

js 复制代码
const completedEvtType = this.$cornerstoneTools.EVENTS.MEASUREMENT_COMPLETED;
element.addEventListener(completedEvtType, function (evt) {
  const eventData = evt.detail; 
   _this.handleMeasurementComplete(eventData)
});

三、鼠标悬浮匹配测量标注

这个文档中没有提示,源码注释也没写清楚。。

老规矩,还是先贴上cornerstoneTools的源码部分:

js 复制代码
/**
   *
   *
   * @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坐标,不需要你提前转换。

实践方法:

js 复制代码
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来获取全局的标注状态管理器来进行操作。

实践方法:

js 复制代码
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);
      }
    })
})  

暂时先写一部分,后续我有时间会继续更新。 如果你觉得有用,点个赞不过分吧,欢迎大家交流哈 ^_^ 如果有错误欢迎指出!!

相关推荐
苦瓜小生20 分钟前
【前端】|【js手撕】经典高频面试题:手写实现function.call、apply、bind
java·前端·javascript
和沐阳学逆向43 分钟前
我现在怎么用 CC Switch 管中转站,顺手拿 Codex 举个例子
开发语言·javascript·ecmascript
kgduu3 小时前
js之客户端存储
javascript·数据库·oracle
四千岁3 小时前
2026 最新版:WSL + Ubuntu 全栈开发环境,一篇搞定!
javascript·node.js
竹林8183 小时前
从“连接失败”到丝滑登录:我用 ethers.js 连接 MetaMask 的完整踩坑实录
前端·javascript
铭毅天下4 小时前
EasySearch Rules 规则语法速查手册
开发语言·前端·javascript·ecmascript
bjzhang754 小时前
使用 HTML + JavaScript 实现 SQL 智能补全功能
javascript·html·sql智能补全
全栈前端老曹4 小时前
【前端地图】地图开发基础概念——地图服务类型(矢量图、卫星图、地形图)、WGS84 / GCJ-02 / BD09 坐标系、地图 SDK 简介
前端·javascript·地图·wgs84·gcj-02·bd09·地图sdk
Fairy要carry5 小时前
项目01-手搓Agent之loop
前端·javascript·python
kyriewen5 小时前
DOM树与节点操作:用JS给网页“动手术”
前端·javascript·面试