vtkImageViewer2 解析


       本文代碼版本: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 的主要功能全部介紹完畢。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM