002:显示DICOM图像(替换掉 vtkImageViewer2 )

news2024/9/20 16:46:36

 VTK 医学图像处理---DICOM图像显示

   对第一个DICOM显示例子的展开(替换掉vtkImageViewer2类)

 

两个例子实现的效果对比,其中右侧是对第一个例子展开后的显示效果,展示了一个完整的VTK渲染管线的过程。

目录

 VTK 医学图像处理---DICOM图像显示

简介:

1 代码对比

2 VTK完整的渲染管线

3  动手测试一下

4  VTK中相机和物体 

总结:


简介:

        在第000章节VTK的安装中,我们运行了第一个例子,来显示DICOM图像,从而让我们可以快速的看到输入、输出和交互,建立学习兴趣,在该例子中使用vtkImageViewer2来显示DICOM图像,在实际开发中我们基本不会用vtkImageViewer2,因为该类对很多内容进行了封装,使用起来不够灵活,同时也使得我们无法深入理解如何来显示一张DICOM 图像,从这一章开始,我们将完整的熟悉VTK的渲染管线。接下来我们将对vtkImageViewer2进行展开,使用完成的VTK渲染管线来渲染同样的DICOM图像,并开始熟悉VTK中的物体和相机的设置。

         本章的组成为,我们先一起来看下vtkImageViewer2显示DICOM图 和  对vtkImageViewer2展开后的代码对比,读者也可以自己新建项目运行下这两种实现方式。然后我们对展开后的代码进行解释和说明。

1 代码对比

 1、第000章节VTK的安装中第一个例子的代码

#include <vtkNew.h>
#include <vtkDICOMImageReader.h>
#include <vtkImageViewer2.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
 
void main()
{
	// 读取指定文件夹"D:\\234"内的所有DICOM文件.
	vtkNew<vtkDICOMImageReader> reader;
	reader->SetDirectoryName("D:\\DicomFiles");
	reader->Update();
 
	// 创建一个交互对象,用了处理鼠标和键盘事件
	vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
 
	// 显示DICOM数据
	vtkNew<vtkImageViewer2> imageViewer;
	imageViewer->SetInputConnection(reader->GetOutputPort());
	imageViewer->SetupInteractor(renderWindowInteractor);
 
	// 初始化渲染和相机
	imageViewer->Render();
	imageViewer->GetRenderer()->ResetCamera();
	/* 指定窗口的比较颜色为灰色(.2,.2,.2)*/
	imageViewer->GetRenderer()->SetBackground(.2,.2,.2);  
	/* 设置窗体的大小为宽800,高800*/
	imageViewer->GetRenderWindow()->SetSize(800, 800);
	/* 设置窗口的标题*/
	imageViewer->GetRenderWindow()->SetWindowName("Read DICOM Series");
	imageViewer->Render();
	renderWindowInteractor->Start();
}

2、 替换掉vtkImageViewer2类后,展开的代码

#include "vtkImageMapToWindowLevelColors.h"
#include "vtkImageActor.h"
#include "vtkImageMapper3D.h"
#include "vtkImageData.h"
#include <vtkNew.h>
#include <vtkDICOMImageReader.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>

int ImageSlice = 0;
void main()
{
	vtkNew<vtkDICOMImageReader> reader;
	reader->SetDirectoryName("D:\\DicomFiles");
	reader->Update();

	int* ext = reader->GetOutput()->GetExtent();

	// map the input image through a lookup table and window / level it
	vtkNew<vtkImageMapToWindowLevelColors> windowLevel;
	windowLevel->SetInputConnection(reader->GetOutputPort());

	//vtkImageActor: draw an image in a rendered 3D scene
	vtkNew<vtkImageActor> imageActor;
	imageActor->SetDisplayExtent(ext[0], ext[1], ext[2], ext[3], ImageSlice, ImageSlice);
	imageActor->GetMapper()->SetInputConnection(windowLevel->GetOutputPort());

	// The renderer generates the image
    // which is then displayed on the render window.
    // It can be thought of as a scene to which the actor is added
	vtkNew<vtkRenderer> renderer;
	renderer->AddActor(imageActor);
	renderer->SetBackground(.2,.2,.2);

	// The render window is the actual GUI window
    // that appears on the computer screen
	vtkNew<vtkRenderWindow> renderWindow;
	renderWindow->SetSize(800, 800);
	renderWindow->AddRenderer(renderer);
	renderWindow->SetWindowName("Dicom Image");

	// The render window interactor captures mouse events
    // and will perform appropriate camera or actor manipulation
    // depending on the nature of the events.
	vtkNew<vtkRenderWindowInteractor> interactor;
	interactor->SetRenderWindow(renderWindow);

	// This starts the event loop and as a side effect causes an initial render.
	renderWindow->Render();
	interactor->Start();
}

