六、QT基于VTK的体渲染程序

一、功能说明

       本程序主要功能在于能够通过VTK读取数据,然后通过QT的ui界面显示出VTK的体渲染结果

       版本:VTK 8.2.0

                   QT: 5.13.1

                   VS2019

二、思路

       VTK的体渲染程序主要是基于:  VTK体渲染程序

        QT界面布局以及程序的构建参考这篇博客:VTK读取图片并通过QT显示

三、工程建立

       1、参考VTK读取图片并通过QT显示这篇博客构建工程

        ui界面如下:

        

 

 

        我们把几个控件分别拖动到图中的位置然后保存,编译,在VS界面选择项目-重新扫描解决方案,这样就可以在Cpp里面使用控件

    2-添加程序

        QTVTKDisplay.cpp

#include "QTVTKDisplay.h"


QTVTKDisplay::QTVTKDisplay(QWidget *parent)
    : QMainWindow(parent)
{
    //消除vtk版本警告
    vtkOutputWindow::SetGlobalWarningDisplay(0);
    ui.setupUi(this);
    //初始化renderer
    renderer = vtkSmartPointer<vtkRenderer>::New();
    //设置qvtk的渲染器
    ui.qvtkWidget->GetRenderWindow()->AddRenderer(renderer);
    //连接信号和槽
    connect(ui.pushButton, SIGNAL(clicked(bool)), this, SLOT(openDicomImage()));
}
void QTVTKDisplay::openDicomImage(void)
{
    

    /*-----------------------打开文件---------------------------*/
    QString OpenFile, OpenFilePath;
    OpenFile = QFileDialog::getOpenFileName(this,
        "please choose an image file",
        "",
        "Image Files (*.jpg *.png *.bmp *pbm);;All(*.*)");
    if (OpenFile != "")
    {
        QDir InputDir;
        QFileInfo OpenFileInfo;
        OpenFileInfo = QFileInfo(OpenFile);
        
        InputDir = OpenFileInfo.absoluteDir();
        OpenFilePath = InputDir.absolutePath();
        ui.lineEdit->setText(OpenFilePath);
    }
    //把路径传入到vtk里面处理
    //把Qsting类型转换为string类型
    string inputPath_str = OpenFilePath.toStdString();
    //DisplyRenderingImage((char*)inputPath_str.data());

    /*------------------vtk部分程序---------------------------*/
    //参数初始化
    int count = 1;
    char* dirname = nullptr;
    double opacityWindow = 4096;
    double opacityLevel = 2048;
    int blendType = 0;
    int clip = 0;
    double reductionFactor = 1.0;
    double frameRate = 10.0;
    char* fileName = nullptr;
    int fileType = 0;

    bool independentComponents = true;
    //获得路径
    size_t size = strlen((char*)inputPath_str.data()) + 1;
    dirname = new char[size];
    int num_personInputFile = 0;
    num_personInputFile = snprintf(dirname, size, "%s", (char*)inputPath_str.data());
    if (!dirname && !fileName)
    {
        cout << "Error: you must specify a directory of DICOM data or a .vti file or a .mha!" << endl;
        cout << endl;

        exit(EXIT_FAILURE);
    }
    //把所有的renWin注释掉
    //vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();
    //renWin->AddRenderer(renderer);

    // Connect it all. Note that funny arithematic on the
    // SetDesiredUpdateRate - the vtkRenderWindow divides it
    // allocated time across all renderers, and the renderer
    // divides it time across all props. If clip is
    // true then there are two props
    
    //comment 设置交互器为qvtkWidget
    //vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New();
    vtkSmartPointer<vtkRenderWindowInteractor> iren = ui.qvtkWidget->GetRenderWindow()->GetInteractor();
    //comment 设置qvtk交互器的交互窗口
    //iren->SetRenderWindow(renWin);
    iren->SetRenderWindow(ui.qvtkWidget->GetRenderWindow());
    iren->SetDesiredUpdateRate(frameRate / (1 + clip));

    iren->GetInteractorStyle()->SetDefaultRenderer(renderer);

    // Read the data
    vtkSmartPointer<vtkAlgorithm> reader = nullptr;
    vtkImageData* input = nullptr;


    if (dirname)
    {

        vtkDICOMImageReader* dicomReader = vtkDICOMImageReader::New();
        dicomReader->SetDirectoryName(dirname);
        dicomReader->Update();
        input = dicomReader->GetOutput();
        reader = dicomReader;
    }
    else if (fileType == VTI_FILETYPE)
    {
        vtkXMLImageDataReader* xmlReader = vtkXMLImageDataReader::New();
        xmlReader->SetFileName(fileName);
        xmlReader->Update();
        input = xmlReader->GetOutput();
        reader = xmlReader;
    }
    else if (fileType == MHA_FILETYPE)
    {
        vtkMetaImageReader* metaReader = vtkMetaImageReader::New();
        metaReader->SetFileName(fileName);
        metaReader->Update();
        input = metaReader->GetOutput();
        reader = metaReader;
    }
    else
    {
        cout << "Error! Not VTI or MHA!" << endl;
        exit(EXIT_FAILURE);
    }

    // Verify that we actually have a volume
    int dim[3];
    input->GetDimensions(dim);
    if (dim[0] < 2 ||
        dim[1] < 2 ||
        dim[2] < 2)
    {
        cout << "Error loading data!" << endl;
        exit(EXIT_FAILURE);
    }

    vtkSmartPointer<vtkImageResample> resample = vtkSmartPointer< vtkImageResample>::New();
    if (reductionFactor < 1.0)
    {
        resample->SetInputConnection(reader->GetOutputPort());
        resample->SetAxisMagnificationFactor(0, reductionFactor);
        resample->SetAxisMagnificationFactor(1, reductionFactor);
        resample->SetAxisMagnificationFactor(2, reductionFactor);
    }

    // Create our volume and mapper
    vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
    vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> mapper = vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New();
    
    
    if (reductionFactor < 1.0)
    {
        mapper->SetInputConnection(resample->GetOutputPort());
    }
    else
    {
        mapper->SetInputConnection(reader->GetOutputPort());
    }


    // Set the sample distance on the ray to be 1/2 the average spacing
    double spacing[3];
    if (reductionFactor < 1.0)
    {
        resample->GetOutput()->GetSpacing(spacing);
    }
    else
    {
        input->GetSpacing(spacing);
    }

    //  mapper->SetSampleDistance( (spacing[0]+spacing[1]+spacing[2])/6.0 );
    //  mapper->SetMaximumImageSampleDistance(10.0);


      // Create our transfer function
    vtkSmartPointer<vtkColorTransferFunction> colorFun = vtkSmartPointer<vtkColorTransferFunction>::New();
    vtkSmartPointer<vtkPiecewiseFunction> opacityFun = vtkSmartPointer< vtkPiecewiseFunction>::New();

    // Create the property and attach the transfer functions
    vtkSmartPointer<vtkVolumeProperty> property = vtkSmartPointer<vtkVolumeProperty>::New();
    property->SetIndependentComponents(independentComponents);
    property->SetColor(colorFun);
    property->SetScalarOpacity(opacityFun);
    property->SetInterpolationTypeToLinear();

    // connect up the volume to the property and the mapper
    volume->SetProperty(property);
    volume->SetMapper(mapper);

    // Depending on the blend type selected as a command line option,
    // adjust the transfer function
    switch (blendType)
    {
        // MIP
        // Create an opacity ramp from the window and level values.
        // Color is white. Blending is MIP.
    case 0:
        colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0);
        opacityFun->AddSegment(opacityLevel - 0.5 * opacityWindow, 0.0,
            opacityLevel + 0.5 * opacityWindow, 1.0);
        mapper->SetBlendModeToMaximumIntensity();
        break;

        // CompositeRamp
        // Create a ramp from the window and level values. Use compositing
        // without shading. Color is a ramp from black to white.
    case 1:
        colorFun->AddRGBSegment(opacityLevel - 0.5 * opacityWindow, 0.0, 0.0, 0.0,
            opacityLevel + 0.5 * opacityWindow, 1.0, 1.0, 1.0);
        opacityFun->AddSegment(opacityLevel - 0.5 * opacityWindow, 0.0,
            opacityLevel + 0.5 * opacityWindow, 1.0);
        mapper->SetBlendModeToComposite();
        property->ShadeOff();
        break;

        // CompositeShadeRamp
        // Create a ramp from the window and level values. Use compositing
        // with shading. Color is white.
    case 2:
        colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0);
        opacityFun->AddSegment(opacityLevel - 0.5 * opacityWindow, 0.0,
            opacityLevel + 0.5 * opacityWindow, 1.0);
        mapper->SetBlendModeToComposite();
        property->ShadeOn();
        break;

        // CT_Skin
        // Use compositing and functions set to highlight skin in CT data
        // Not for use on RGB data
    case 3:
        colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0);
        colorFun->AddRGBPoint(-1000, .62, .36, .18, 0.5, 0.0);
        colorFun->AddRGBPoint(-500, .88, .60, .29, 0.33, 0.45);
        colorFun->AddRGBPoint(3071, .83, .66, 1, 0.5, 0.0);

        opacityFun->AddPoint(-3024, 0, 0.5, 0.0);
        opacityFun->AddPoint(-1000, 0, 0.5, 0.0);
        opacityFun->AddPoint(-500, 1.0, 0.33, 0.45);
        opacityFun->AddPoint(3071, 1.0, 0.5, 0.0);

        mapper->SetBlendModeToComposite();
        property->ShadeOn();
        property->SetAmbient(0.1);
        property->SetDiffuse(0.9);
        property->SetSpecular(0.2);
        property->SetSpecularPower(10.0);
        property->SetScalarOpacityUnitDistance(0.8919);
        break;

        // CT_Bone
        // Use compositing and functions set to highlight bone in CT data
        // Not for use on RGB data
    case 4:
        colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0);
        colorFun->AddRGBPoint(-16, 0.73, 0.25, 0.30, 0.49, .61);
        colorFun->AddRGBPoint(641, .90, .82, .56, .5, 0.0);
        colorFun->AddRGBPoint(3071, 1, 1, 1, .5, 0.0);

        opacityFun->AddPoint(-3024, 0, 0.5, 0.0);
        opacityFun->AddPoint(-16, 0, .49, .61);
        opacityFun->AddPoint(641, .72, .5, 0.0);
        opacityFun->AddPoint(3071, .71, 0.5, 0.0);

        mapper->SetBlendModeToComposite();
        property->ShadeOn();
        property->SetAmbient(0.1);
        property->SetDiffuse(0.9);
        property->SetSpecular(0.2);
        property->SetSpecularPower(10.0);
        property->SetScalarOpacityUnitDistance(0.8919);
        break;

        // CT_Muscle
        // Use compositing and functions set to highlight muscle in CT data
        // Not for use on RGB data
    case 5:
        colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0);
        colorFun->AddRGBPoint(-155, .55, .25, .15, 0.5, .92);
        colorFun->AddRGBPoint(217, .88, .60, .29, 0.33, 0.45);
        colorFun->AddRGBPoint(420, 1, .94, .95, 0.5, 0.0);
        colorFun->AddRGBPoint(3071, .83, .66, 1, 0.5, 0.0);

        opacityFun->AddPoint(-3024, 0, 0.5, 0.0);
        opacityFun->AddPoint(-155, 0, 0.5, 0.92);
        opacityFun->AddPoint(217, .68, 0.33, 0.45);
        opacityFun->AddPoint(420, .83, 0.5, 0.0);
        opacityFun->AddPoint(3071, .80, 0.5, 0.0);

        mapper->SetBlendModeToComposite();
        property->ShadeOn();
        property->SetAmbient(0.1);
        property->SetDiffuse(0.9);
        property->SetSpecular(0.2);
        property->SetSpecularPower(10.0);
        property->SetScalarOpacityUnitDistance(0.8919);
        break;

        // RGB_Composite
        // Use compositing and functions set to highlight red/green/blue regions
        // in RGB data. Not for use on single component data
    case 6:
        opacityFun->AddPoint(0, 0.0);
        opacityFun->AddPoint(5.0, 0.0);
        opacityFun->AddPoint(30.0, 0.05);
        opacityFun->AddPoint(31.0, 0.0);
        opacityFun->AddPoint(90.0, 0.0);
        opacityFun->AddPoint(100.0, 0.3);
        opacityFun->AddPoint(110.0, 0.0);
        opacityFun->AddPoint(190.0, 0.0);
        opacityFun->AddPoint(200.0, 0.4);
        opacityFun->AddPoint(210.0, 0.0);
        opacityFun->AddPoint(245.0, 0.0);
        opacityFun->AddPoint(255.0, 0.5);

        mapper->SetBlendModeToComposite();
        property->ShadeOff();
        property->SetScalarOpacityUnitDistance(1.0);
        break;
    default:
        vtkGenericWarningMacro("Unknown blend type.");
        break;
    }
    
    //这一部分是触发显示的
    // Set the default window size
    //renWin->SetSize(600, 600);
    ui.qvtkWidget->GetRenderWindow()->Render();

    // Add the volume to the scene
    renderer->AddVolume(volume);

    renderer->ResetCamera();

    // interact with data
    ui.qvtkWidget->GetRenderWindow()->Render();
    

}
View Code

        QTVTKDisplay.hpp

