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

news2025/6/4 19:38:52

文章目录

  • 前言
  • 一、三维调窗原理
  • 二、自定义三维调窗工具
  • 三、调用流程
    • 1. 修改mprvr.js
    • 2. 修改DispalyerArea3D.vue
    • 3. view3d.vue
    • 4. Toolbar3D.vue
  • 总结


前言

从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. 自定义工具流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2396471.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

经典面试题:一文了解常见的缓存问题

在面试过程中&#xff0c;面试官的桌子上摆放着很多高频的面试题&#xff0c;能否顺利回答决定了你面试通过的概率。其中缓存问题就是其中的一份&#xff0c;可以说掌握缓存问题及解决方法是面试前必须准备的内容。那么缓存有什么典型的问题&#xff0c;出现的原因是什么&#…

GC1267F:单相全波风扇电机预驱动芯片解析

在现代电子设备中&#xff0c;风扇电机的驱动控制是散热系统的关键组成部分。GC1267F 是一款由浙江新麦科技有限公司生产的单相全波风扇电机预驱动芯片&#xff0c;适用于需要大风量和大电流的服务器以及消费类电器风扇电机驱动。 芯片特性 GC1267F 支持外部 PWM 信号的变速功…

Linux --进程状态

目录 进程状态(宏观) Linux进程状态 进程状态的查看 进程状态(宏观) 为了了解Linux的进程状态&#xff0c;首先我们得了解进程状态&#xff0c;因为不仅仅是在Linux下有进程状态&#xff0c;macos和windows下都有进程状态&#xff0c;这里先解释的是一个宏观概念下的&#xff…

智能手机上用Termux安装php+Nginx

Termux的官方网站&#xff1a;Termux | The main termux site and help pages. 以下是在 Termux 上安装和配置 PHP Nginx 的完整流程总结&#xff0c;包含关键步骤和命令&#xff1a; 一、安装依赖 pkg update && pkg upgrade # 更新包列表和系统pkg install nginx p…

Visual Studio 调试中 PDB 与图像不匹配

Visual Studio 调试中 PDB 与图像不匹配 在使用 Visual Studio 进行本地或远程调试时&#xff0c;很多开发者会遇到 PDB 加载失败、符号不匹配的问题&#xff0c;甚至程序进程未退出&#xff0c;导致 .exe 文件无法成功覆盖。本文详细解析了从后台进程清理、构建产物验证、模块…

设计模式——策略设计模式(行为型)

摘要 策略设计模式是一种行为型设计模式&#xff0c;它定义了一系列算法并将每个算法封装起来&#xff0c;使它们可以相互替换。该模式让算法的变化独立于使用算法的客户&#xff0c;从而使得算法可以灵活地切换和扩展。其主要角色包括策略接口、具体策略类和环境类。策略模式…

保持本地 Git 项目副本与远程仓库完全同步

核心目标&#xff1a; 保持本地 Git 项目副本与 GitHub 远程仓库完全同步。 关键方法&#xff1a; 定期执行 git pull 命令。 操作步骤&#xff1a; 进入项目目录&#xff1a; 在终端/命令行中&#xff0c;使用 cd 命令切换到你的项目文件夹。执行拉取命令&#xff1a; 运行…

设计模式——模版方法设计模式(行为型)

摘要 模版方法设计模式是一种行为型设计模式&#xff0c;定义了算法的步骤顺序和整体结构&#xff0c;将某些步骤的具体实现延迟到子类中。它通过抽象类定义模板方法&#xff0c;子类实现抽象步骤&#xff0c;实现代码复用和算法流程控制。该模式适用于有固定流程但部分步骤可…

Deepin 20.9社区版安装Docker

个人博客地址&#xff1a;Deepin 20.9社区版安装Docker | 一张假钞的真实世界 注意事项 Deepin 20.9 社区版安装 Docker 需要注意两点&#xff1a; 因为某些原因&#xff0c;Docker 官方源基本不可用&#xff0c;所以需要使用镜像源进行安装。当然也可以用安装包直接安装&am…

纯数据挖掘也能发Microbiome?

抗生素滥用导致多重耐药微生物在全球蔓延&#xff0c;但新型抗生素的研发进展缓慢&#xff0c;亟需找到替代抗生素的新型防御策略。抗菌肽&#xff08;AMPs&#xff09;作为天然防御分子&#xff0c;具有低耐药潜力和广谱活性。德国小蠊&#xff08;Blattella germanica&#x…