3、两个代码运行的结果是一样的,都显示了同一张DICOM图像,但显示效果又差异,这个差异是有什么造成的呢?是由以下两行代码造成的,可以通过注释掉这两行代码,重新编译运行看下显示效果,结果和第一个例子的效果是一致的。

windowLevel->SetWindow(300);
windowLevel->SetLevel(40);

 大家可以在VTK的源代码中搜索  vtkImageViewer2,会搜索到以下两个文件:

打开vtkImageViewer2.h 后,通过查看其源代码,会发现,该类封装了所有展开后例子中使用的类,各个类之间的使用可查看vtkImageViewer2.cxx中的源码,对于初学者来说,vtkImageViewer2.cxx中的很多代码看起来比较费劲,大家可以先跳过这个环节,等后面对VTK熟悉后,再回过头来查看和学习,通过对vtkImageViewer2的学习,大家可以自己来继承vtkObject对象实现简单的封装。

2 VTK完整的渲染管线

     VTK渲染管线,通俗来说就像一节一节的水管,把它们对接起来,连接上水龙头就可以流水了。代码里每个对象就是一节水管,把对象的输入和输出连接起来,就形成了管道,最后启动下,类似连接水龙头,就可以看到渲染的图像,并通过鼠标进行交互了。

     在上面的例子里面,

  1. 首先是 vtkDICOMImageReader,该类主要负责读取文件夹内的DICOM文件;
  2. 然后把读取的数据传递给 vtkImageMapToWindowLevelColors(windowLevel->SetInputConnection(reader->GetOutputPort());); vtkImageMapToWindowLevelColors主要负责把输入的数据转换为RGB或RGBA数据,因为DICOM数据很多时候都是short类型的数据,无法直接在显示器上显示,因此要转换为可在显示器显示的RGB类型数据。
  3. 将数据转换为RGB类型之后在接上下一个处理的类vtkImageActor( imageActor->GetMapper()->SetInputConnection(windowLevel->GetOutputPort()); ),vtkImageActor负责如何把RGB类型的数据绘制出来;
  4. 然后将imageActor输入给 vtkRenderer,vtkRenderer可以添加多个Actor, 还可以设置窗口的背景颜色,设置相机等;
  5. 最后把vtkRenderer传给vtkRenderWindow就结束了,vtkRenderWindow主要用来管理最后显示窗口的。
  6. 这里还有个vtkRenderWindowInteractor类,它主要是用来交互的,后面会单独有一个章节来讲这个类。
  7. 最后连接并打开水龙头, 这两行代码完成: renderWindow->Render();     interactor->Start();

3  动手测试一下

  1.    任务一:修改窗体的背景颜色;
  2.    任务二:修改 vtkImageMapToWindowLevelColors对象中SetWindow  和 SetLevel的值,观察结果;
  3.    任务三:通过查找文档,熟悉vtkImageActor 类中方法 SetDisplayExtent的作用是什么?

4  VTK中相机和物体 

      通常来说,到这里就要开始讲VTK的坐标系统了,但为了让大家更容易上手,我们还是慢慢来。不同于传统的GDI/GDI+在屏幕上绘制图像,VTK有自己的一套坐标系统,在这里我们主要先熟悉下被渲染的物体(DCIM图像)和相机。在上面的代码中是没有相机的,哪来的相机呢,我们慢慢来,反正大家先在心里记住VTK里面是有一个相机的,要想看到物体,那么相机必须对准物体,屏幕上才可以显示物体。类似下图:

