VTK教程系列:VTK基础及应用开发教程

        由于OpenCV不能使用,只能使用VTK库的图像处理库,暂时还没有找到其他可以全面替代的库;

       CSDN东灵工作室:http://blog.csdn.net/www_doling_net/article/details/8763686


复制其第一篇:

注:以后将陆续分享一些对经典图像、图形算法的介绍,

新浪微博: @东灵工作室

邮箱:www_doling_net@163.com

ITK、VTK研究群:209215671

 

系列一 《VTK基础及应用开发教程》

基础入门篇:

1.        从零开始学习VTK

2.        VTK编译安装

3.        VTK基础概念

1)      VTK基础概念(1)

2)      VTK基础概念(2)

4.        VTK可视化管线

1)      VTK可视化管线(1)

2)      VTK可视化管线(2)

5.        VTK在图像处理中的应用

1)      VTK在图像处理中的应用(1)

2)      VTK在图像处理中的应用(2)

3)      VTK在图像处理中的应用(3)

4)      VTK在图像处理中的应用(4)

5)      VTK在图像处理中的应用(5)

6)      VTK在图像处理中的应用(6)

7)      VTK在图像处理中的应用(7)

8)      VTK在图像处理中的应用(8)

9)      VTK在图像处理中的应用(9)

6.        VTK基本数据结构

1)      VTK基本数据结构(1)

2)      VTK基本数据结构(2)

3)      VTK基本数据结构(3)

4)      VTK基本数据结构(4)

5)      VTK基本数据结构(5)

高阶应用篇:

7.      基于VTKQt应用程序开发

8.      基于VTK&ITKQt应用程序开发

9.      基于VTKMFC应用程序开发

1)      基于VTKMFC应用程序开发(1)

2)      基于VTKMFC应用程序开发(2)

3)      基于VTKMFC应用程序开发(3)


基于VTK的Qt应用程序开发

分类: VTK应用示例 1916人阅读 评论(23) 收藏 举报

目录(?)[+]