#ifndef _QVTKDISAPLAY_HPP
#define _QVTKDISAPLAY_HPP
#include <QtWidgets/QMainWindow>
#include "ui_QTVTKDisplay.h"
#include <Qlabel>
#include <QLineEdit>
#include <QImage>
#include <QFileInfo>
#include <QFileDialog>
//vtkäÖȾ³ÌÐò
#include "vtkDisplay.h"
class QTVTKDisplay : public QMainWindow
{
    Q_OBJECT
    
public:
    QTVTKDisplay(QWidget *parent = Q_NULLPTR);

private:
    Ui::QTVTKDisplayClass ui;
private:
    vtkSmartPointer<vtkRenderer> renderer;
private slots:
    void openDicomImage(void);
};
#endif
View Code

        再自己添加一个vtkDisplay文件。

#ifndef  VTK_DISPLAY_HPP
#define  VTK_DISPLAY_HPP

#include "vtkBoxWidget.h"
#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkColorTransferFunction.h"
#include "vtkDICOMImageReader.h"
#include "vtkImageData.h"
#include "vtkImageResample.h"
#include "vtkMetaImageReader.h"
#include "vtkPiecewiseFunction.h"
#include "vtkPlanes.h"
#include "vtkProperty.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkVolume.h"
#include "vtkVolumeProperty.h"
#include "vtkXMLImageDataReader.h"
#include "vtkFixedPointVolumeRayCastMapper.h"
#include <vtkOutputWindow.h>
//#include <string>
using  std::string;
#define VTI_FILETYPE 1
#define MHA_FILETYPE 2
//包含头文件comment
//#include "QTVTKDisplay.h"
//为了vtk可以在VS里面顺利编译,自己添加的
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2); // VTK was built with vtkRenderingOpenGL2
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
//消除vtk的版本警告

