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

 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 itvtkNew<vtkImageMapToWindowLevelColors> windowLevel;windowLevel->SetInputConnection(reader->GetOutputPort());//vtkImageActor: draw an image in a rendered 3D scenevtkNew<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 addedvtkNew<vtkRenderer> renderer;renderer->AddActor(imageActor);renderer->SetBackground(.2,.2,.2);// The render window is the actual GUI window// that appears on the computer screenvtkNew<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 itvtkNew<vtkImageMapToWindowLevelColors> windowLevel;windowLevel->SetWindow(300);windowLevel->SetLevel(40);windowLevel->SetInputConnection(reader->GetOutputPort());//vtkImageActor: draw an image in a rendered 3D scenevtkNew<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 addedvtkNew<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 screenvtkNew<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.rhkb.cn/news/416404.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?

本文将介绍&#xff0c;ClkLog针对神策不支持全埋点的客户端实现用户访问基础统计分析 1。 客户遇到的问题 ClkLog的用户访问基础统计分析功能是基于神策SDK的全埋点来实现的。 我们遇到有些客户是使用C、C#等语言来开发的客户端&#xff0c;然而神策此类SDK&#xff08;如C, C…

[B站大学]Zotero7教程

参考资料: https://www.bilibili.com/video/BV1PSvUetEQX 2. 账号注册与同步 本节内容参考zotero中文社区文档&#xff1a;https://zotero-chinese.com/user-guide/sync 2.1 数据同步 首先注册一个Zotero官方账户。登录账号密码。 2.2 文件同步 按照文档&#xff0c;推荐…

快消品渠道开发方案,让你拥有源源不断的批发客户!

对于快消品行业来说&#xff0c;优质批发渠道客户&#xff0c;决定了你产品的销量。这篇文章&#xff0c;和大家分享下&#xff0c;如何开发渠道端客户&#xff01; 1、分析目标市场 首先&#xff0c;分析快消品行业的趋势&#xff0c;以及你们的产品&#xff0c;目前市场占有…

Java-线程的生命周期7大状态

在 Java 中&#xff0c;线程的生命周期可以分为多个状态&#xff0c;这些状态描述了线程从创建到终止的整个过程。Java 线程的生命周期主要包括以下七大状态&#xff1a; 1.新建状态&#xff08;New&#xff09; 当一个线程对象被创建但尚未调用 start() 方法时&#xff0c;线…

Apache SeaTunnel Zeta 引擎源码解析(一)Server端的初始化

引入 本系列文章是基于 Apache SeaTunnel 2.3.6版本&#xff0c;围绕Zeta引擎给大家介绍其任务是如何从提交到运行的全流程&#xff0c;希望通过这篇文档&#xff0c;对刚刚上手SeaTunnel的朋友提供一些帮助。 我们整体的文章将会分成三篇&#xff0c;从以下方向给大家介绍&am…

鸿蒙(API 12 Beta6版)图形【使用Drawing实现图形绘制与显示 (C/C++)】方舟2D图形服务

场景介绍 Native Drawing模块提供了一系列的接口用于基本图形和字体的绘制。 Drawing绘制的内容无法直接在屏幕上显示&#xff0c;需要借用XComponent以及Native Window的能力支持&#xff0c;将绘制的内容通过Native Window送显。 接口说明 Drawing常用接口如下表所示。 …

二分查找:手拿把掐!------Java代码实现

“没有天赋,那就不断重复.” 文章目录 前言文章有误敬请斧正 不胜感恩&#xff01;模板一:(最基本的)**左闭右闭:** [left,right] 模板二:**左闭右开区间模板:**区间:左闭右开[left,right): 模板三:开区间模板:(left,right) 循环不变量:二分查找易错点:做题经验:疑问及解答&…

内衣内裤衣机什么牌子好?五款口碑爆棚王炸机型推荐

如今科技是越来越发展了&#xff0c;迷你洗衣机的功能也是越来越强大了&#xff0c;这样小户型的家庭甚是喜爱&#xff0c;不仅解决了清洗衣物的问题&#xff0c;还能让小型洗衣机在家中起到一定的装饰效果。在清洁衣物的污渍的同时&#xff0c;还能有效除去衣物上的各种细菌。…

upload-labs闯关攻略