VTK附带的程序示例中大多是基于控制台的,作为可视化开发工具包,VTK也可以与很多流行的GUI开发工具整合,比如MFC、Qt(题外话:Qt已经被Digia从诺基亚手中收购了,Qt现在的链接是:http://qt-project.org/,也有已经编译好的版本:http://code.google.com/p/qt-msvc-installer/downloads/list直接下载安装。可能因为大学课程里会教授MFC的内容,一些非计算机专业的会偏向于采用MFC,个人觉得,对于非计算机专业而言,如果一定要选择一种GUI工具做开发的话,建议用Qt,容易上手,学习周期短)、FLTK(http://www.fltk.org/,FLTK也是跨平台的,是一种比较轻便的GUI工具,VTK官方发布版本没有提供对FLTK的接口,但可以借助类vtkFlRenderWindowInteractor,来实现VTK与FLTK的整合)等等,VTK的源码目录里(VTK-5.10\Examples\GUI)包含有VTK与Qt、MFC、Tcl等工具的整合。考虑到VTK对Qt的特殊照顾(VTK提供了很多针对Qt的类可以非常方便地与Qt整合),以及Qt自身的一些性质(如易用性、跨平台等),我们参考了VTK自带的一些例子,给出了VTK与Qt整合的详细步骤。

1.   CMakeLists.txt文件

我们已经知道了VTK工程的管理是用CMake的,而Qt自身有qmake工具,如果对于一些小工程而言,单纯的Qt程序用qmake来构建工程,确实很方便,但如果随着工程复杂度的增加以及工程依赖其他的函数库时,使用CMake来管理工程或许是一个明智的选择。而且随着你对CMake语法的了解,你会发现用CMake来管理工程是一件非常棒的事情。

我们先看看对于单纯的Qt工程,怎么来写CMakeLists.txt脚本文件。

1.1 用CMake来管理Qt工程

官方对于这个话题给出的解释在这里。我们引用一下这篇博文的图,然后给出每句CMakeLists.txt脚本的注释,结合这个图以及脚本的注释,相信你应该能明白了。

 

[plain] view plaincopy
  1. #----------------------------------------------  
  2. # 下面这两行,没什么好解释的  
  3. cmake_minimum_required( VERSION 2.8 )  
  4. project( YourProjectName )  
  5.    
  6. #----------------------------------------------  
  7. # 下面这两行,也没什么好解释的  
  8. find_package( Qt4 REQUIRED )  
  9. include( ${QT_USE_FILE} )  
  10.    
  11. #----------------------------------------------  
  12. # 程序所有源文件。<TODO:在此处添加源文件>  
  13. # 定义变量Project_SRCS,其值为所列的文件列表  
  14. SET( Project_SRCS  
  15.     main.cpp  
  16.   )  
  17.    
  18. #----------------------------------------------  
  19. # 程序所有UI文件。<TODO:在此处添加UI文件>  
  20. # 定义变量Project_UIS,其值为所列的文件列表  
  21. SET( Project_UIS  
  22.     YourQtWindows.ui  
  23. )  
  24.    
  25. #----------------------------------------------  
  26. # 所有包含Q_OBJECT的头文件。<TODO:在此处添加头文件>  
  27. # 定义变量Project_MOC_HDRS,其值为所列的文件列表  
  28. SET( Project_MOC_HDRS  
  29.     YourQtProjectFiles.h  
  30. )  
  31.    
  32. #-----------------------------------------------  
  33. # 通过Qt的uic.exe生成UI文件对应的ui_XXXX.h文件  
  34. # 将生成的ui_XXXX.h文件放在变量Project_UIS_H里,  
  35. # QT4_WRAP_UI就是干这个事情。  
  36. QT4_WRAP_UI( Project_UIS_H ${Project_UIS} )  
  37.    
  38. #-----------------------------------------------  
  39. # 通过Qt的moc.exe生成包含Q_OBJECT的头文件对应的  
  40. # moc_XXXX.cxx文件,将生成的moc_XXXX.cxx文件放在  
  41. # 变量Project_MOC_SRCS里。QT4_WRAP_CPP就是干这个事情。  
  42. QT4_WRAP_CPP( Project_MOC_SRCS ${Project_MOC_HDRS} )  
  43.    
  44. #-----------------------------------------------  
  45. # Qt的MOC和UIC程序生成的moc_XXXX.cxx和ui_XXXX.h  
  46. # 等文件是存放在CMake的“Where to build the binaries"  
  47. # 里指定的目录里,所以必须都这些路径包含进来。  
  48. INCLUDE_DIRECTORIES( ${Project_SOURCE_DIR}  
  49.                      ${CMAKE_CURRENT_BINARY_DIR}  
  50.                    )  
  51.    
  52. #-----------------------------------------------                            
  53. # Qt程序如果有资源文件(*.qrc),要包含资源文件,  
  54. # 然后用Qt的rcc.exe生成相应的qrc_XXXX.cpp文件。  
  55. # QT4_ADD_RESOURCES就是干这个事情。  
  56. SET( Project_RCCS YourProject.qrc)  
  57. QT4_ADD_RESOURCES( Project_RCC_SRCS ${Project_RCCS})  
  58.    
  59. #-----------------------------------------------  
  60. # 根据程序的cpp文件、头文件以及中间生成的ui_XXXX.h、  
  61. # moc_XXXX.cxx、qrc_XXXX.cxx等生成可执行文件,并链接  
  62. # Qt的动态库(Qt的动态库都定义在QT_LIBRARIES变量里了)  
  63. ADD_EXECUTABLE( YourProjectName  
  64.                 ${Project_SRCS}  
  65.                 ${Project_UIS_H}  
  66.                 ${Project_MOC_SRCS}  
  67.                 ${Project_RCC_SRCS}                             
  68.               )  
  69. TARGET_LINK_LIBRARIES ( YourProjectName ${QT_LIBRARIES} )  

1.2 用CMake来管理Qt与VTK工程

我们在上面的基础上添加VTK相关的CMake脚本文件,如下:

[plain] view plaincopy
  1. #----------------------------------------------------------------------------------  
  2. cmake_minimum_required( VERSION 2.8 )  
  3. project( CombineQtAndVTK )  
  4.    
  5. #----------------------------------------------------------------------------------  
  6. find_package( VTK REQUIRED )  
  7. find_package( Qt4 REQUIRED )  
  8.    
  9. include( ${VTK_USE_FILE} )  
  10. include( ${QT_USE_FILE} )  
  11.    
  12. #----------------------------------------------------------------------------------  
  13. SET( PROJECT_SRCS  
  14.     main.cpp  
  15.     ProjectMainWindow.cpp  
  16.     )  
  17.    
  18. SET( PROJECT_UIS  
  19.     ProjectMainWindow.ui  
  20. )  
  21.    
  22. SET( PROJECT_MOC_HDRS  
  23.   ProjectMainWindow.h  
  24. )  
  25.    
  26. #----------------------------------------------------------------------------------  
  27. QT4_WRAP_UI( PROJECT_UIS_H  
  28.              ${PROJECT_UIS}  
  29.            )  
  30.    
  31. QT4_WRAP_CPP( PROJECT_MOC_SRCS  
  32.               ${PROJECT_MOC_HDRS}  
  33.             )  
  34.    
  35. #----------------------------------------------------------------------------------  
  36. INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}  
  37.                      ${CMAKE_CURRENT_BINARY_DIR}  
  38.                      ${VTK_DIR}  
  39.                    )  
  40.    
  41. ADD_EXECUTABLE( CombineQtAndVTK  
  42.                 ${PROJECT_SRCS}  
  43.                 ${PROJECT_UIS_H}  
  44.                 ${PROJECT_MOC_SRCS}  
  45.               )  
  46.    
  47. TARGET_LINK_LIBRARIES ( CombineQtAndVTK  
  48.   ${VTK_LIBRARIES}  
  49.   QVTK  
  50.   )  