在我们的例子里面,物体就是我们要绘制的DICOM图像,既然要相机对准物体才能显示图像,那我们肯定得先知道物体在哪里,然后根据物体的位置来设置相机。我们可以通过下面的代码来获取物体的坐标信息。 先上代码

#include "vtkImageMapToWindowLevelColors.h"
#include "vtkImageActor.h"
#include "vtkImageMapper3D.h"
#include "vtkImageData.h"
#include "vtkNew.h"
#include "vtkDICOMImageReader.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkCamera.h"
int ImageSlice = 0;
void main()
{
	vtkNew<vtkDICOMImageReader> reader;
	reader->SetDirectoryName("D:\\234");
	reader->Update();

	int* ext = reader->GetOutput()->GetExtent();

	// map the input image through a lookup table and window / level it
	vtkNew<vtkImageMapToWindowLevelColors> windowLevel;
	windowLevel->SetWindow(300);
	windowLevel->SetLevel(40);
	windowLevel->SetInputConnection(reader->GetOutputPort());

	//vtkImageActor: draw an image in a rendered 3D scene
	vtkNew<vtkImageActor> imageActor;
	imageActor->SetDisplayExtent(ext[0], ext[1], ext[2], ext[3], ImageSlice, ImageSlice);
	imageActor->GetMapper()->SetInputConnection(windowLevel->GetOutputPort());

	// The renderer generates the image
    // which is then displayed on the render window.
    // It can be thought of as a scene to which the actor is added
	vtkNew<vtkRenderer> renderer;
	renderer->AddActor(imageActor);
	renderer->SetBackground(.2,.2,.2);

	vtkCamera *cam = renderer->GetActiveCamera();
	if (cam)
	{
		// 获取物体在三维空间中的原点,XYZ范围和中心
		vtkImageData* idata = reader->GetOutput();
		double* origins = idata->GetOrigin(); // 三维坐标中的起点
		double* bounds = idata->GetBounds();  // 包围盒的xyz范围
		double* center = idata->GetCenter();  // 中心

		// 输出
		std::cout << "Actor origins: " << origins[0] << "   " << origins[1] << "   " << origins[2] << std::endl;
		std::cout << "Actor center: " << center[0] << "   " << center[1] << "   " << center[2] << std::endl;
		std::cout << "Actor bounds: " << bounds[0] << "   " << bounds[1] << "   " << bounds[2] 
			<< bounds[3] << "   " << bounds[4] << "   " << bounds[5] << std::endl;

		cam->SetFocalPoint(center);
		cam->SetPosition(center[0], center[1], center[2] - bounds[5]); // -1 if medical ?
		cam->SetViewUp(0, 1, 0);
		cam->SetClippingRange(0.1,1000);
		renderer->ResetCamera();
	}

	// The render window is the actual GUI window
    // that appears on the computer screen
	vtkNew<vtkRenderWindow> renderWindow;
	renderWindow->SetSize(512, 512);
	renderWindow->AddRenderer(renderer);
	renderWindow->SetWindowName("Dicom Image");

	// The render window interactor captures mouse events
    // and will perform appropriate camera or actor manipulation
    // depending on the nature of the events.
	vtkNew<vtkRenderWindowInteractor> interactor;
	interactor->SetRenderWindow(renderWindow);

	// This starts the event loop and as a side effect causes an initial render.
	renderWindow->Render();
	interactor->Start();
}

 在我们增加的代码中,我们先获取物体的坐标信息,然后再获取相机,将相机的焦点对准了物体的中心,将相机的位置设置再Z方向上原理物体bounds[5]的位置上,然后设置相机的正方向指向Y轴,最后设置相机的成像范围(SetClippingRange),之后就可以显示图像了。

小任务:通过VTK的官网文档熟悉 vtkImageData 和 vtkCamera两个类,后面我们会重点介绍vtkImageData类

总结:

         主要介绍了完整的VTK渲染管线、VTK中的物体和相机的设置。这三部分内容是需要大家重点去掌握和熟悉的,可以配合VTK电子书的章节一起来学习:Chapter 3 - Computer Graphics Primerexamples.vtk.org/site/VTKBook/03Chapter3/