pass-1 提前准备好的一个PHP木马&#xff0c;然后将后缀名改为jpg上传 然后在上传的过程中利用抓包&#xff0c;将抓取到的包里面的后缀jpg改为php如图所示&#xff0c;然后放行 接着我们去访问上传的图片信息&#xff0c;如下图所示就为成功 pass-2 提前准备好的一个PHP木马…

http连接处理(最新版)

分析http类及请求接收 基础 epoll epoll_create函数 #include <sys/epoll.h> int epoll_create(int size) 创建一个指示epoll内核事件表的文件描述符&#xff0c;该描述符将用作其他epoll系统调用的第一个参数&#xff0c;size不起作用。 epoll_ctl函数 #include …

紫光同创——PLL IP 的使用(Logos2)

本文档主要针对 Logos2 系列的 PLL 配置&#xff0c;至于 Logos 系列的 PLL&#xff0c;可以参考《PLLIP 的使用(Logos)》的文档。 一、PLL IP 介绍 1、PLL 基本配置模式 Basic Configurations PLL IP 是紫光同创基于 PLL 及时钟网络资源设计的 IP&#xff0c;通过不同的参数配…

牛客周赛 Round 58(ABCDF)

目录 A.会赢吗&#xff1f; B.能做到的吧 C.会赢的&#xff01; D.好好好数 F.随机化游戏时间 A.会赢吗&#xff1f; 思路&#xff1a; 签到题&#xff0c;比大小 void solve() {double a,b;cin>>a>>b;if(a>b) cout<<"NO";else cout<&…

ByteTrack多目标跟踪(一)—理论基础

ByteTrack多目标跟踪 算法概述 github: https://github.com/ifzhang/ByteTrack ByteTrack是一种基于Tracking-by-Detection范式的多目标跟踪算法。 先前的多目标追踪算法一般在完成当前帧的目标检测后只会保留置信度比较大的检测框用于进行目标跟踪&#xff0c;比如图中置信度…

思维导图在线制作怎么制作?5个软件教你快速进行思维导图制作

思维导图在线制作怎么制作&#xff1f;5个软件教你快速进行思维导图制作 思维导图是一种用于组织信息、梳理思路和激发创意的可视化工具。在线制作思维导图可以帮助你随时随地进行创作和分享&#xff0c;以下是五款在线思维导图工具&#xff0c;可以帮助你快速进行思维导图的制…

828华为云征文|基于华为云Flexus云服务器X搭建FTP服务器

❀目录 ❀概述❀特点❀环境准备❀安装❀配置文件修改❀创建目录、修改权限❀控制台安全组开启21端口❀工具验证❀总结 ❀概述 FTP文件传输协议是一种在网络中进行文件传输的广泛使用的标准协议。作为网络通信中的基础工具&#xff0c;FTP允许用户通过客户端软件与服务器进行交…

Java技术栈 —— Spark入门(二)之实时WordCount

Java技术栈 —— Spark入门&#xff08;二&#xff09; 一、kafka1.1 创建topic1.2 准备input与查看output 二、spark2.1 spark下的程序文件2.2 用spark-submit提交作业 参考文章&#xff1a; 参考文章或视频链接[1] 《Kafka Spark Stream实时WordCount》 实验环境&#xff…

【AQS源码】深入理解AQS的工作原理

【AQS源码】深入理解AQS的工作原理-CSDN博客

从零开始掌握容器技术:Docker的奇妙世界

容器技术在当今的云计算和软件开发领域中扮演着越来越重要的角色。如果你是一名计算机专业的学生或从事IT行业的从业者&#xff0c;可能已经听说过Docker这个词。它在软件开发、部署、运维等环节中大放异彩&#xff0c;但对于刚接触这个概念的朋友来说&#xff0c;可能还是有些…

JMeter在Mac下的安装使用

前言 开发过程中需要对系统进行性能测试&#xff0c;可以选用jemter对接口进行压测&#xff0c;jemter优点如下&#xff1a; 开源许可证&#xff1a;Jmeter完全免费&#xff0c;允许开发者使用源代码进行开发 友好的 GUI&#xff1a;Jmeter 非常易于使用&#xff0c;不需要花…

flume 使用 exec 采集容器日志,转储磁盘

flume 使用 exec 采集容器日志&#xff0c;转储磁盘 在该场景下&#xff0c;docker 服务为superset&#xff0c;flume 的sources 选择 exec &#xff0c; sinks选择 file roll 。 任务配置 具体配置文件如下&#xff1a; #simple.conf: A single-node Flume configuration#…