天天看點

[VTK][DICOM]修改後的VTK sample code

這周開始調查學習利用VTK渲染DICOM MPR/VR圖像。看到了VTK自帶的例子:

VTK-8.1.0\Examples\GUI\Qt\FourPaneViewer

這個例子用QVTKOpenGLWidget控件顯示四個view,分别顯示相交的MPRA、MPRB、MPRC以及3D slices(三個相交的MPR面)。

下面的UI是我修改後的。

[VTK][DICOM]修改後的VTK sample code

VTK官方UI是類的構造函數直接接收形參過來的Series目錄。我修改後的,可以選擇Series目錄。

另外,在學習使用這個例子的時候積累了一些經驗,與大家分享一下。

1.QVTKOpenGLWidget面向QT5.9和更高版本

最初我的環境為VTK8.1.0+QT5.7.1+VS2013,運作例子,滑鼠在四個View(尤其是右上角的3D slice view)内點選/拖動的時候很容易就崩潰了。調試代碼時提示的錯誤與QT滑鼠事件有關。(抱歉當時忘了截圖,現在環境更新了)

在調查問題時,看到VTK官網的一段話:

https://www.vtk.org/doc/nightly/html/classQVTKOpenGLWidget.html

QVTKOpenGLWidget is targeted for Qt version 5.9 and above.

于是我更新了環境為VTK8.1.0+QT5.11.1+VS2015,再次編譯運作例子,滑鼠在四個View(尤其是右上角的3D slice view)内點選/拖動的時候還是很容易就崩潰了。但提示的錯誤已經不一樣了。

[VTK][DICOM]修改後的VTK sample code

2. 調查上面的問題,text相關的代碼猜測為vtkImagePlaneWidget::DisplayTextOn(),将其改為DisplayTextOff(),然後運作程式,滑鼠在四個View(尤其是右上角的3D slice view)内點選/拖動的時候就不崩潰了。(其實這點還沒太明白)

下面是QtVTKRenderWindows.cxx檔案:

#include "QtVTKRenderWindows.h"

#include "vtkBoundedPlanePointPlacer.h"
#include "vtkDICOMImageReader.h"
#include "vtkDistanceRepresentation.h"
#include "vtkDistanceRepresentation2D.h"
#include "vtkDistanceWidget.h"
#include "vtkHandleRepresentation.h"
#include "vtkImageData.h"
#include "vtkImageMapToWindowLevelColors.h"
#include "vtkImageSlabReslice.h"
#include "vtkInteractorStyleImage.h"
#include "vtkLookupTable.h"
#include "vtkPlane.h"
#include "vtkPlaneSource.h"
#include "vtkPointHandleRepresentation2D.h"
#include "vtkPointHandleRepresentation3D.h"
#include <vtkRenderWindow.h>
#include "vtkRenderWindowInteractor.h"
#include "vtkResliceImageViewer.h"
#include "vtkResliceCursorLineRepresentation.h"
#include "vtkResliceCursorThickLineRepresentation.h"
#include "vtkResliceCursorWidget.h"
#include "vtkResliceCursorActor.h"
#include "vtkResliceCursorPolyDataAlgorithm.h"
#include "vtkResliceCursor.h"
#include "vtkResliceImageViewerMeasurements.h"
#include "vtkCamera.h"

#include <QFileDialog>
#include <QMessageBox>

#include "CommonFunc.h"

//----------------------------------------------------------------------------