以上的脚本除了红色字体标注的跟1.1注释的不太像之外,其他的都一样,不再解释。

1.3 CMake脚本里增加工程环境变量的加载

很多非计算机专业的用户在使用VTK进行编程时,经常会碰到类似下图所示的一些错误。

 

碰到这样的错误以后,可能很多用户就不知道怎么处理了,其实上面的提示信息已经写得非常清楚了,就是缺少“vtkCommon.dll”文件。但是又有人会说:我的电脑里明明有这个文件存在啊,为什么会找不到呢?

一般的解决方法可能是:

方法一:将缺少的dll文件全部拷贝的工程的Debug或者Release目录下(拷贝的时候要注意你编译的VTK是Debug版本的还是Release版本的,如果拷错的话,又会出现其他不可预知的错误了)。但是这个方法是你每建一个工程,运行工程之前得把缺少的动态库文件又要拷贝过去,如果你不嫌麻烦的话,可以采用。

方法二:将缺少的dll文件全部拷贝到Windows系统的目录下,即C:\Windows\system32或者C:\Windows\system目录下,这个方法是你拷贝一次,以后再基于你拷贝的VTK动态库的工程运行的时候问题都解决了。但它同样有一个问题,假如你电脑里的VTK升级成别的版本,重新编译了一份动态库,或者是同时在你电脑里编译了好几个版本的VTK,这个时候就有点凌乱了。

为什么这两种方法都可以解决问题?原来动态编译的程序在启动的时候,会搜索程序所在的目录以及系统环境变量PATH所列的目录,如果这些目录有该程序需要的动态库时,就加载它们,如果没有,就提示无法加载相应动态库的错误。

可以在工程的CMakeLists.txt文件里添加一些脚本,把系统的PATH环境变量作一些更改,在工程启动之前加载这些环境变量。也就是(在工程的CMakeLists.txt最后添加):

[plain] view plaincopy
  1. #-----------------------------------------------------------------------------------  
  2. # Construct a list of paths containing runtime directories for project applications on Windows  
  3. set(PROJECT_RUNTIME_PATH  "${VTK_LIBRARY_DIRS}/@VS_BUILD_TYPE@;  
  4. ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/@VS_BUILD_TYPE@"  
  5.     )  
  6. if(QT4_FOUND)  
  7.   set(PROJECT_RUNTIME_PATH "${PROJECT_RUNTIME_PATH};${QT_LIBRARY_DIR}/../bin")  
  8. endif()  
  9.    
  10. include(CreateWindowsBatchScript.cmake)  
  11.    
  12. # If we are under Windows, create two batch files which correctly  
  13. # set up the environment for the application and for Visual Studio  
  14. if(WIN32)  
  15.   set(VS_SOLUTION_FILE "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.sln")  
  16.   foreach(VS_BUILD_TYPE debug release)  
  17.     CreateWindowsBatchScript("${CMAKE_SOURCE_DIR}/StartVS.bat.in"  
  18.       ${PROJECT_BINARY_DIR}/StartVS_${VS_BUILD_TYPE}.bat  
  19.       ${VS_BUILD_TYPE})  
  20.   endforeach()  
  21. endif(WIN32)  


以上的脚本也不是特别复杂,但提到了两个文件:CreateWindowsBatchScript.cmake以及StartVS.bat.in。这两个文件的内容分别是:

CreateWindowsBatchScript.cmake:

 

[plain] view plaincopy
  1. function(CreateWindowsBatchScript in out build_type)  
  2.   if(VTK_DIR)  
  3.     set(VTK_BIN_DIR "${VTK_DIR}/bin/${build_type}")  
  4.   else()  
  5.     set(VTK_BIN_DIR)  
  6.   endif()  
  7.    
  8.   set(VS_BUILD_TYPE ${build_type})  
  9.   configure_file(${in} ${out} @ONLY)  
  10.   # substitute again  
  11.   configure_file(${out} ${out} @ONLY)  
  12. endfunction()  

StartVS.bat.in

 

