VS2022联合Qt5开发学习5(QT5.12.3联合VTK在VS2022上开发医学图像项目)

这一篇博文主要介绍一个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();
}

运行结果

三维图像切片交互提取(回调函数、观察者-命令模式) 

其实在医学图像显示项目里,一般用的多的是四个窗口显示:矢状面、冠状面和横断面三个特殊切面的三个单独窗口显示,以及一个整体的图的窗口显示。这个项目留到下一个博文里在写吧。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值