void vtkResliceCursorCallback::Execute(
    vtkObject *caller,
    unsigned long ev,
    void *callData
) {
    if (ev == vtkResliceCursorWidget::WindowLevelEvent ||
        ev == vtkCommand::WindowLevelEvent ||
        ev == vtkResliceCursorWidget::ResliceThicknessChangedEvent)
    {
        // Render everything
        for (int i = 0; i < 3; i++)
        {
            this->resliceCursorWidget[i]->Render();
        }
        //view4 update
        this->imagePlabeWidget[0]->GetInteractor()->GetRenderWindow()->Render();
        return;
    }

    vtkImagePlaneWidget* ipw =
        dynamic_cast<vtkImagePlaneWidget*>(caller);
    if (ipw)
    {
        double* wl = static_cast<double*>(callData);

        if (ipw == this->imagePlabeWidget[0])
        {
            this->imagePlabeWidget[1]->SetWindowLevel(wl[0], wl[1], 1);
            this->imagePlabeWidget[2]->SetWindowLevel(wl[0], wl[1], 1);
        }
        else if (ipw == this->imagePlabeWidget[1])
        {
            this->imagePlabeWidget[0]->SetWindowLevel(wl[0], wl[1], 1);
            this->imagePlabeWidget[2]->SetWindowLevel(wl[0], wl[1], 1);
        }
        else if (ipw == this->imagePlabeWidget[2])
        {
            this->imagePlabeWidget[0]->SetWindowLevel(wl[0], wl[1], 1);
            this->imagePlabeWidget[1]->SetWindowLevel(wl[0], wl[1], 1);
        }
    }

    vtkResliceCursorWidget *rcw = dynamic_cast<
        vtkResliceCursorWidget *>(caller);
    if (rcw)
    {
        vtkResliceCursorLineRepresentation *rep = dynamic_cast<
            vtkResliceCursorLineRepresentation *>(rcw->GetRepresentation());
        // Although the return value is not used, we keep the get calls
        // in case they had side-effects
        rep->GetResliceCursorActor()->GetCursorAlgorithm()->GetResliceCursor();
        for (int i = 0; i < 3; i++)
        {
            vtkPlaneSource *ps = static_cast<vtkPlaneSource *>(
                this->imagePlabeWidget[i]->GetPolyDataAlgorithm());
            ps->SetOrigin(this->resliceCursorWidget[i]->GetResliceCursorRepresentation()->
                GetPlaneSource()->GetOrigin());
            ps->SetPoint1(this->resliceCursorWidget[i]->GetResliceCursorRepresentation()->
                GetPlaneSource()->GetPoint1());
            ps->SetPoint2(this->resliceCursorWidget[i]->GetResliceCursorRepresentation()->
                GetPlaneSource()->GetPoint2());

            // If the reslice plane has modified, update it on the 3D widget
            this->imagePlabeWidget[i]->UpdatePlacement();
        }
    }

    // Render everything
    for (int i = 0; i < 3; i++)
    {
        this->resliceCursorWidget[i]->Render();
    }
    this->imagePlabeWidget[0]->GetInteractor()->GetRenderWindow()->Render();
}

QtVTKRenderWindows::QtVTKRenderWindows()
{
    ui = new Ui_QtVTKRenderWindows;
    ui->setupUi(this);

    initVTKUI();
}

void QtVTKRenderWindows::initVTKUI()
{
    ui->thickModeCheckBox->setEnabled(0);
    ui->blendModeGroupBox->setEnabled(0);

    for (int i = 0; i < 3; ++i) {
        mResliceViewer[i] = vtkSmartPointer<vtkResliceImageViewer>::New();
        mResliceRenderWin[i] = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
        mResliceViewer[i]->SetRenderWindow(mResliceRenderWin[i]);
    }

    ui->view1->SetRenderWindow(mResliceRenderWin[0]);
    mResliceViewer[0]->SetupInteractor(mResliceRenderWin[0]->GetInteractor());

    ui->view2->SetRenderWindow(mResliceRenderWin[1]);
    mResliceViewer[1]->SetupInteractor(mResliceRenderWin[1]->GetInteractor());

    ui->view3->SetRenderWindow(mResliceRenderWin[2]);
    mResliceViewer[2]->SetupInteractor(mResliceRenderWin[2]->GetInteractor());

    for (int i = 0; i < 3; ++i) {
        // make them all share the same reslice cursor object.
        mResliceViewer[i]->SetResliceCursor(mResliceViewer[0]->GetResliceCursor());
    }

    mPlanePicker = vtkSmartPointer<vtkCellPicker>::New();
    mPlanePicker->SetTolerance(0.005);

    mPlaneRenderWin = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    ui->view4->SetRenderWindow(mPlaneRenderWin);
    mPlaneRender = vtkSmartPointer<vtkRenderer>::New();
    ui->view4->GetRenderWindow()->AddRenderer(mPlaneRender);
    double planeBkColor[3] = {0.5, 0.5, 0.5};
    mPlaneRender->SetBackground(planeBkColor);

    vtkRenderWindowInteractor *iren = ui->view4->GetInteractor();
    mPlaneProperty = vtkSmartPointer<vtkProperty>::New();
    for (int i = 0; i < 3; ++i) {
        mPlaneWidget[i] = vtkSmartPointer<vtkImagePlaneWidget>::New();
        mPlaneWidget[i]->SetInteractor(iren);
        mPlaneWidget[i]->SetPicker(mPlanePicker);
        double color[3] = { 0.0, 0.0, 0.0 };
        color[i] = 1.0;
        mPlaneWidget[i]->GetPlaneProperty()->SetColor(color);

        color[0] /= 4.0;
        color[1] /= 4.0;
        color[2] /= 4.0;
        mResliceViewer[i]->GetRenderer()->SetBackground(color);

        mPlaneWidget[i]->SetTexturePlaneProperty(mPlaneProperty);
        mPlaneWidget[i]->SetDefaultRenderer(mPlaneRender);

        // Make them all share the same color map.
        mResliceViewer[i]->SetLookupTable(mResliceViewer[0]->GetLookupTable());
        mPlaneWidget[i]->GetColorMap()->SetLookupTable(mResliceViewer[0]->GetLookupTable());
        mPlaneWidget[i]->SetColorMap(mResliceViewer[i]->GetResliceCursorWidget()
            ->GetResliceCursorRepresentation()->GetColorMap());
    }

    mResliceCallback = vtkSmartPointer<vtkResliceCursorCallback>::New();
    for (int i = 0; i < 3; ++i) {
        mResliceCallback->imagePlabeWidget[i] = mPlaneWidget[i];
        mResliceCallback->resliceCursorWidget[i] = mResliceViewer[i]->GetResliceCursorWidget();
        mResliceViewer[i]->GetResliceCursorWidget()->AddObserver(
            vtkResliceCursorWidget::ResliceAxesChangedEvent, mResliceCallback);
        mResliceViewer[i]->GetResliceCursorWidget()->AddObserver(
            vtkResliceCursorWidget::WindowLevelEvent, mResliceCallback);
        mResliceViewer[i]->GetResliceCursorWidget()->AddObserver(
            vtkResliceCursorWidget::ResliceThicknessChangedEvent, mResliceCallback);
        mResliceViewer[i]->GetResliceCursorWidget()->AddObserver(
            vtkResliceCursorWidget::ResetCursorEvent, mResliceCallback);
        mResliceViewer[i]->GetInteractorStyle()->AddObserver(
            vtkCommand::WindowLevelEvent, mResliceCallback);
    }

    for (int i = 0; i < 3; ++i) {
        mResliceRenderWin[i]->Render();
    }
    mPlaneRenderWin->Render();
}