[plain] view plaincopy
  1. @set CL=/D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE  
  2. @set LINK=/LARGEADDRESSAWARE  
  3.    
  4. PATH=@PROJECT_RUNTIME_PATH@;%PATH%  
  5. "@VS_SOLUTION_FILE@"  

将工程通过CMake的configure->generate以后,即可生成StartVS_debug.bat和StartVS_release.bat两个脚本文件。如果你要编译、运行Debug版本的工程,即双击StartVS_debug.bat文件打开对应的工程,同理,Release版本的也一样。一旦按这种方式打开相应的工程,就不用再担心类似“无法加载***.dll文件”的错误了。如果你的程序还增加了ITK等函数库,也可以照着上面的脚本作相应的修改。

注意:使用时将CreateWindowsBatchScript.cmakeStartVS.bat.in两个文件与工程的CMakeLists.txt放在同一级目录里。即类似下图的目录结构:

 

2.   用QVTKWidget整合Qt&VTK

Qt与VTK的整合可以使用VTK提供的类QVTKWidget,看这个类名就知道这个类其实就是一个Qt里的Widget (QVTKWidget派生自QWidget),所以可以把它当作普通的Qt里的Widget来使用,甚至可以在Qt Designer里像Qt的其他标准控件一样拖来拖去。

2.1 在Qt Designer里集成

要实现QVTKWidget在Qt Designer里像Qt的其他标准控件一样拖来拖去,需要把编译生成的QVTKWidgetPlugin.dll/QVTKWidgetPlugin.lib(Release版本)复制到Qt的安装目录里的plugins\designer目录下。完了以后,你会在Qt Designer里面看到如下的控件:

 

2.2 读入一幅图像,并在Qt界面上显示

接下来,我们来完成一个小功能,就是读入一幅JPG图像,然后在Qt界面上,用VTK来显示。功能非常简单,程序也非常简单。上代码:

ProjectMainWindow.h:

  1. #ifndef Project_MainWindow_H  
  2. #define Project_MainWindow_H  
  3.    
  4. #include <QMainWindow>  
  5. #include "ui_ProjectMainWindow.h"  
  6.    
  7. #include <vtkSmartPointer.h>  
  8.    
  9. class vtkImageViewer2;  
  10. class vtkRenderer;  
  11.    
  12.    
  13. class ProjectMainWindow : public QMainWindow, public Ui::ProjectMainWindow  
  14. {  
  15.        Q_OBJECT  
  16.    
  17. public:  
  18.        ProjectMainWindow();  
  19.        ~ProjectMainWindow();  
  20.    
  21. private slots:  
  22.        //响应打开图像文件的槽函数  
  23.        void onOpenSlot();  
  24.    
  25. private:  
  26.        vtkSmartPointer< vtkImageViewer2 > m_pImageViewer;  
  27.        vtkSmartPointer< vtkRenderer > m_pRenderder;  
  28. };  
  29.    
  30. #endif  

ProjectMainWindow.cpp:

  1. #include "ProjectMainWindow.h"  
  2.    
  3. #include <QFileDialog>  
  4. #include <QDir>  
  5.    
  6. #include <vtkRenderWindow.h>  
  7. #include <vtkRenderer.h>  
  8. #include <vtkImageViewer2.h>  
  9. #include <QVTKWidget.h>  
  10. #include <vtkJPEGReader.h>  
  11. #include <vtkImageActor.h>  
  12.    
  13. ProjectMainWindow::ProjectMainWindow()  
  14. {  
  15.        setupUi(this);  
  16.    
  17.        m_pImageViewer  = vtkSmartPointer< vtkImageViewer2 >::New();  
  18.        m_pRenderder      = vtkSmartPointer< vtkRenderer >::New();  
  19.    
  20.        // 设置m_QVTKWidget的渲染器  
  21.        m_QVTKWidget->GetRenderWindow()->AddRenderer(m_pRenderder);  
  22.    
  23.        //连接打开的信号与相应的槽  
  24.        connect( m_OpenAction, SIGNAL( triggered() ), this, SLOT( onOpenSlot() ) );  
  25. }  
  26.    
  27. ProjectMainWindow::~ProjectMainWindow()  
  28. {  
  29. }  
  30.    
  31. void ProjectMainWindow::onOpenSlot()  
  32. {  
  33.        QString filter;  
  34.        filter = "JPEG image file (*.jpg *.jpeg)";  
  35.    
  36.        QDir dir;  
  37.        QString fileName = QFileDialog::getOpenFileName( this,   
  38.                                  QString(tr("打开图像")), dir.absolutePath() , filter );  
  39.        if ( fileName.isEmpty() == true ) return;  
  40.    
  41.        // 支持带中文路径的读取  
  42.        QByteArray ba = fileName.toLocal8Bit();  
  43.        const char *fileName_str = ba.data();  
  44.    
  45.        // 用vtkJPEGReader读取JPG图像  
  46.        vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();  
  47.        reader->SetFileName(fileName_str);  
  48.    
  49.        // 将reader的输出作为m_pImageViewer的输入,并设置m_pImageViewer与渲染器m_pRenderer的关联  
  50.        m_pImageViewer->SetInput(reader->GetOutput());  
  51.        m_pImageViewer->UpdateDisplayExtent();  
  52.        m_pImageViewer->SetRenderWindow(m_QVTKWidget->GetRenderWindow());  
  53.        m_pImageViewer->SetRenderer(m_pRenderder);  
  54.        m_pImageViewer->SetupInteractor(m_QVTKWidget->GetRenderWindow()->GetInteractor());  
  55.        m_pImageViewer->SetSliceOrientationToXY(); //默认就是这个方向的  
  56.        m_pImageViewer->GetImageActor()->InterpolateOff();  
  57.        m_pRenderder->ResetCamera();  
  58.        m_pRenderder->DrawOn();  
  59.        m_QVTKWidget->GetRenderWindow()->Render();  
  60. }  

