本文代碼版本:VTK-8.2.
vtkImageViewer2 是一個非常實用的類,它簡化了我們通過管線處理數據的工作, 因為它內部封裝了一些對象,並將它們連接成管線, 這些對象有 vtkRendererWindow, vtkRenderer, vtkImageActor 和 vtkImageMapToWindowLevelColors。 vtkImageViewer2 天然地提供了一些圖像的基本功能——渲染,綻放,平移以及翻層, 同時也支持在XY, YZ 或 XZ 不同方向上進行切換以顯示不同的切片。因此,研究這個類對我們理解和學習 VTK 的圖像處理非常有幫助。
當 vtkImageViewer2 被構造時,它將對上面所列舉的對像進行初始化。
vtkImageViewer2::vtkImageViewer2() { this->RenderWindow = nullptr; this->Renderer = nullptr; this->ImageActor = vtkImageActor::New(); this->WindowLevel = vtkImageMapToWindowLevelColors::New(); this->Interactor = nullptr; this->InteractorStyle = nullptr; this->Slice = 0; this->FirstRender = 1; this->SliceOrientation = vtkImageViewer2::SLICE_ORIENTATION_XY; // Setup the pipeline vtkRenderWindow *renwin = vtkRenderWindow::New(); this->SetRenderWindow(renwin); renwin->Delete(); vtkRenderer *ren = vtkRenderer::New(); this->SetRenderer(ren); ren->Delete(); this->InstallPipeline(); }
在上面的代碼中的最后一行,對數據管線進行初始化, 下面是初始化代碼。
1 void vtkImageViewer2::InstallPipeline() 2 { 3 if (this->RenderWindow && this->Renderer) 4 { 5 this->RenderWindow->AddRenderer(this->Renderer); 6 } 7 8 if (this->Interactor) 9 { 10 if (!this->InteractorStyle) 11 { 12 this->InteractorStyle = vtkInteractorStyleImage::New(); 13 vtkImageViewer2Callback *cbk = vtkImageViewer2Callback::New(); 14 cbk->IV = this; 15 this->InteractorStyle->AddObserver(
16 vtkCommand::WindowLevelEvent, cbk);
17 this->InteractorStyle->AddObserver( 18 vtkCommand::StartWindowLevelEvent, cbk); 19 this->InteractorStyle->AddObserver( 20 vtkCommand::ResetWindowLevelEvent, cbk); 21 cbk->Delete(); 22 } 23 24 this->Interactor->SetInteractorStyle(this->InteractorStyle); 25 this->Interactor->SetRenderWindow(this->RenderWindow); 26 } 27 28 if (this->Renderer && this->ImageActor) 29 { 30 this->Renderer->AddViewProp(this->ImageActor); 31 } 32 33 if (this->ImageActor && this->WindowLevel) 34 { 35 this->ImageActor->GetMapper()->SetInputConnection( 36 this->WindowLevel->GetOutputPort()); 37 } 38 }
在上面對數據管線初始化代碼中,對WindowLevelEvent、StartWindowLevelEvent、ResetWindowLevelEvent 三個事件進行了監聽。至此,數據管線建立完畢。 我們下面看一看各項功能,vtkImageViewer2 是如何實現的。首先要看的就是 SetInputData 函數。
1 void vtkImageViewer2::SetInputData(vtkImageData *in) 2 { 3 this->WindowLevel->SetInputData(in); 4 this->UpdateDisplayExtent(); 5 }
在SetInputData 函數中, 首先將輸入的 vtkImageData 傳給 WindowLevel 對象(它是vtkImageMapToWindowLevelColors的實例), 然后調用 UpdateDisplayExtent () 來更新顯示范圍。下面就看一看在 UpdateDisplayExtent 函數中,vtkImageViewer2 做了些什么。
1 void vtkImageViewer2::UpdateDisplayExtent() 2 { 3 vtkAlgorithm *input = this->GetInputAlgorithm(); 4 if (!input || !this->ImageActor) 5 { 6 return; 7 } 8 9 input->UpdateInformation(); 10 vtkInformation* outInfo = input->GetOutputInformation(0); 11 int *w_ext = outInfo->Get( 12 vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()); 13 14 // Is the slice in range ? If not, fix it 15 16 int slice_min = w_ext[this->SliceOrientation * 2]; 17 int slice_max = w_ext[this->SliceOrientation * 2 + 1]; 18 if (this->Slice < slice_min || this->Slice > slice_max) 19 { 20 this->Slice = static_cast<int>((slice_min + slice_max) * 0.5); 21 } 22 23 // Set the image actor 24 25 switch (this->SliceOrientation) 26 { 27 case vtkImageViewer2::SLICE_ORIENTATION_XY: 28 this->ImageActor->SetDisplayExtent( 29 w_ext[0], w_ext[1], w_ext[2], w_ext[3], this->Slice, this->Slice); 30 break; 31 32 case vtkImageViewer2::SLICE_ORIENTATION_XZ: 33 this->ImageActor->SetDisplayExtent( 34 w_ext[0], w_ext[1], this->Slice, this->Slice, w_ext[4], w_ext[5]); 35 break; 36 37 case vtkImageViewer2::SLICE_ORIENTATION_YZ: 38 this->ImageActor->SetDisplayExtent( 39 this->Slice, this->Slice, w_ext[2], w_ext[3], w_ext[4], w_ext[5]); 40 break; 41 } 42 43 // Figure out the correct clipping range 44 45 if (this->Renderer) 46 { 47 if (this->InteractorStyle && 48 this->InteractorStyle->GetAutoAdjustCameraClippingRange()) 49 { 50 this->Renderer->ResetCameraClippingRange(); 51 } 52 else 53 { 54 vtkCamera *cam = this->Renderer->GetActiveCamera(); 55 if (cam) 56 { 57 double bounds[6]; 58 this->ImageActor->GetBounds(bounds); 59 double spos = bounds[this->SliceOrientation * 2]; 60 double cpos = cam->GetPosition()[this->SliceOrientation]; 61 double range = fabs(spos - cpos); 62 double *spacing = outInfo->Get(vtkDataObject::SPACING()); 63 double avg_spacing = 64 (spacing[0] + spacing[1] + spacing[2]) / 3.0; 65 cam->SetClippingRange( 66 range - avg_spacing * 3.0, range + avg_spacing * 3.0); 67 } 68 } 69 } 70 }
這個函數的實現代碼中有兩行注釋, 正是這兩行注釋將這個函數的代碼分成了三個部分。 第一部分代碼的功能是根據新傳入的 vtkImageData 和顯示的切片方向(SliceOrientation變量)來確定切片范圍,並默認將切片位置設置為中間位置。第二部分代碼的功能是為 ImageActor 設置顯示范圍。這兩部分代碼確定了要顯示的數據在 vtkImageData 中的位置與范圍。剩下的就是調整攝像機的位置與裁切范圍,將要顯示的數據顯示出來,而這就是第三部分代碼的功能。至此我們設置的數據就顯示出來了。
我們可以通過調用 SetSliceOrientationToXY (), SetSliceOrientationToXZ() 和 SetSliceOrientationToYZ() 來顯示不同方向的切片圖像。而這三個函數都是使用恰當的參數在其內部調用 SetSliceOrientation 來實現的。 下面展示的是 SetSliceOrientation 的代碼, 看看它做了些什么。
1 void vtkImageViewer2::SetSliceOrientation(int orientation) 2 { 3 if (orientation < vtkImageViewer2::SLICE_ORIENTATION_YZ || 4 orientation > vtkImageViewer2::SLICE_ORIENTATION_XY) 5 { 6 vtkErrorMacro("Error - invalid slice orientation " << orientation); 7 return; 8 } 9 10 if (this->SliceOrientation == orientation) 11 { 12 return; 13 } 14 15 this->SliceOrientation = orientation; 16 17 // Update the viewer 18 19 int *range = this->GetSliceRange(); 20 if (range) 21 { 22 this->Slice = static_cast<int>((range[0] + range[1]) * 0.5); 23 } 24 25 this->UpdateOrientation(); 26 this->UpdateDisplayExtent(); 27 28 if (this->Renderer && this->GetInput()) 29 { 30 double scale = this->Renderer->GetActiveCamera()->GetParallelScale(); 31 this->Renderer->ResetCamera(); 32 this->Renderer->GetActiveCamera()->SetParallelScale(scale); 33 } 34 35 this->Render(); 36 }
在 SetSliceOrientation 函數中首先使用成員變量保存了參數 orientation 的值, 然后調用 UpdateOrientation () 來更新顯示的切片方向,並更新顯示范圍。 最后將顯示的比例調整到顯示上一個切片方向時所使用的比例。UpdataDisplayExtent 函數, 我們在上面已經解析過了,所以下面重點觀察一下 UpdateOrientation () 函數的實現。
1 void vtkImageViewer2::UpdateOrientation() 2 { 3 // Set the camera position 4 5 vtkCamera *cam = this->Renderer ? this->Renderer->GetActiveCamera() : nullptr; 6 if (cam) 7 { 8 switch (this->SliceOrientation) 9 { 10 case vtkImageViewer2::SLICE_ORIENTATION_XY: 11 cam->SetFocalPoint(0,0,0); 12 cam->SetPosition(0,0,1); // -1 if medical ? 13 cam->SetViewUp(0,1,0); 14 break; 15 16 case vtkImageViewer2::SLICE_ORIENTATION_XZ: 17 cam->SetFocalPoint(0,0,0); 18 cam->SetPosition(0,-1,0); // 1 if medical ? 19 cam->SetViewUp(0,0,1); 20 break; 21 22 case vtkImageViewer2::SLICE_ORIENTATION_YZ: 23 cam->SetFocalPoint(0,0,0); 24 cam->SetPosition(1,0,0); // -1 if medical ? 25 cam->SetViewUp(0,0,1); 26 break; 27 } 28 } 29 }
在 UpdateSliceOrientation 函數中,它只是根據不同的切片方向,來調整攝像機而已。 首先調整的是攝像機的焦點,然后是位置,最后是向上方向,就完成了對切片方向的改變。在不同的方向上,我們可以調用 SetSlice () 函數來顯示在視線方向上不同位置的切片,以下是SetSlice() 的實現方法。
1 void vtkImageViewer2::SetSlice(int slice) 2 { 3 int *range = this->GetSliceRange(); 4 if (range) 5 { 6 if (slice < range[0]) 7 { 8 slice = range[0]; 9 } 10 else if (slice > range[1]) 11 { 12 slice = range[1]; 13 } 14 } 15 16 if (this->Slice == slice) 17 { 18 return; 19 } 20 21 this->Slice = slice; 22 this->Modified(); 23 24 this->UpdateDisplayExtent(); 25 this->Render(); 26 }
在 SetSlice() 函數中,首先確定了 參數傳入的切片位置位於 Slice 的合理范圍之內,並將其存入成員變量 Slice 中, 然后調用 UpdateDisplayExtent () 函數應用新的切片位置,最后更新顯示范圍和渲染。
至此,關於vtkImageViewer2 的主要功能全部介紹完畢。