基于cornerstone3D的dicom影像浏览器 第二十五章 自定义VR调窗工具

发布于:2025-05-29 ⋅ 阅读:(23) ⋅ 点赞:(0)


前言

从cornerstoneTools BaseTool派生VolumeShiftColorTool,实现鼠标键按下并移动时,对3D窗口的preset进行偏移,达到三维调窗的目的。
演示视频中绑定鼠标右键进行调窗,和其他工具一样,也可以绑定左键,中键。
效果如下:
在这里插入图片描述


一、三维调窗原理

观察cornerstonejs中 viewport preset 数据结构:
源码位置:packages\core\src\constants\viewportPresets.ts

const presets: ViewportPreset[] = [
{
    name: 'CT-AAA',
    gradientOpacity: '4 0 1 255 1',
    specularPower: '10',
    scalarOpacity:
      '12 -3024 0 143.556 0 166.222 0.686275 214.389 0.696078 419.736 0.833333 3071 0.803922',
    specular: '0.2',
    shade: '1',
    ambient: '0.1',
    colorTransfer:
      '24 -3024 0 0 0 143.556 0.615686 0.356863 0.184314 166.222 0.882353 0.603922 0.290196 214.389 1 1 1 419.736 1 0.937033 0.954531 3071 0.827451 0.658824 1',
    diffuse: '0.9',
    interpolation: '1',
  },
  ...
]

我们关心两个数据

  1. scalarOpacity:图像灰度值映射不透明度
    值为字符串,以空格为分隔符:
    第一个数据(12)表示有效映射数据个数,即第二个数据(-3024)到最后一个数据(0.803922)的个数。从第二个数据~最后一个数据,每两个数据为一组映射,如第一组[-3024 0],表示像素值为-3024时对应的不透明度为0。第一个数据为12,计算可知有6组映射。
  2. colorTransfer:图像灰度值映射RGB值
    值为字符串,以空格为分隔符:
    第一个数据(24)表示有效映射数据个数,即第二个数据(-3024)到最后一个数据(1)的个数。从第二个数据~最后一个数据,每四个数据为一组映射,如第一组:[-3024 0 0 0],表示像素值为-3024时对应的RGB值 为(0,0,0)。此处为归一化的RGB值,乘255可得RGB值。第一个数据为24,计算可知有6组映射。

实现三维调窗的方法:在鼠标按下并移动时,修改preset中scalarOpacity和colorTransfer中每组映射中的第一个值,即图像灰度值,再把修改后的preset值设置到vieport即可实现。

_shiftVRColor(viewport, preset, pos) {
		const volumeActor = viewport.getDefaultActor().actor;
		// 字符串以空格为分割符转为数值数组
		const color = preset.colorTransfer.split(" ").map(Number);
		// 偏移数据
		for (let i = 1; i < color.length; i += 4) {
			color[i] = color[i] + pos;
		}

		// 字符串以空格为分割符转为数值数组
		const opacity = preset.scalarOpacity.split(" ").map(Number);
		// 偏移数据
		for (let i = 1; i < opacity.length; i += 2) {
			opacity[i] = opacity[i] + pos;
		}
		
		// 生成新的preset
		const newPreset = { ...preset };
		newPreset.colorTransfer = color.join(" ");
		newPreset.scalarOpacity = opacity.join(" ");

		// 记录偏移
		preset.shiftPos = pos;

		// 应用新的preset
		csUtils.applyPreset(volumeActor, newPreset);
		viewport.render();
	}

二、自定义三维调窗工具

新建VolumeShiftColorTool.js
参考WindowLevelTool,从BaseTool派生,重写mouseDragCallback。在mouseDragCallback中调用上一节的_shiftVRColor函数
源码位置:packages\tools\src\tools\WindowLevelTool.ts

import {
	getEnabledElement,
	utilities as csUtils,
	VolumeViewport3D
} from "@cornerstonejs/core";
import * as cornerstoneTools from "@cornerstonejs/tools";
const { BaseTool } = cornerstoneTools;

export default class VolumeShiftColorTool extends BaseTool {
	static toolName;
	constructor(
		toolProps = {},
		defaultToolProps = {
			supportedInteractionTypes: ["Mouse", "Touch"]
		}
	) {
		super(toolProps, defaultToolProps);
	}

