cornerstone系列 - cornerstoneTools的synchronize(同步化)

前言

由于工作接触影像项目,用到了cornerstone及其相关的库,网上感觉资料也挺少,于是用到啥记录啥吧。开始第一篇,cornerstoneTools的Synchronizer相关。

在Example页面中( https://tools.cornerstonejs.org/examples )可以看到有Synchronization这一列,就是同步的例子,比如扫描定位线、不同的图像同步修改调窗等。总之,运用的场景就是需要状态联动的地方。

关于Synchronizer

cornerstoneTools提供了一个同步化的功能,文档里找到相关内容,可以从Synchronizer源码看它的使用方法。

首先export一个Synchronizer的类,参数是event和handler。event是注册的事件名(可以是多个用空格分开),当事件触发时,该synchronization就会触发。handler是synchronization触发时,target element要执行的方法。

sourceElements和targetElements是Synchronizer的两个重要属性,分别存放了同步的源和目标对象,上面说到event触发,即是触发了源对象(sourceElements)上注册的事件,触发后目标对象(targetElements)会执行handler。

部分实例上的方法:

addSource :参数为element,添加到sourceElements,给该element注册事件(Synchronizer的event)
addTarget :参数为element,添加到targetElements
add :参数为element,调用了addSource和addTarget(remove同理
getSourceElements :获取源对象集合
getTargetElements :获取目标对象集合
setViewport :调用了cornerstone的setViewport

/**
 * Synchronize target and source elements when an event fires on the source element
 * @export @public constructor
 * @name Synchronizer
 *
 * @param {String} event - The event(s) that will trigger synchronization. Separate multiple events by a space
 * @param {Function} handler - The function that will make the necessary changes to the target element in order to synchronize it with the source element
 */
function Synchronizer(event, handler) {
  ...
  const sourceElements = [];
  const targetElements = [];
  ...
  this.addSource = function(element){...}
  this.addTarget = function(element){...}
  this.add = function(element){...}
  this.remove = function(element){...}
  ...
}

实际场景

我这边的场景是,要实现多个影像序列viewport的相对同步(如序列1调窗windowWidth和windowCenter各增加了100,那同步的序列2也要这两个属性在原本的基础上各加100酱紫,有点废话- -),那先想下大概要做的事:

  • 自定义一个相对同步事件
  • 实例化Synchronizer,添加好source和target
  • 新建一个handler,用来处理target对象(也是Synchronizer的参数)
  • 在操作的时候触发自定义事件
  • ...
1.自定义事件
// cornerstone-core-plus.js
export const EVENTS = {
    RELATIVE_SYNC: 'cornerstonerelativesync'
}

从设想中可以感觉到,要统一控制所有的改变,肯定要对原本cornerstone的setViewport做手脚,所以这个文件就写一些对cornerstone-core使用的补充。

2.Synchronizer实例化
export const linkSynchronizer = new cornerstoneTools.Synchronizer(
    EVENTS.RELATIVE_SYNC,
    cornerstoneTools.linkSynchronizer
);

// 某view层面
linkSynchronizer.add(element);

第一个参数就是上面定义的事件,第二个参数是handler,由于我这边在cornerstoneTools上做了很多扩展,所以直接加到cornerstoneTools上了。在开启同步的时候调用add方法,把elemen同时t加到source和target上(因为是同步功能,所以随便哪个都可以当源)

3.新建handler

从Synchronizer源码中可以看到handler在调用时传入的参数,实例本身、触发的源对象、当前目标对象、事件传过来的详情内容。这边主要要靠 eventData 来传递操作详情。

可见eventData 的源头是 onEvent 方法中的参数,即 addSource 时注册的事件。cornerstone这儿事件的触发调用 cornerstone.triggerEvent(el, type, detail) 方法,detail就是最终传过来的e.detail,这样就明了了,只要trigger的时候带上需要的数据即可。

// cornerstoneTools源码 - Synchronize.js片段
eventHandler(
        that,
        sourceElement,
        targetElement,
        eventData,
        positionDifference
)
...
element.addEventListener(oneEvent, onEvent);
...
function onEvent(e) {
    const eventData = e.detail;
    if (ignoreFiredEvents === true) {
      return;
    }
    fireEvent(e.currentTarget, eventData);
}

下面就开始写handler,需求是所有的变化都是相对变化,所以在项目中其实有几种情况:

1.比如鼠标左键的拖动改变调窗、缩放、移动这种操作,需要计算相对位移,所以监听鼠标事件,记录mousedown时的viewport,作为 originViewporteventData 中传过来

2.比如固定窗高窗位的设置,就不需要 originViewport,只要直接把 targetElement 的对应属性设置成sourceElement viewport的属性值就行了

3.比如顺时针旋转这种操作,需要知道旋转的度数,所以传入 changeData

activeTool 是为了区别当前做的是什么操作

// 即cornerstoneTools.linkSynchronizer
import cornerstoneTools from 'cornerstone-tools';
const { external } = cornerstoneTools;
export default function (synchronizer, sourceElement, targetElement, eventData) {
    // 防止死循环
    if (targetElement === sourceElement) {
        return;
    }
    const { originViewport, activeTool, changeData } = eventData;
    if (!activeTool) {
        return
    }
    const cornerstone = external.cornerstone;
    const sourceViewport = cornerstone.getViewport(sourceElement);
    const targetViewport = cornerstone.getViewport(targetElement);
    // 拿缩放举个例子
    switch (activeTool) {
        case 'Zoom':
            return handleZoom();
        ...
    }
    function handleZoom() {
        const scaleTarget = targetViewport.scale;
        const scaleSource = sourceViewport.scale;
        if (originViewport) {
            const scaleOrigin = originViewport.scale;
            if (scaleSource === scaleOrigin) {
                return
            }
            targetViewport.scale = scaleTarget + (scaleSource - scaleOrigin);
        } else {
            if (scaleTarget === scaleSource) {
                return
            }
            targetViewport.scale = scaleSource;
        }
        synchronizer.setViewport(targetElement, targetViewport);
    }
}
4.事件的触发

上面也提到了,就是在操作的时候调用 cornerstone.triggerEvent(el, type, detail) 方法,按设计调用的地方有两个。

1.鼠标操作处(3中的情况1),当mouseup的时候触发,传过去当前的tool和mousedown时存下的originViewport。

2.全部setViewport的地方(准确说是所有使得当前的source的图像改变的地方),由于原本修改viewport的地方都是调用的setViewport,所以要对这个方法扩展一下,重新定义一个 setViewportWithEvent 方法代替原本调用setViewport的地方。

export function setViewportWithEvent(element, viewport, activeTool, changeData) {
    cornerstone.setViewport(element, viewport);
    cornerstone.triggerEvent(element, EVENTS.RELATIVE_SYNC, {
        activeTool,
        changeData
    })
}

最后

同步化这块儿把大体设计了解了后就比较容易了,的确对很多功能的实现很有帮助,比如上文的联动,还有扫描定位线、序列图像同步滚动等等场景。还有些没用过的属性、方法,用到的时候只能多读读源码了- -

你可能感兴趣的:(cornerstone系列 - cornerstoneTools的synchronize(同步化))