void QtVTKRenderWindows::SetBlendMode(
    int m
){
    for (int i = 0; i < 3; ++i) {
        vtkImageSlabReslice *thickSlabReslice = vtkImageSlabReslice::SafeDownCast(
            vtkResliceCursorThickLineRepresentation::SafeDownCast(
            mResliceViewer[i]->GetResliceCursorWidget()->GetRepresentation())->GetReslice());
        thickSlabReslice->SetBlendMode(m);
        mResliceViewer[i]->Render();
    }
}

void QtVTKRenderWindows::AddDistanceMeasurementToView(
    int i
){
    // remove existing widgets.
    if (this->mDistanceWidget[i])
    {
        this->mDistanceWidget[i]->SetEnabled(0);
        this->mDistanceWidget[i] = nullptr;
    }

    // add new widget
    this->mDistanceWidget[i] = vtkSmartPointer< vtkDistanceWidget >::New();
    this->mDistanceWidget[i]->SetInteractor(
        this->mResliceViewer[i]->GetResliceCursorWidget()->GetInteractor());

    // Set a priority higher than our reslice cursor widget
    this->mDistanceWidget[i]->SetPriority(
        this->mResliceViewer[i]->GetResliceCursorWidget()->GetPriority() + 0.01);

    vtkSmartPointer< vtkPointHandleRepresentation2D > handleRep =
        vtkSmartPointer< vtkPointHandleRepresentation2D >::New();
    vtkSmartPointer< vtkDistanceRepresentation2D > distanceRep =
        vtkSmartPointer< vtkDistanceRepresentation2D >::New();
    distanceRep->SetHandleRepresentation(handleRep);
    this->mDistanceWidget[i]->SetRepresentation(distanceRep);
    distanceRep->InstantiateHandleRepresentation();
    distanceRep->GetPoint1Representation()->SetPointPlacer(mResliceViewer[i]->GetPointPlacer());
    distanceRep->GetPoint2Representation()->SetPointPlacer(mResliceViewer[i]->GetPointPlacer());

    // Add the distance to the list of widgets whose visibility is managed based
    // on the reslice plane by the ResliceImageViewerMeasurements class
    this->mResliceViewer[i]->GetMeasurements()->AddItem(this->mDistanceWidget[i]);

    this->mDistanceWidget[i]->CreateDefaultRepresentation();
    this->mDistanceWidget[i]->EnabledOn();
}

void QtVTKRenderWindows::on_btnSelectFolder_clicked()
{
    QString dir_name = QFileDialog::getExistingDirectory(
        this, tr("Select DICOM folder")
        );
    if (dir_name.isEmpty()){
        QMessageBox::critical(this, tr("Error"),
            tr("Please select a DICOM folder"));
        return;
    }
    ui->editDicomFolder->setText(dir_name);

    readDicomSeries(dir_name);
}