	touchDragCallback(evt) {
		this.mouseDragCallback(evt);
	}

	mouseDragCallback(evt) {
		const { element, deltaPoints } = evt.detail;
		const enabledElement = getEnabledElement(element);
		const { viewport } = enabledElement;
		
		if (viewport instanceof VolumeViewport3D) {
			const { preset } = viewport.getProperties();
			let shiftPos = preset.shiftPos || 0;
			// 鼠标上下移动
			const yDelta = deltaPoints.canvas[1];
			shiftPos += yDelta;

			this._shiftVRColor(viewport, preset, shiftPos);
		}
	}

	_shiftVRColor(viewport, preset, pos) {
		const volumeActor = viewport.getDefaultActor().actor;
		const color = preset.colorTransfer.split(" ").map(Number);
		for (let i = 1; i < color.length; i += 4) {
			color[i] = color[i] + pos;
		}
		
		const opacity = preset.scalarOpacity.split(" ").map(Number);
		for (let i = 1; i < opacity.length; i += 2) {
			opacity[i] = opacity[i] + pos;
		}

		const newPreset = { ...preset };
		newPreset.colorTransfer = color.join(" ");
		newPreset.scalarOpacity = opacity.join(" ");

		preset.shiftPos = pos;

		csUtils.applyPreset(volumeActor, newPreset);
		viewport.render();
	}
}

VolumeShiftColorTool.toolName = "VolumeShiftColor";

三、调用流程

1. 修改mprvr.js

  • 导入、添加VolumeShiftColorTool
  • 添加函数enableVolumeShiftColor,切换ZoomTool和VolumeShiftColorTool绑定鼠标右键
import VolumeShiftColorTool from "./VolumeShiftColorTool";

export default class MPR {
	constructor(params) {
		this.toolGroup = null;
		this.vrToolGroup = null;
		this.renderingEngine = null;
		this.registered = false;
		...
		this.init(params);
	}

	init(config = {}) {
	 	const { elAxial, elSagittal, elCoronal, elVR } = config;

		cornerstoneTools.addTool(CrosshairsTool);
		...
		this.vrToolGroup = ToolGroupManager.getToolGroup(vrToolGroupId);
		cornerstoneTools.addTool(VolumeShiftColorTool);
		if (!this.vrToolGroup) {
			...
			this.vrToolGroup.addTool(VolumeShiftColorTool.toolName);
		}
	}
	enableVolumeShiftColor(enable) {
		if (enable) {
			this.vrToolGroup.setToolDisabled(ZoomTool.toolName);
			this.vrToolGroup.setToolActive(VolumeShiftColorTool.toolName, {
				bindings: [
					{
						mouseButton: MouseBindings.Secondary // Right Click
					}
				]
			});
		} else {
			this.vrToolGroup.setToolDisabled(VolumeShiftColorTool.toolName);
			this.vrToolGroup.setToolActive(ZoomTool.toolName, {
				bindings: [
					{
						mouseButton: MouseBindings.Secondary // Right Click
					}
				]
			});
		}
	}
}

2. 修改DispalyerArea3D.vue

const enableVRShiftColor = enable => {
	theMPR.enableVolumeShiftColor(enable);
};

defineExpose({
	...
	enableVRShiftColor
});

3. view3d.vue

响应工具栏enableVRShift事件

async function OnToolbarAction(action) {
	switch (action.name) {
		...
		case "enableVRShift":
			displayArea.value.enableVRShiftColor(action.value);
			break;
		default:
			break;
	}
}

4. Toolbar3D.vue

添加“VR调窗” el-checkbox, 绑定变量enableVRShift

const enableVRShift = ref(false);
watch(enableVRShift, (newValue) => {
	emit("action", { name: "enableVRShift", value: newValue });
});

<template>
	<div class="toolbar">
		...
		
		<div class="toolbar-row">
			<el-checkbox v-model="enableVRShift" label="VR调窗" size="large" />
		</div>
       ...

	</div>
</template>

总结

  1. 讲解三维调窗原理
  2. 自定义工具流程

网站公告

今日签到

点亮在社区的每一天
去签到