程序运行结果:

 

2.3 用vtkEventQtSlotConnect实现VTK事件与Qt槽的连接

类vtkEventQtSlotConnect可以实现VTK的事件与Qt的槽函数的连接,VTK的事件主要在vtkCommand.h文件里定义,包括鼠标单击、鼠标双击、鼠标移动等等,如:

vtkCommand::ProgressEvent

vtkCommand::ErrorEvent

vtkCommand::WarningEvent

vtkCommand::PickEvent

vtkCommand::StartPickEvent

vtkCommand::EndPickEvent

vtkCommand::CharEvent

vtkCommand::KeyPressEvent

vtkCommand::KeyReleaseEvent

vtkCommand::LeftButtonPressEvent

vtkCommand::LeftButtonReleaseEvent

vtkCommand::MouseMoveEvent

……

具体的代码实现:

  1. private slots:  
  2.        //响应鼠标移动的消息,实时输出鼠标的当前位置  
  3.        void updateCoords(vtkObject* obj);  
  4.    
  5. private:  
  6.        vtkEventQtSlotConnect* m_Connections;  


源文件:

  1. //构造函数里:  
  2.        m_Connections = vtkEventQtSlotConnect::New();  
  3.        m_Connections->Connect(m_QVTKWidget->GetRenderWindow()->GetInteractor(),  
  4.               vtkCommand::MouseMoveEvent,  
  5.               this,  
  6.               SLOT(updateCoords(vtkObject*)));  
  7.    
  8. //槽函数的实现  
  9. void ProjectMainWindow::updateCoords(vtkObject* obj)  
  10. {  
  11.        // 获取交互器  
  12.        vtkRenderWindowInteractor* iren = vtkRenderWindowInteractor::SafeDownCast(obj);  
  13.    
  14.        // 获取鼠标的当前位置  
  15.        int event_pos[2];  
  16.        iren->GetEventPosition(event_pos);  
  17.    
  18.        QString str;  
  19.        str.sprintf("x=%d : y=%d", event_pos[0], event_pos[1]);  
  20.        m_StatusBar->showMessage(str);  
  21. }  


程序运行结果:

 

示例代码及该博文文档下载地址:http://download.csdn.net/detail/www_doling_net/5137375



(3)二维图像处理:05-VTK在图像处理中的应用(4)

分类: VTK系列教程 948人阅读 评论(4) 收藏 举报

目录(?)[+]

5.7 区域提取

5.7.1 提取感兴趣区域 

感兴趣区域(Volum of Interest)是指图像内部的一个子区域。在VTK中vtkExtractVOI类实现由用户指定的区域范围提取图像的子图像。该Filter的输入和输出都是一个vtkImageData,因此其结果可以直接作为图像保存。

 

   1:      vtkSmartPointer<vtkBMPReader> reader =

   2:          vtkSmartPointer<vtkBMPReader>::New();

   3:      reader->SetFileName ( "lena.bmp" );

   4:      reader->Update();

   5:  

   6:      int dims[3];

   7:      reader->GetOutput()->GetDimensions(dims);

   8:  

   9:      vtkSmartPointer<vtkExtractVOI> extractVOI =

  10:          vtkSmartPointer<vtkExtractVOI>::New();

  11:      extractVOI->SetInputConnection(reader->GetOutputPort());

  12:      extractVOI->SetVOI(dims[0]/4.,3.*dims[0]/4.,dims[1]/4.,3.*dims[1]/4., 0, 0);

  13:      extractVOI->Update();

 