void QtVTKRenderWindows::on_resliceModeCheckBox_toggled(
    bool checked
){
    ui->thickModeCheckBox->setEnabled(checked);
    ui->blendModeGroupBox->setEnabled(checked);

    for (int i = 0; i < 3; ++i) {
        mResliceViewer[i]->SetResliceMode(checked ? 1 : 0);
        mResliceViewer[i]->GetRenderer()->ResetCamera();
        mResliceViewer[i]->Render();
    }
}

void QtVTKRenderWindows::on_thickModeCheckBox_toggled(
    bool checked
){
    for (int i = 0; i < 3; ++i) {
        mResliceViewer[i]->SetThickMode(checked ? 1 : 0);
        mResliceViewer[i]->Render();
    }
}

void QtVTKRenderWindows::on_radioButton_Max_toggled(
    bool checked
){
    if(checked){
        SetBlendMode(VTK_IMAGE_SLAB_MAX);
    }
}

void QtVTKRenderWindows::on_radioButton_Min_toggled(
    bool checked
){
    if(checked){
        SetBlendMode(VTK_IMAGE_SLAB_MIN);
    }
}

void QtVTKRenderWindows::on_radioButton_Mean_toggled(
    bool checked
){
    if(checked){
        SetBlendMode(VTK_IMAGE_SLAB_MEAN);
    }
}

void QtVTKRenderWindows::on_resetButton_clicked()
{
    // Reset the reslice image views
    for (int i = 0; i < 3; ++i) {
        mResliceViewer[i]->Reset();
    }

    // Also sync the Image plane widget on the 3D top right view with any
    // changes to the reslice cursor.
    for (int i = 0; i < 3; ++i) {
        vtkPlaneSource *ps = static_cast<vtkPlaneSource *>(
            mPlaneWidget[i]->GetPolyDataAlgorithm());
        ps->SetNormal(mResliceViewer[0]->GetResliceCursor()->GetPlane(i)->GetNormal());
        ps->SetCenter(mResliceViewer[0]->GetResliceCursor()->GetPlane(i)->GetOrigin());

        // If the reslice plane has modified, update it on the 3D widget
        mPlaneWidget[i]->UpdatePlacement();
    }

    // Render in response to changes.
    for (int i = 0; i < 3; ++i) {
        mResliceViewer[i]->Render();
    }
    ui->view4->GetRenderWindow()->Render();
}

void QtVTKRenderWindows::on_AddDistance1Button_clicked()
{
    AddDistanceMeasurementToView(1);
}

void QtVTKRenderWindows::readDicomSeries(
    const QString &dir_name
){
    std::string series_dir = CommonFunc::qs2s(dir_name);

    auto reader = vtkSmartPointer<vtkDICOMImageReader>::New();
    reader->SetDirectoryName(series_dir.c_str());
    reader->Update();
    int imageDims[3];
    reader->GetOutput()->GetDimensions(imageDims);

    for (int i = 0; i < 3; ++i) {
        auto rep = vtkResliceCursorLineRepresentation::SafeDownCast(
            mResliceViewer[i]->GetResliceCursorWidget()->GetRepresentation());
        rep->GetResliceCursorActor()->
            GetCursorAlgorithm()->SetReslicePlaneNormal(i);

        mResliceViewer[i]->SetInputData(reader->GetOutput());
        mResliceViewer[i]->SetSliceOrientation(i);
        mResliceViewer[i]->SetResliceModeToAxisAligned();
    }

    for (int i = 0; i < 3; ++i) {
        mPlaneWidget[i]->RestrictPlaneToVolumeOn();

        mPlaneWidget[i]->TextureInterpolateOff();
        mPlaneWidget[i]->SetResliceInterpolateToLinear();
        mPlaneWidget[i]->SetInputConnection(reader->GetOutputPort());
        mPlaneWidget[i]->SetPlaneOrientation(i);
        mPlaneWidget[i]->SetSliceIndex(imageDims[i] / 2);
        mPlaneWidget[i]->DisplayTextOff();
        mPlaneWidget[i]->SetWindowLevel(1358, -27);
        mPlaneWidget[i]->On();
        mPlaneWidget[i]->InteractionOn();
    }

    for (int i = 0; i < 3; ++i) {
        mResliceViewer[i]->GetRenderer()->ResetCamera();
        mResliceRenderWin[i]->Render();
    }
    mPlaneRender->ResetCamera();
    mPlaneRenderWin->Render();
}