vtkImageViewer2是一个非常有用而且也经常用的类,它简化了我们通过管线处理数据的工作,内部封装了vtkRenderWindow,vtkRenderer,vtkImageActor,vtkImageMapToWindowLevelColors等一些有用的类。
vtkImageViewer2自动提供了对图像的渲染,缩放,窗宽窗位,翻层,平移,等操作,而且还可以截取XY,YZ,XZ三个方向的切片。
下面是vtkImageViewer2的构造函数:
- vtkImageViewer2::vtkImageViewer2()
- {
- this->RenderWindow = NULL;
- this->Renderer = NULL;
- this->ImageActor = vtkImageActor::New();
- this->WindowLevel = vtkImageMapToWindowLevelColors::New();
- this->Interactor = NULL;
- this->InteractorStyle = NULL;
-
- 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, this->SetRenderer(ren);
和
2,this->InstallPipeline();
我们先来分析第一句this->SetRenderer(ren);他同样在vtkImageViewer2中,如下:
void vtkImageViewer2::SetRenderer(vtkRenderer *arg)
{
if (this->Renderer == arg)
{
return;
}
this->UnInstallPipeline();
if (this->Renderer)
{
this->Renderer->UnRegister(this);
}
this->Renderer = arg;
if (this->Renderer)
{
this->Renderer->Register(this);
}
this->InstallPipeline();
this->UpdateOrientation();
}
这里面有this->UnInstallPipeline();反安装管线,和标黄的三句话,其中this->Renderer->Register(this);很好理解,就是注册Renderer。this->InstallPipeline();是不是是不是和构造函数中的this->InstallPipeline();重复了?
我们来看看这个函数:
- void vtkImageViewer2::InstallPipeline()
- {
- if (this->RenderWindow && this->Renderer)//this->Renderer此时还没有New出来
- {
- this->RenderWindow->AddRenderer(this->Renderer);
- }
-
- if (this->Interactor)//Interactor为空
- {
- if (!this->InteractorStyle)
- {
- this->InteractorStyle = vtkInteractorStyleImage::New();
- vtkImageViewer2Callback *cbk = vtkImageViewer2Callback::New();
- cbk->IV = this;
- this->InteractorStyle->AddObserver(
- vtkCommand::WindowLevelEvent, cbk);
- this->InteractorStyle->AddObserver(
- vtkCommand::StartWindowLevelEvent, cbk);
- this->InteractorStyle->AddObserver(
- vtkCommand::ResetWindowLevelEvent, cbk);
- cbk->Delete();
- }
-
- this->Interactor->SetInteractorStyle(this->InteractorStyle);
- this->Interactor->SetRenderWindow(this->RenderWindow);
- }
-
- if (this->Renderer && this->ImageActor)this->Renderer此时还没有New出来
- {
- this->Renderer->AddViewProp(this->ImageActor);
- }
-
- if (this->ImageActor && this->WindowLevel)//此处执行
- {
- this->ImageActor->GetMapper()->SetInputConnection(
- this->WindowLevel->GetOutputPort());
- }
- }
在第一次void vtkImageViewer2::SetRenderer(vtkRenderer *arg)中虽然也跳用了,但是此时this->Renderer和this->Interactor都为空,还没有创建,所以这个函数只有最后一个 if语句执行了,而到了vtkImageViewer2构造函数的最后一句,这些成员都已构造完成,所以整个函数都会执行,这样一条具有RenderWIndow,Render, Interactor InteractorStyle和CBK回调函数的管线就建立起来了,在上面管线中对三个时间进行了监听:vtkCommand::WindowLevelEvent,vtkCommand::StartWindowLevelEvent,vtkCommand::ResetWindowLevelEvent。
至此,数据管线已经建立,我下面分析一下:
- void vtkImageViewer2::SetInputData(vtkImageData *in)
- {
- this->WindowLevel->SetInputData(in);
- this->UpdateDisplayExtent();
- }
this->WindowLevel->SetInputData(in);此句话添加数据源,我们看看下面一句:
this->UpdateDisplayExtent()
- void vtkImageViewer2::UpdateDisplayExtent()
- {
- vtkAlgorithm *input = this->GetInputAlgorithm();
- if (!input || !this->ImageActor)
- {
- return;
- }
-
- input->UpdateInformation();
- vtkInformation* outInfo = input->GetOutputInformation(0);
- int *w_ext = outInfo->Get(
- vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
-
- // Is the slice in range ? If not, fix it
-
- int slice_min = w_ext[this->SliceOrientation * 2];
- int slice_max = w_ext[this->SliceOrientation * 2 + 1];
- if (this->Slice < slice_min || this->Slice > slice_max)
- {
- this->Slice = static_cast<int>((slice_min + slice_max) * 0.5);
- }
-
- // Set the image actor
-
- switch (this->SliceOrientation)
- {
- case vtkImageViewer2::SLICE_ORIENTATION_XY:
- this->ImageActor->SetDisplayExtent(
- w_ext[0], w_ext[1], w_ext[2], w_ext[3], this->Slice, this->Slice);
- break;
-
- case vtkImageViewer2::SLICE_ORIENTATION_XZ:
- this->ImageActor->SetDisplayExtent(
- w_ext[0], w_ext[1], this->Slice, this->Slice, w_ext[4], w_ext[5]);
- break;
-
- case vtkImageViewer2::SLICE_ORIENTATION_YZ:
- this->ImageActor->SetDisplayExtent(
- this->Slice, this->Slice, w_ext[2], w_ext[3], w_ext[4], w_ext[5]);
- break;
- }
-
- // Figure out the correct clipping range
-
- if (this->Renderer)
- {
- if (this->InteractorStyle &&
- this->InteractorStyle->GetAutoAdjustCameraClippingRange())
- {
- this->Renderer->ResetCameraClippingRange();
- }
- else
- {
- vtkCamera *cam = this->Renderer->GetActiveCamera();
- if (cam)
- {
- double bounds[6];
- this->ImageActor->GetBounds(bounds);
- double spos = bounds[this->SliceOrientation * 2];
- double cpos = cam->GetPosition()[this->SliceOrientation];
- double range = fabs(spos - cpos);
- double *spacing = outInfo->Get(vtkDataObject::SPACING());
- double avg_spacing =
- (spacing[0] + spacing[1] + spacing[2]) / 3.0;
- cam->SetClippingRange(
- range - avg_spacing * 3.0, range + avg_spacing * 3.0);
- }
- }
- }
- }
w_ext是一个指针,指向数据的范围(数据本身的取值范围),switch语句决定具体显示那个方向的那一帧(this->Slice),我们可以通过调用SetSliceOrientation(int orientation)或者SetSliceOrientationToXY(),SetSliceOrientationToYZ(),SetSliceOrientationToXZ()还确定显示的方向。
- void vtkImageViewer2::SetSliceOrientation(int orientation)
- {
- if (orientation < vtkImageViewer2::SLICE_ORIENTATION_YZ ||
- orientation > vtkImageViewer2::SLICE_ORIENTATION_XY)
- {
- vtkErrorMacro("Error - invalid slice orientation " << orientation);
- return;
- }
-
- if (this->SliceOrientation == orientation)
- {
- return;
- }
-
- this->SliceOrientation = orientation;
-
- // Update the viewer
-
- int *range = this->GetSliceRange();
- if (range)
- {
- this->Slice = static_cast<int>((range[0] + range[1]) * 0.5);
- }
-
- this->UpdateOrientation();
- this->UpdateDisplayExtent();
-
- if (this->Renderer && this->GetInput())
- {
- double scale = this->Renderer->GetActiveCamera()->GetParallelScale();
- this->Renderer->ResetCamera();
- this->Renderer->GetActiveCamera()->SetParallelScale(scale);
- }
-
- this->Render();
- }
在SetSliceOrientation中使用成员变量保存了参数orientation的值,然后调用UpdateOrientation()来更新显示的切片方向,并更显显示范围。最后将显示的比例调整到显示上一个切片方向是所使用的比例。我们下面重点观察一下UpdateOrientation()函数的功能。
- void vtkImageViewer2::UpdateOrientation()
- {
- // Set the camera position
-
- vtkCamera *cam = this->Renderer ? this->Renderer->GetActiveCamera() : NULL;
- if (cam)
- {
- switch (this->SliceOrientation)
- {
- case vtkImageViewer2::SLICE_ORIENTATION_XY:
- cam->SetFocalPoint(0,0,0);
- cam->SetPosition(0,0,1); // -1 if medical ?
- cam->SetViewUp(0,1,0);
- break;
-
- case vtkImageViewer2::SLICE_ORIENTATION_XZ:
- cam->SetFocalPoint(0,0,0);
- cam->SetPosition(0,-1,0); // 1 if medical ?
- cam->SetViewUp(0,0,1);
- break;
-
- case vtkImageViewer2::SLICE_ORIENTATION_YZ:
- cam->SetFocalPoint(0,0,0);
- cam->SetPosition(1,0,0); // -1 if medical ?
- cam->SetViewUp(0,0,1);
- break;
- }
- }
- }
在UpdateSliceOrientation函数中,他只是根据不同的切片方向,来调整相机方向而已。首先貂正的是相机的焦点,然后是位置,最后是向上方向,就完成了对切片方向的改变。在不同的方向上,我们可以调用SetSlice()函数来显示在实现方向上不同位置的切片。以下是SetSlice()的实现方法:
- void vtkImageViewer2::SetSlice(int slice)
- {
- int *range = this->GetSliceRange();
- if (range)
- {
- if (slice < range[0])
- {
- slice = range[0];
- }
- else if (slice > range[1])
- {
- slice = range[1];
- }
- }
-
- if (this->Slice == slice)
- {
- return;
- }
-
- this->Slice = slice;
- this->Modified();
-
- this->UpdateDisplayExtent();
- this->Render();
- }
SetSlice()函数中,首先确定了参数传入的切片的位置位于Slice的合理范围内,并将其存入成员变量Slice中,然后调用UpdateDisplayExtent()函数应用新的切片位置,最后更新显示范围和渲染。