這周開始調查學習利用VTK渲染DICOM MPR/VR圖像。看到了VTK自帶的例子:
VTK-8.1.0\Examples\GUI\Qt\FourPaneViewer
這個例子用QVTKOpenGLWidget控件顯示四個view,分别顯示相交的MPRA、MPRB、MPRC以及3D slices(三個相交的MPR面)。
下面的UI是我修改後的。
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)内點選/拖動的時候還是很容易就崩潰了。但提示的錯誤已經不一樣了。
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();
}