这一篇博文主要介绍一个QT5.12.3联合VTK在VS2022上的小项目。单独学了这么久VTK和Qt,终于能步入正题琢磨着把这俩合在一起实现我想做的东西了o(TヘTo)。在网上找了很久,本来可供参考的就不多,好多因为版本啊各种报错,就很炸毛。所以啊,希望我这个版本能给和我有同样困扰的朋友们一些帮助吧。
在这篇博文(6.VTK二维三维图像显示以及二维三维图像切面交互提取(回调函数、观察者-命令模式)-CSDN博客)里,我利用vtkImageViewer2类实现了图像的显示和交互,同时我采用观察者/命令(Observer/Command)模式机制来实现鼠标滑动控制图像切片的转换。现在,我要做的就是用QT做成的界面来显示医学图像。在博文(VS2022联合Qt5开发学习1(信号和槽)-CSDN博客)中我已经详细介绍了如何在VS2022上新建Qt项目以及生成槽函数。这些步骤很重要,后面我们会用到,不熟悉的朋友可以先看看这篇博文。
1.Qt项目显示.mhd、.stl图像
步骤一:新建一个Qt Widgets Application项目打开UI界面。双击QtWidgetsApplication1.ui文件,参照下图添加控件。
这里需要特别注意的是QVTKWidget这个控件。如果你用的是VTK9以前的版本(我这里举例子用的是VTK7),那么你需要手动将VTK中编译(必须是Release)生成的QVTKWidgetPlugin.dll和QVTKWidgetPlugin.lib复制到Qt安装目录中的plugins\designer下。复制完成后,Qt Designer界面如下图所示:
如果是VTK9的版本,可以使用QVTKOpenGLNativeWidget来代替旧版本的QVTKWidget。新版本不再像QVTKWidget以前那样有所谓的Plugin.dll和Plugin.lib了。
QVTKOpenGLNativeWidget怎么在Qt使用起来:
打开Qt设计师界面,先手动拖入一个普通的Widget控件,然后再点击右键提升这个窗口部件
提升之后:
步骤二:为pushButton控件增加槽函数(不清楚这一步怎么操作的,看我最前面提到的博文)
步骤三:回到VS窗口,在VS解决方案中选中方案,右键->Qt->Refresh intelliSense进行刷新。在VS解决方案中右键 .ui 文件 -> "编译",会生成对应的 ui_XX.h 头文件。
步骤四:
打开STLshowtest_vtk7.h、STLshowtest_vtk7.cpp两个文件,添加相关功能。
.h文件中,添加private slots:标明信号类函数。
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_STLshowtest_vtk7.h"
#include <vtkAutoInit.h>
#include <vtkBMPReader.h>
#include <vtkFloatArray.h>
#include <vtkImageActor.h>
#include <vtkImageChangeInformation.h>
#include <vtkImageData.h>
#include <vtkImageImport.h>
#include <vtkImageViewer2.h>
#include <vtkInteractorStyleImage.h>
#include <vtkJPEGReader.h>
#include <vtkLookupTable.h>
#include <vtkMetaImageReader.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPointData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSTLReader.h>
#include <vtkSmartPointer.h>
#include <vtkXMLImageDataWriter.h>
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
class STLshowtest_vtk7 : public QMainWindow
{
Q_OBJECT
public:
STLshowtest_vtk7(QWidget *parent = nullptr);
~STLshowtest_vtk7();
private slots:
void openFileSlot();
private:
Ui::STLshowtest_vtk7Class ui;
};
.cpp文件中添加具体的功能。
#include "STLshowtest_vtk7.h"
#include <QDebug>
#include <QFileDialog>
#include <vtkImageBlend.h>
#include <vtkImageCanvasSource2D.h>
#include <vtkImageChangeInformation.h>
#include <vtkImageData.h>
#include <vtkImageIterator.h>
#include <vtkImageLuminance.h>
#include <vtkImageStencil.h>
#include <vtkImageStencilData.h>
#include <vtkImageViewer.h>
#include <vtkInteractorStyleImage.h>
#include <vtkPNGReader.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
STLshowtest_vtk7::STLshowtest_vtk7(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
}
STLshowtest_vtk7::~STLshowtest_vtk7()
{}
void STLshowtest_vtk7::openFileSlot()
{
QString selectFilePath = QFileDialog::getOpenFileName(this, QString("choose STL file"), QString(""), QString("file(*.stl)"));
if (selectFilePath.isEmpty())
{
ui.textBrowser->append("The address of the STL file you choose is null!");
return;
}
// 原始图像
vtkSmartPointer<vtkSTLReader> reader = vtkSmartPointer<vtkSTLReader>::New();
reader->SetFileName(selectFilePath.toStdString().c_str());
reader->Update();
//将source转换成mapper
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(reader->GetOutputPort());
//送入渲染引擎进行显示
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
//渲染
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground(.0, .0, .0);
//设置渲染窗口
vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New();
ui.qvtkWidget->SetRenderWindow(window);
ui.qvtkWidget->GetRenderWindow()->AddRenderer(renderer); //等价于window->AddRenderer(renderer);
//ok
ui.textBrowser->append(QString("upload the file:") + selectFilePath + QString(" succeed !"));
}
main函数没什么变化
#include "STLshowtest_vtk7.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
STLshowtest_vtk7 w;
w.show();
return a.exec();
}
运行结果
Qt项目显示.stl图像
对于VTK9版本,其他代码部分不变,在渲染窗口部分,我们使用vtkGenericOpenGLRenderWindow:
//设置渲染窗口
vtkSmartPointer<vtkGenericOpenGLRenderWindow> window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
ui.qvtkWidget->setRenderWindow(window);
ui.qvtkWidget->renderWindow()->AddRenderer(renderer); //等价于window->AddRenderer(renderer);
哦对了,最前面vtkGenericOpenGLRenderWindow.h的头文件别忘了加上。
对于.mhd文件的显示,只需要修改这里:
QString selectFilePath = QFileDialog::getOpenFileName(this, QString("choose STL file"), QString(""), QString("file(*.stl)"));
改为
QString selectFilePath = QFileDialog::getOpenFileName(this, QString("choose MHD file"), QString(""), QString("file(*.mhd)"));
vtkSmartPointer<vtkSTLReader> reader = vtkSmartPointer<vtkSTLReader>::New();
改为
vtkSmartPointer<vtkMetaImageReader> reader = vtkSmartPointer<vtkMetaImageReader>::New();
切记,.raw文件一定要和.mhd文件放在同一个路径,不然会出现找不到文件,无法读取图片的状况。
2.三维图像切片交互提取(回调函数、观察者-命令模式)
接下来是一个稍微复杂的程序——通过滑动鼠标来切换三维图像切片。这个的纯VTK版本我已经在博文(6.VTK二维三维图像显示以及二维三维图像切面交互提取(回调函数、观察者-命令模式)-CSDN博客)里介绍了,一些需要注意的点我也已经在里面提到了,这里不再重复。这里主要叙述的是Qt的构建部分。(这里就以VTK7为例子了,VTK9版本只需要参考我上一个例子修改即可。)
步骤一:新建一个Qt Widgets Application项目打开UI界面。双击QtWidgetsApplication1.ui文件,参照下图添加控件。
步骤二:为pushButton控件增加槽函数(不清楚这一步怎么操作的,看我最前面提到的博文)
步骤三:回到VS窗口,在VS解决方案中选中方案,右键->Qt->Refresh intelliSense进行刷新。在VS解决方案中右键 .ui 文件 -> "编译",会生成对应的 ui_XX.h 头文件。
步骤四:打开MHDshow.h、MHDshow.cpp两个文件,添加相关功能。
.h文件中,添加private slots:标明信号类函数。添加vtkImageInteractionCallback类,定义鼠标控制的相关函数。
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_MHDshow.h"
#include <vtkAutoInit.h>
#include <vtkBMPReader.h>
#include <vtkFloatArray.h>
#include <vtkImageActor.h>
#include <vtkImageChangeInformation.h>
#include <vtkImageData.h>
#include <vtkImageImport.h>
#include <vtkImageViewer2.h>
#include <vtkInteractorStyleImage.h>
#include <vtkJPEGReader.h>
#include <vtkLookupTable.h>
#include <vtkMetaImageReader.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPointData.h>
#include <vtkMatrix4x4.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkImageMapToColors.h>
#include <vtkRenderer.h>
#include <vtkSTLReader.h>
#include <vtkSmartPointer.h>
#include <vtkXMLImageDataWriter.h>
#include <vtkImageReslice.h>
#include <vtkCommand.h> //建立“观察者/命令”模式监听鼠标消息 完成交互
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
class MHDshow : public QMainWindow
{
Q_OBJECT
public:
MHDshow(QWidget *parent = nullptr);
~MHDshow();
private slots:
void openFileSlot();
private:
Ui::MHDshowClass ui;
};
class vtkImageInteractionCallback : public vtkCommand {
public:
static vtkImageInteractionCallback* New() //回调函数初始化函数
{
return new vtkImageInteractionCallback;
}
vtkImageInteractionCallback()
{
this->Slicing = 0;
this->ImageReslice = 0;
this->Interactor = 0;
}
void SetImageReslice(vtkImageReslice* reslice)
{
this->ImageReslice = reslice;
}
vtkImageReslice* GetImageReslice()
{
return this->ImageReslice;
}
void SetImageResliceColorMap(vtkImageMapToColors* colorMap)
{
this->colorMap = colorMap;
}
vtkImageMapToColors* GetImageResliceColorMap()
{
return this->colorMap;
}
void SetInteractor(vtkRenderWindowInteractor* interactor)
{
this->Interactor = interactor;
}
vtkRenderWindowInteractor* GetInteractor()
{
return this->Interactor;
}
virtual void Execute(vtkObject*, unsigned long event, void*)
{
vtkRenderWindowInteractor* interactor = GetInteractor();
int lastPos[2];
interactor->GetLastEventPosition(lastPos);
int currPos[2];
interactor->GetEventPosition(currPos);
if (event == vtkCommand::LeftButtonPressEvent)
{
this->Slicing = 1; //标志位
}
else if (event == vtkCommand::LeftButtonReleaseEvent)
{
this->Slicing = 0; //标志位
}
else if (event == vtkCommand::MouseMoveEvent)
{
if (this->Slicing)//检验鼠标左键已经按下 正在执行操作
{
std::cout << "LeftButton has been pressed: " << std::endl;
vtkImageReslice* reslice = this->ImageReslice;
//记下鼠标Y向变化的幅值大小
int deltaY = lastPos[1] - currPos[1];
std::cout << "鼠标Y向变化的幅值大小: " << deltaY << std::endl;
//记下鼠标X向变化的幅值大小
int deltaX = lastPos[0] - currPos[0];
std::cout << "鼠标X向变化的幅值大小: " << deltaX << std::endl;
reslice->Update();
double sliceSpacing = reslice->GetOutput()->GetSpacing()[2];
vtkMatrix4x4* matrix = reslice->GetResliceAxes();
//重新定位切片需要经过的中心点
double point[4];
double center[4];
point[0] = 0;
point[1] = 0;
point[2] = sliceSpacing * deltaY;
point[3] = 1.0;
matrix->MultiplyPoint(point, center);
//***********理解MultiplyPoint center4*1 = matrix4*4 乘以 point4*1
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
std::cout << matrix->GetElement(i, j) << "\t";
}
std::cout << "\n";
}
std::cout << "\n";
std::cout << point[0] << "\t" << point[1] << "\t" << point[2] << "\t" << point[3] << "\n";
std::cout << center[0] << "\t" << center[1] << "\t" << center[2] << "\t" << center[3] << "\n";
//******************
matrix->SetElement(0, 3, center[0]);
matrix->SetElement(1, 3, center[1]);
matrix->SetElement(2, 3, center[2]);
static double colorWindowLevel = 500;
static double colorWindowWith = 2000;
colorWindowLevel += deltaX;
colorMap->GetLookupTable()->SetRange(colorWindowLevel - colorWindowWith / 2, colorWindowLevel + colorWindowWith / 2);
reslice->Update();
colorMap->Update();
interactor->Render();
}
else
{
vtkInteractorStyle* style = vtkInteractorStyle::SafeDownCast(
interactor->GetInteractorStyle());
if (style)
{
style->OnMouseMove();
}
}
}
}
private:
int Slicing;
vtkImageReslice* ImageReslice;
vtkImageMapToColors* colorMap;
vtkRenderWindowInteractor* Interactor;
};
.cpp文件中添加具体的功能。
#include "MHDshow.h"
#include <QDebug>
#include <QFileDialog>
#include <vtkImageBlend.h>
#include <vtkImageCanvasSource2D.h>
#include <vtkImageChangeInformation.h>
#include <vtkImageData.h>
#include <vtkImageIterator.h>
#include <vtkImageLuminance.h>
#include <vtkImageStencil.h>
#include <vtkImageStencilData.h>
#include <vtkImageViewer.h>
#include <vtkInteractorStyleImage.h>
#include <vtkPNGReader.h>
#include <vtkMetaImageReader.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkSmartPointer.h>
#include <vtkMatrix4x4.h> //
#include <vtkImageReslice.h>
#include <vtkLookupTable.h>
#include <vtkImageMapToColors.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkCommand.h> //建立“观察者/命令”模式监听鼠标消息 完成交互
MHDshow::MHDshow(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
}
MHDshow::~MHDshow()
{}
void MHDshow::openFileSlot()
{
QString selectFilePath = QFileDialog::getOpenFileName(this, QString("choose mhd file"), QString(""), QString("file(*.mhd)"));
if (selectFilePath.isEmpty())
{
ui.textBrowser->append("The address of the STL file you choose is null!");
return;
}
// 原始图像
vtkSmartPointer<vtkMetaImageReader> reader = vtkSmartPointer<vtkMetaImageReader>::New();
reader->SetFileName(selectFilePath.toStdString().c_str());
reader->Update();
int extent[6];
double spacing[3];
double origin[3];
reader->GetOutput()->GetExtent(extent);
reader->GetOutput()->GetSpacing(spacing);
reader->GetOutput()->GetOrigin(origin);
double center[3];
center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);
//*****************************************************************//
static double axialElements[16] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
vtkSmartPointer<vtkMatrix4x4> resliceAxes =
vtkSmartPointer<vtkMatrix4x4>::New();
resliceAxes->DeepCopy(axialElements);
resliceAxes->SetElement(0, 3, center[0]);
resliceAxes->SetElement(1, 3, center[1]);
resliceAxes->SetElement(2, 3, center[2]);
vtkSmartPointer<vtkImageReslice> reslice =
vtkSmartPointer<vtkImageReslice>::New();
reslice->SetInputConnection(reader->GetOutputPort());
reslice->SetOutputDimensionality(2);
reslice->SetResliceAxes(resliceAxes);
reslice->SetInterpolationModeToLinear();
reslice->Update();
//*****************************************************************//
vtkSmartPointer<vtkLookupTable> colorTable =
vtkSmartPointer<vtkLookupTable>::New();
colorTable->SetRange(0, 1000);
colorTable->SetValueRange(0.0, 1.0);
colorTable->SetSaturationRange(0.0, 0.0);
colorTable->SetRampToLinear();
colorTable->Build();
vtkSmartPointer<vtkImageMapToColors> colorMap =
vtkSmartPointer<vtkImageMapToColors>::New();
colorMap->SetLookupTable(colorTable);
colorMap->SetInputConnection(reslice->GetOutputPort());
colorMap->Update();
//将source转换成mapper
//vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
//mapper->SetInputConnection(reader->GetOutputPort());
//送入渲染引擎进行显示
//vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
//actor->SetMapper(mapper);
vtkSmartPointer<vtkImageActor> actor =
vtkSmartPointer<vtkImageActor>::New();
actor->SetInputData(colorMap->GetOutput());
//渲染
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground(1.0, 1.0, 1.0);
//设置渲染窗口
vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New();
ui.qvtkWidget->SetRenderWindow(window);
ui.qvtkWidget->GetRenderWindow()->AddRenderer(renderer); //等价于window->AddRenderer(renderer);
//ok
ui.textBrowser->append(QString("upload the file:") + selectFilePath + QString(" succeed !"));
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> imagestyle =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(imagestyle);
rwi->SetRenderWindow(window);
rwi->Initialize();
//****************建立 观察者-命令 模式****************//
vtkSmartPointer<vtkImageInteractionCallback> callback =
vtkSmartPointer<vtkImageInteractionCallback>::New();
callback->SetImageReslice(reslice);
callback->SetInteractor(rwi);
callback->SetImageResliceColorMap(colorMap);
imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);
rwi->Start();
}
运行结果
三维图像切片交互提取(回调函数、观察者-命令模式)
其实在医学图像显示项目里,一般用的多的是四个窗口显示:矢状面、冠状面和横断面三个特殊切面的三个单独窗口显示,以及一个整体的图的窗口显示。这个项目留到下一个博文里在写吧。。。