上例代码实现了提取一副图像的子区域。首先读取一个图像,并获取图像的维数。然后定义vtkExtractVOI对象,该对象接收两个输入一个是图像数据,第二个是区域大小。设置区域大小的函数原型:

void SetVOI(int _arg1, int _arg2, int _arg3, int _arg4, int _arg5, int _arg6)

void SetVOI(int _arg[])

其参数是提取的区域各个方向的大小,共6个参数,依次表示x方向最小值,x方向最大值,y方向最小值,y方向最大值,z方向最小值和z方向最大值。上例中由于读取的是二维图像,因此z方向的区域为[0,0],而在x方向范围为[ dims[0]/4 , 3*dims[0]/4 ],y方向范围为[ dims[1]/4 , 3*dims[1]/4 ],即提取图像原图中间1/4图像。执行结果如下:

 

图5.18 提取感兴趣区域

5.7.2 三维图像切片提取

切片是指三维图像中的一个切面对应的图像。切面可以是过图像内部一点且平行于XY、YZ、XZ平面的平面,也可以是任意的过三维图像内部一点任意方向的平面。通过提取切片可以方便的浏览和分析图像内部组织结构,是医学图像浏览软件中的一个重要的功能。在VTK中vtkImageReslice类实现图像切片提取功能。下面首先看一段切片提取的代码。

1:  vtkSmartPointer<vtkMetaImageReader> reader =

   2:     vtkSmartPointer<vtkMetaImageReader>::New();

   3:  reader->SetFileName ( " brain.mhd" );

   4:  reader->Update();

   5:   

   6:  int extent[6];

   7:  double spacing[3];

   8:  double origin[3];

   9:   

  10:  reader->GetOutput()->GetExtent(extent);

  11:  reader->GetOutput()->GetSpacing(spacing);

  12:  reader->GetOutput()->GetOrigin(origin);

  13:   

  14:  double center[3];

  15:  center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);

  16:  center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);

  17:  center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);

  18:   

  19:  static double axialElements[16] = {

  20:     1, 0, 0, 0,

  21:     0, 1, 0, 0,

  22:     0, 0, 1, 0,

  23:     0, 0, 0, 1 };

  24:   

  25:  vtkSmartPointer<vtkMatrix4x4> resliceAxes =

  26:     vtkSmartPointer<vtkMatrix4x4>::New();

  27:  resliceAxes->DeepCopy(axialElements);

  28:   

  29:  resliceAxes->SetElement(0, 3, center[0]);

  30:  resliceAxes->SetElement(1, 3, center[1]);

  31:  resliceAxes->SetElement(2, 3, center[2]);

  32:   

  33:   

  34:  vtkSmartPointer<vtkImageReslice> reslice =

  35:     vtkSmartPointer<vtkImageReslice>::New();

  36:  reslice->SetInputConnection(reader->GetOutputPort());

  37:  reslice->SetOutputDimensionality(2);

  38:  reslice->SetResliceAxes(resliceAxes);

  39:  reslice->SetInterpolationModeToLinear();

  40:   

  41:  vtkSmartPointer<vtkLookupTable> colorTable =

  42:     vtkSmartPointer<vtkLookupTable>::New();

  43:  colorTable->SetRange(0, 1000);

  44:  colorTable->SetValueRange(0.0, 1.0);

  45:  colorTable->SetSaturationRange(0.0, 0.0);

  46:  colorTable->SetRampToLinear();

  47:  colorTable->Build();

  48:   

  49:  vtkSmartPointer<vtkImageMapToColors> colorMap =

  50:     vtkSmartPointer<vtkImageMapToColors>::New();

  51:  colorMap->SetLookupTable(colorTable);

  52:  colorMap->SetInputConnection(reslice->GetOutputPort());

  53:   

  54:  vtkSmartPointer<vtkImageActor> imgActor =

  55:     vtkSmartPointer<vtkImageActor>::New();

  56:  imgActor->SetInput(colorMap->GetOutput());

  57:   

  58:  vtkSmartPointer<vtkRenderer> renderer =

  59:     vtkSmartPointer<vtkRenderer>::New();

  60:  renderer->AddActor(imgActor);

  61:  renderer->SetBackground(.4, .5, .6);

  62:   

  63:  vtkSmartPointer<vtkRenderWindow> renderWindow =

  64:     vtkSmartPointer<vtkRenderWindow>::New();

  65:  renderWindow->SetSize(500, 500);

  66:  renderWindow->AddRenderer(renderer);

  67:   

  68:  vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =

  69:     vtkSmartPointer<vtkRenderWindowInteractor>::New();

  70:  vtkSmartPointer<vtkInteractorStyleImage> imagestyle =

  71:     vtkSmartPointer<vtkInteractorStyleImage>::New();

  72:   

  73:  renderWindowInteractor->SetInteractorStyle(imagestyle);

  74:  renderWindowInteractor->SetRenderWindow(renderWindow);

  75:  renderWindowInteractor->Initialize();

  76:   

  77:  renderWindowInteractor->Start();

 

