前言
由於工作接觸影像項目,用到了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,作為 originViewport
在 eventData
中傳過來
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
})
}
最后
同步化這塊兒把大體設計了解了后就比較容易了,的確對很多功能的實現很有幫助,比如上文的聯動,還有掃描定位線、序列圖像同步滾動等等場景。還有些沒用過的屬性、方法,用到的時候只能多讀讀源碼了- -