2025年05月30日Github流行趋势

项目名称&#xff1a;agenticSeek 项目地址url&#xff1a;https://github.com/Fosowl/agenticSeek项目语言&#xff1a;Python历史star数&#xff1a;13040今日star数&#xff1a;1864项目维护者&#xff1a;Fosowl, steveh8758, klimentij, ganeshnikhil, apps/copilot-pull-…

跨平台猫咪桌宠 BongoCat v0.4.0 绿色版

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://pan.xunlei.com/s/VORWH1a7lPhdwvon6DJgKvrNA1?pwdcw2h# 【​本章下载二】&#xff1a;https://pan.quark.cn/s/c3ac86f4e296 【百款黑科技】&#xff1a;https://ucnygalh6wle.feishu.cn/wiki/…

Dify案例实战之智能体应用构建(一)

一、部署dify Windows安装Docker部署dify&#xff0c;接入阿里云api-key进行rag测试-CSDN博客 可以参考我的前面文章&#xff0c;创建一个本地dify或者直接dify官网使用一样的&#xff08;dify官网需要科学上网&#xff09; 二、Dify案例实战之智能体 2.1 智能面试官 需求;…

从模式到架构:Java 工厂模式的设计哲学与工程化实践

一、工厂模式概述 &#xff08;一&#xff09;定义与核心思想 工厂模式&#xff08;Factory Pattern&#xff09;是软件开发中常用的创建型设计模式&#xff0c;其核心思想是将对象的创建过程封装起来&#xff0c;通过工厂类来统一管理对象的创建逻辑。这种模式分离了对象的创…

docker问题记录

docker pull镜像&#xff1a; 即使配置了镜像源也还是走的国外的镜像源&#xff1a; 解决办法&#xff1a;在pull镜像的时候强制走自己的镜像 比如&#xff1a;拉取rabbitmq&#xff0c;强制使用"https://docker.m.daocloud.io"这个镜像 docker pull docker.m.da…

设计模式——代理设计模式(结构型)

摘要 本文详细介绍了代理设计模式&#xff0c;包括其定义、结构组成、实现方式、适用场景及实战示例。代理设计模式是一种结构型设计模式&#xff0c;通过代理对象控制对目标对象的访问&#xff0c;可增强功能或延迟加载等。文中通过类图、时序图、静态代理、JDK动态代理、CGL…

从“固定“到“流动“:移动充电如何重塑用户体验?

在传统充电模式中&#xff0c;"固定"不仅是技术的特征&#xff0c;更成为用户行为的枷锁——人们需要规划行程、寻找插座、等待电量填满&#xff0c;这种被动适配正在被移动充电技术颠覆。当充电设备从墙面解放&#xff0c;化身可携带的能源胶囊&#xff0c;甚至嵌入…

玩客云 OEC/OECT 笔记(1) 拆机刷入Armbian固件

目录 玩客云 OEC/OECT 笔记(1) 拆机刷入Armbian固件玩客云 OEC/OECT 笔记(2) 运行RKNN程序 外观 内部 PCB正面 PCB背面 PCB背面 RK3566 1Gbps PHY 配置 OEC 和 OECT(OEC-turbo) 都是基于瑞芯微 RK3566/RK3568 的网络盒子, 没有HDMI输入输出. 硬件上 OEC 和 OECT…

GIS数据类型综合解析

GIS数据类型综合解析 目录 GIS数据类型综合解析1. 总体介绍2. GIS数据类型分类与对比2.1 主要数据类型对比表 3. 详细解析与扩展内容3.1 矢量数据&#xff08;Vector Data&#xff09;3.2 栅格数据&#xff08;Raster Data&#xff09;3.3 属性数据&#xff08;Attribute Data&…

Prometheus + Grafana 监控常用服务

一、引言 Prometheus监控常见服务的原理主要包括服务暴露指标和Prometheus抓取指标。一方面&#xff0c;被监控服务通过自身提供的监控接口或借助Exporter将服务的性能指标等数据以HTTP协议的方式暴露出来&#xff1b;另一方面&#xff0c;Prometheus根据配置好的采集任务&…