首先通过vtkMetaImageReader读取一副医学三维图像,并获取得到图像范围(extent),原点和像素间隔;由这三个参数可以计算图像的中心位置center;接下来定义了切面的变换矩阵axialElements,该矩阵的前三列分别表示x、y和z方向向量,第四列为中心点坐标;代码中的axialElements表示切面变换矩阵与当前坐标系一致,且切面为过中心点center,并平行于XY平面的平面。当前,定义该切面时,也可以是其他平面,甚至是任意平面,但是必须要过图像内部点。下面给出了一个常用的变换矩阵:

 

static double coronalElements[16] = {

 1, 0, 0, 0,

 0, 0, 1, 0,

0,-1, 0, 0,

 0, 0, 0, 1 }; 提取平行于XZ平面的切片

 

static double sagittalElements[16] = {

 0, 0,-1, 0,

 1, 0, 0, 0,

 0,-1, 0, 0,

 0, 0, 0, 1 }; 提取平行于YZ平面的切片

 

static double obliqueElements[16] = {

 1, 0, 0, 0,

 0, 0.866025, -0.5, 0,

 0, 0.5, 0.866025, 0,

 0, 0, 0, 1 }; 提取斜切切片

 

注意使用这些变换矩阵的时候,需要将第四列替换为切片经过图像的一个点坐标,上例中将图像的中心添加到axialElements矩阵,并通过函数SetResliceAxes设置变换矩阵,SetOutputDimensionality(2)指定输出的图像为一个二维图像;而函数SetInterpolationModeToLinear()则指定了切面提取中的差值方式为线性差值,另外该类中还提供了其他的差值方式:

SetInterpolationModeToNearestNeighbor():最近邻方式

SetInterpolationModeToCubic():三次线性差值

设置完毕后,执行Update()即可完成切面计算。运行结果如下图:

 

图5.19 切片提取

5.7.3 扩展

学习三维图像切面的提取后,我们在上节的程序上做一个扩展,实现一个稍微复杂的程序——通过滑动鼠标来切换三维图像切片,这也是医学图像处理软件中一个很基本的功能。实现该功能难点是怎样在VTK中控制鼠标来实时提取图像切片。在前面的章节中已经介绍观察者/命令(Observer/Command)模式,我们也采用这种机制来实现。VTK中鼠标消息是在交互类型对象(interactorstyle)中响应,因此通过为交互类型对象(interactorstyle)添加观察者(observer)来监听相应的消息,当消息触发时,由命令模式执行相应的回调函数。闲话少说,放代码。

  1:  class vtkImageInteractionCallback : public vtkCommand

   2:  {

   3:  public:

   4:   

   5:      static vtkImageInteractionCallback *New()

   6:      {

   7:          return new vtkImageInteractionCallback;

   8:      }

   9:   

  10:      vtkImageInteractionCallback()

  11:      {

  12:          this->Slicing = 0;

  13:          this->ImageReslice = 0;

  14:          this->Interactor = 0;

  15:      }

  16:   

  17:      void SetImageReslice(vtkImageReslice *reslice)

  18:      {

  19:          this->ImageReslice = reslice;

  20:      }

  21:   

  22:      vtkImageReslice *GetImageReslice()

  23:      {

  24:          return this->ImageReslice;

  25:      }

  26:   

  27:      void SetInteractor(vtkRenderWindowInteractor *interactor)

  28:      {

  29:          this->Interactor = interactor;

  30:      }

  31:   

  32:      vtkRenderWindowInteractor *GetInteractor()

  33:      {

  34:          return this->Interactor;

  35:      }

  36:   

  37:      virtual void Execute(vtkObject *, unsigned long event, void *)

  38:      {

  39:          vtkRenderWindowInteractor *interactor = this->GetInteractor();

  40:   

  41:          int lastPos[2];

  42:          interactor->GetLastEventPosition(lastPos);

  43:          int currPos[2];

  44:          interactor->GetEventPosition(currPos);

  45:   

  46:          if (event == vtkCommand::LeftButtonPressEvent)

  47:          {

  48:              this->Slicing = 1;

  49:          }

  50:          else if (event == vtkCommand::LeftButtonReleaseEvent)

  51:          {

  52:              this->Slicing = 0;

  53:          }

  54:          else if (event == vtkCommand::MouseMoveEvent)

  55:          {

  56:              if (this->Slicing)

  57:              {

  58:                  vtkImageReslice *reslice = this->ImageReslice;

  59:   

  60:                  // Increment slice position by deltaY of mouse

  61:                  int deltaY = lastPos[1] - currPos[1];

  62:   

  63:                  reslice->Update();

  64:                  double sliceSpacing = reslice->GetOutput()->GetSpacing()[2];

  65:                  vtkMatrix4x4 *matrix = reslice->GetResliceAxes();

  66:                  // move the center point that we are slicing through

  67:                  double point[4];

  68:                  double center[4];

  69:                  point[0] = 0.0;

  70:                  point[1] = 0.0;

  71:                  point[2] = sliceSpacing * deltaY;

  72:                  point[3] = 1.0;

  73:                  matrix->MultiplyPoint(point, center);

  74:                  matrix->SetElement(0, 3, center[0]);

  75:                  matrix->SetElement(1, 3, center[1]);

  76:                  matrix->SetElement(2, 3, center[2]);

  77:                  interactor->Render();

  78:              }

  79:              else

  80:              {

  81:                  vtkInteractorStyle *style = vtkInteractorStyle::SafeDownCast(

  82:                      interactor->GetInteractorStyle());

  83:                  if (style)

  84:                  {

  85:                      style->OnMouseMove();

  86:                  }

  87:              }

  88:          }

  89:      }

  90:   

  91:  private:

  92:      int Slicing;

  93:      vtkImageReslice *ImageReslice;

  94:      vtkRenderWindowInteractor *Interactor;

  95:  };

 

