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()函数应用新的切片位置,最后更新显示范围和渲染。