本文代码版本: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 的主要功能全部介绍完毕。
原文:https://www.cnblogs.com/biaohuang/p/14287854.html