vtkImageInteractionCallback继承自vtkCommand类,并覆盖父类函数Execute()。该类提供了两个接口:SetImageReslice和SetInteractor。SetImageReslice用以设置vtkImageSlice对象,vtkImageSlice根据设置的变换矩阵提取三维图像切片。SetInteractor用以设置vtkRenderWindowInteractor,vtkRenderWindowInteractor类对象负责每次提取切片后刷新视图。

下面我们重点来看一下Execute函数,该函数提供了具体的切片提取功能。在该函数里面,主要监听了三个消息:

vtkCommand::LeftButtonPressEvent,

vtkCommand::LeftButtonReleaseEvent,

vtkCommand::MouseMoveEvent,

前两个消息分别是鼠标左键的按下和弹起消息。当鼠标左键按下时,就设置切片提取标志为1,而当弹起时,将标志置为0。这样在鼠标移动时,只有在确定切片提取标志为1时,执行切片提取功能。

vtkCommand::MouseMoveEvent即为鼠标移动消息。当检测到该消息时,首先检查切片提取标志,当为1时提取切片。提取切片时,需要为vtkImageSlice对象设置变换矩阵。这里在函数开始时,首先获取了鼠标滑动的前后两次点的位置lastPos和currPos。然后根据两点的Y坐标差deltaY,计算新的中心点center并变换至vtkImageSlice当前变换矩阵中,得到变换中心点,将其设置到原来的变换矩阵matrix中,并设置到vtkImageSlice中,最后执行interactor->Render()即可不断的根据鼠标移动刷新图像。

Command对象定义完毕后,即可为交互对象InteractorStyle添加观察者,响应鼠标消息。这里可以在上节的程序上进行修改,前面代码一致,只需要在最后添加如下代码:

1:      vtkSmartPointer<vtkImageInteractionCallback> callback =

   2:          vtkSmartPointer<vtkImageInteractionCallback>::New();

   3:      callback->SetImageReslice(reslice);

   4:      callback->SetInteractor(renderWindowInteractor);

   5:  

   6:      imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);

   7:      imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);

   8:      imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);

   9:  

  10:      renderWindowInteractor->Start();

这里主要是定义了vtkImageInteractionCallback对象,并设置vtkImageSlice对象和vtkRenderWindowInteractor对象。然后为交互对象vtkInteractorStyle添加观察者来监控相应的消息,这里主要是三个消息:

vtkCommand::LeftButtonPressEvent,

vtkCommand::LeftButtonReleaseEvent,

vtkCommand::MouseMoveEvent,

当响应到这三个消息时,立即执行vtkImageInteractionCallback的Execute函数,以便实现切片的实时提取和更新。完成以后,运行程序,当鼠标在图像上移动时,会发现图像会跟着鼠标的移动而变化,神奇吧?有兴趣的话,还可以实现YZ平面、XZ平面切片提取,甚至是任意方向的切面提取。

 

==========欢迎转载,转载时请保留该声明信息==========

版权归@东灵工作室所有,更多信息请访问东灵工作室




posted @ 2013-10-24 13:31  wishchin  阅读(34019)  评论(2编辑  收藏  举报