cornerstone系列1 - 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
    })
}

最后

同步化這塊兒把大體設計了解了后就比較容易了,的確對很多功能的實現很有幫助,比如上文的聯動,還有掃描定位線、序列圖像同步滾動等等場景。還有些沒用過的屬性、方法,用到的時候只能多讀讀源碼了- -


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM