likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 17

前言

暑假被导师叫去公司干活,咱们实验室做的是医院信息化,这次来公司我主要负责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
评论
请登录