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 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 类进行展开,来从更基础的角度看图像的渲染。

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值