后面我们会对vtkImageActor 类进行展开,来从更基础的角度看图像的渲染。

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

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

相关文章

项目文章|PNAS:中国农大田见晖教授团队揭示DNA甲基化保护早期胚胎线粒体基因组稳定性

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 在早期哺乳动物胚胎中&#xff0c;线粒体氧化代谢增强是着床后生存和发育的重要特征&#xff1b;着床前期的线粒体重塑是正常胚胎发生的关键事件。在这些变化中&#xff0c;氧化磷酸化&a…

Java学习中如何分辨 = 和 == 及其使用方法

在学习Java编程语言时&#xff0c; 和 是两个非常基础的运算符&#xff0c;虽然它们看起来相似&#xff0c;但在语义和应用场景上却有明显的区别。理解并正确使用这两个符号对于编写正确且高效的Java代码至关重要。 1. 运算符&#xff1a;赋值运算符 在Java中是赋值运算符&a…

”wait”和“notify”为什么要在Synchronized代码块里面?

wait和notify用来实现多线程之间的协调&#xff0c;wait表示让线程进入到阻塞状态&#xff0c;notify表示让阻塞的线程唤醒。 wait和notify必然是成对出现的&#xff0c;如果一个线程被wait()方法阻塞&#xff0c;那么必然需要另外一个线程通过notify()方法来唤醒这个被阻塞的…

作为技术Leader如何带散一个团队

theme: channing-cyan 大家好&#xff0c;我是程序员凌览。 这个话题本身就很有趣——如何有效地带散一个团队&#xff0c;精选了两位网友的回答让我们一起来看看。 第一位网友的回答 1938年10月14日&#xff0c;毛泽东谈了如何把团队带好。你反着来&#xff0c;肯定能把团…

【计算机组成原理】 计算机发展历程

文章目录 计算机发展历程计算机系统的概念计算机硬件计算机软件 计算机硬件的发展计算机的四代变化计算机元件的更新换代 计算机软件的发展计算机的发展趋势 计算机发展历程 计算机系统的概念 计算机系统 计算机硬件 计算机软件 计算机硬件 计算机的实体&#xff0c;如主…

16、java 面向对象之三:方法参数的值传递机制(参数为基本数据类型、参数为引用数据类型的案例剖析及内存解析)

java 面向对象之三&#xff1a; Ⅰ、方法的参数传递&#xff1a;参数为基本数据类型1、基本数据类型的值传递&#xff1a;其一、代码为&#xff1a;其二、内存解析为&#xff1a;其三、截图为&#xff1a; Ⅱ、方法的参数传递&#xff1a;参数为引用数据类型1、引用数据类型的值…

docker镜像多阶段构建

Docker 多阶段构建是为了创建轻量化和更高效的 Docker 镜像而产生的一种技术。通过使用多阶段构建&#xff0c;可以将不同阶段的构建需求分离开来&#xff0c;最终结果只包含实际部署所需要的部分&#xff0c;从而缩小镜像的大小。 以下是使用多阶段构建的基本步骤&#xff1a…

SpringBoot项目初始化搭建

SpringBoot项目搭建 创建SpringBoot项目事务编程式事务声明式事务 PageHelper主要特点&#xff1a;依赖使用常用方法 跨域问题JSONP&#xff08;JSON with Padding&#xff09;工作原理&#xff1a;代码示例 CORS&#xff08;Cross-Origin Resource Sharing&#xff09;工作原理…

江协科技STM32学习- P11 中断系统,EXTI外部中断

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

网络ACL详解-从原理到实战模拟

引言 在复杂多变的网络环境中&#xff0c;保障网络安全和数据传输的合法性、高效性至关重要。访问控制列表&#xff08;Access Control Lists&#xff0c;简称ACL&#xff09;作为网络安全的重要组成部分&#xff0c;广泛应用于各种网络设备中&#xff0c;用以控制网络流量的流…

传输大咖39 | 半导体行业的FTP替代升级方案

在半导体行业&#xff0c;数据的快速、安全、稳定传输是研发和生产的关键。传统的FTP&#xff08;文件传输协议&#xff09;虽然在早期被广泛使用&#xff0c;但随着行业的发展&#xff0c;其局限性逐渐显现。本文将探讨传统FTP在半导体行业的不足&#xff0c;并介绍镭速提供的…

