vtkImageViewer2 解析

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值