//函数
//void DisplyRenderingImage(char* inputPath);
#endif // ! VTK_DISPLAY_HPP
View Code

      3-添加属性表

      主要是QT和VTK的属性表,可以参考上一个博客

      4-数据准备

      这里主要读取的是dcm序列图像,所以我们直接选择一个序列图像所在的文件夹,然后点击其中的一个就好,程序内部是获得你所点击的文件的目录,然后读取这个目录,并不是你点击的那个文件。

四、编译运行

     图像如下图所示:

     

 

       点击打开图像,选择dcm序列图像就好。

五、说明

        其实通过通过观察,我发现qvtkweight能够显示图像的原因在于以下代码的设置:

//设置交互器
vtkSmartPointer<vtkRenderWindowInteractor> iren = ui.qvtkWidget->GetRenderWindow()->GetInteractor();
//设置窗口
iren->SetRenderWindow(ui.qvtkWidget->GetRenderWindow());
//设置qvtk的渲染器
    ui.qvtkWidget->GetRenderWindow()->AddRenderer(renderer);
//开始渲染
ui.qvtkWidget->GetRenderWindow()->Render();

        其他的和原来的例子没有什么区别。

        注意原来的程序也是可以读取.mha文件和vti文件的,不过我们需要自己修改了。

 

       

posted @ 2020-03-06 20:44  星光夜  阅读(3587)  评论(2编辑  收藏  举报