【JAVA入门】Day31 - 双列集合 —— Map 系列

【JAVA入门】Day31 - 双列集合 —— Map 系列 文章目录 【JAVA入门】Day31 - 双列集合 —— Map 系列一、双列集合体系结构二、Map 的遍历方式2.1 键找值2.2 键值对遍历2.3 Lambda 表达式遍历键值对 三、HashMap3.1 HashMap的创建 四、LinkedHashMap五、TreeMap 单列集合每次添…

Excel使用VLOOKUP公式匹配不出正确公式,返回#N/A

问题&#xff1a; Excel使用VLOOKUP公式匹配不出正确公式&#xff0c;返回#N/A 原因&#xff1a; 原数据和匹配的数据格式不一致 解决办法&#xff1a; 把格式都设置为文本。 例如添加一列&#xff0c;输入英文符号&#xff0c;然后把数据源拼接起来&#xff0c;转换为文…

3招解决苹果手机qq图片恢复问题,快速恢复原图

Q&#xff1a;我手机QQ上的图片打不开了&#xff0c;怎么办呢&#xff1f;你有什么方法可以帮我解决这个问题吗&#xff1f; A&#xff1a;当然有啦&#xff0c;小编在数据恢复这个方面可是很厉害的。今天&#xff0c;小编就告诉你3个超绝的QQ图片恢复的小妙计&#xff0c;可以…

九盾叉车U型区域警示灯,高效照明和安全警示

叉车运作的环境比较复杂&#xff0c;在方便人们物流运输的同时也存在着很大的安全隐患&#xff0c;特别是叉车碰撞人的事故发生率很高&#xff0c;那我们该怎么在减少成本的同时又能避免碰撞事故的发生呢&#xff1f; 九盾叉车U型区域警示灯&#xff0c;仅需一盏灯安装在叉车尾…

有人送出几百克黄金,有人搭上百万!现在快递也不敢收了?

还记得一年多前小柴遇到过个这样的事&#xff0c;有一天早上&#xff0c;小柴还没睡醒&#xff0c;就被一京东小哥的敲门声吵醒了&#xff01; 小柴当时揉着眼睛&#xff0c;稀里糊涂的签收了这小哥送来的两个快递&#xff0c;没成想&#xff0c;不到一分钟的时间&#xff0c;…

基于多模态大语言模型的摄像头实时感知交互

简介&#xff1a; 调用本地摄像头&#xff0c;通过多模态大语言模型实时感知世界&#xff0c;并进行交互 界面&#xff1a; 代码&#xff1a; import tkinter as tk from tkinter import ttk from PIL import Image, ImageTk import cv2 import requests# 定义处理函数 def…

从源码到应用:数字药店系统与医保购药APP的开发实践

本篇文章&#xff0c;我们将深入探讨数字药店系统的开发过程&#xff0c;并介绍医保购药APP如何通过源码设计实现从基础功能到完整应用的转化。 一、数字药店系统概述 数字药店系统是一种基于互联网技术开发的在线药品销售与管理平台&#xff0c;通常包括药品展示、在线购买、…

NAT技术介绍+缺陷(内网穿透+工具),NAPT(介绍,替换过程,原理,NAT转换表)

目录 NAT技术 介绍 NAT转换表 引入 介绍 NAPT技术介绍 NAPT替换过程 NAPT原理 注意点 NAT缺陷 无法直接访问其他内网主机 内网穿透 工具 其他 NAT技术 介绍 NAT 是一种网络技术&#xff0c;它允许在一个公共 IP 地址和多个私有 IP 地址(入口路由器的wan口地址 …

屋顶上的气膜体育馆:商场创新的引流利器—轻空间

现代都市生活中&#xff0c;商场已成为不可或缺的一部分。然而&#xff0c;在竞争激烈的市场环境中&#xff0c;能够脱颖而出的商场往往依赖于独特的经营策略和创新的理念。上海嘉定某商场正是凭借其巧妙的创新思路&#xff0c;成功吸引了大量顾客&#xff0c;成为区域内的商业…