• vtkImageViewer2 解析


     vtkImageViewer2是一个非常有用而且也经常用的类,它简化了我们通过管线处理数据的工作,内部封装了vtkRenderWindow,vtkRenderer,vtkImageActor,vtkImageMapToWindowLevelColors等一些有用的类。

    vtkImageViewer2自动提供了对图像的渲染,缩放,窗宽窗位,翻层,平移,等操作,而且还可以截取XY,YZ,XZ三个方向的切片。

    下面是vtkImageViewer2的构造函数:

    1. vtkImageViewer2::vtkImageViewer2()
    2. {
    3. this->RenderWindow = NULL;
    4. this->Renderer = NULL;
    5. this->ImageActor = vtkImageActor::New();
    6. this->WindowLevel = vtkImageMapToWindowLevelColors::New();
    7. this->Interactor = NULL;
    8. this->InteractorStyle = NULL;
    9. this->Slice = 0;
    10. this->FirstRender = 1;
    11. this->SliceOrientation = vtkImageViewer2::SLICE_ORIENTATION_XY;
    12. // Setup the pipeline
    13. vtkRenderWindow *renwin = vtkRenderWindow::New();
    14. this->SetRenderWindow(renwin);
    15. renwin->Delete();
    16. vtkRenderer *ren = vtkRenderer::New();
    17. this->SetRenderer(ren);
    18. ren->Delete();
    19. this->InstallPipeline();
    20. }

    这个 构造函数中最重要的两句话:

    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();重复了?

    我们来看看这个函数:

    1. void vtkImageViewer2::InstallPipeline()
    2. {
    3. if (this->RenderWindow && this->Renderer)//this->Renderer此时还没有New出来
    4. {
    5. this->RenderWindow->AddRenderer(this->Renderer);
    6. }
    7. if (this->Interactor)//Interactor为空
    8. {
    9. if (!this->InteractorStyle)
    10. {
    11. this->InteractorStyle = vtkInteractorStyleImage::New();
    12. vtkImageViewer2Callback *cbk = vtkImageViewer2Callback::New();
    13. cbk->IV = this;
    14. this->InteractorStyle->AddObserver(
    15. vtkCommand::WindowLevelEvent, cbk);
    16. this->InteractorStyle->AddObserver(
    17. vtkCommand::StartWindowLevelEvent, cbk);
    18. this->InteractorStyle->AddObserver(
    19. vtkCommand::ResetWindowLevelEvent, cbk);
    20. cbk->Delete();
    21. }
    22. this->Interactor->SetInteractorStyle(this->InteractorStyle);
    23. this->Interactor->SetRenderWindow(this->RenderWindow);
    24. }
    25. if (this->Renderer && this->ImageActor)this->Renderer此时还没有New出来
    26. {
    27. this->Renderer->AddViewProp(this->ImageActor);
    28. }
    29. if (this->ImageActor && this->WindowLevel)//此处执行
    30. {
    31. this->ImageActor->GetMapper()->SetInputConnection(
    32. this->WindowLevel->GetOutputPort());
    33. }
    34. }

     在第一次void vtkImageViewer2::SetRenderer(vtkRenderer *arg)中虽然也跳用了,但是此时this->Renderer和this->Interactor都为空,还没有创建,所以这个函数只有最后一个 if语句执行了,而到了vtkImageViewer2构造函数的最后一句,这些成员都已构造完成,所以整个函数都会执行,这样一条具有RenderWIndow,Render, Interactor InteractorStyle和CBK回调函数的管线就建立起来了,在上面管线中对三个时间进行了监听:vtkCommand::WindowLevelEvent,vtkCommand::StartWindowLevelEvent,vtkCommand::ResetWindowLevelEvent。

    至此,数据管线已经建立,我下面分析一下:

    1. void vtkImageViewer2::SetInputData(vtkImageData *in)
    2. {
    3. this->WindowLevel->SetInputData(in);
    4. this->UpdateDisplayExtent();
    5. }

     this->WindowLevel->SetInputData(in);此句话添加数据源,我们看看下面一句:

    this->UpdateDisplayExtent()

    1. void vtkImageViewer2::UpdateDisplayExtent()
    2. {
    3. vtkAlgorithm *input = this->GetInputAlgorithm();
    4. if (!input || !this->ImageActor)
    5. {
    6. return;
    7. }
    8. input->UpdateInformation();
    9. vtkInformation* outInfo = input->GetOutputInformation(0);
    10. int *w_ext = outInfo->Get(
    11. vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
    12. // Is the slice in range ? If not, fix it
    13. int slice_min = w_ext[this->SliceOrientation * 2];
    14. int slice_max = w_ext[this->SliceOrientation * 2 + 1];
    15. if (this->Slice < slice_min || this->Slice > slice_max)
    16. {
    17. this->Slice = static_cast<int>((slice_min + slice_max) * 0.5);
    18. }
    19. // Set the image actor
    20. switch (this->SliceOrientation)
    21. {
    22. case vtkImageViewer2::SLICE_ORIENTATION_XY:
    23. this->ImageActor->SetDisplayExtent(
    24. w_ext[0], w_ext[1], w_ext[2], w_ext[3], this->Slice, this->Slice);
    25. break;
    26. case vtkImageViewer2::SLICE_ORIENTATION_XZ:
    27. this->ImageActor->SetDisplayExtent(
    28. w_ext[0], w_ext[1], this->Slice, this->Slice, w_ext[4], w_ext[5]);
    29. break;
    30. case vtkImageViewer2::SLICE_ORIENTATION_YZ:
    31. this->ImageActor->SetDisplayExtent(
    32. this->Slice, this->Slice, w_ext[2], w_ext[3], w_ext[4], w_ext[5]);
    33. break;
    34. }
    35. // Figure out the correct clipping range
    36. if (this->Renderer)
    37. {
    38. if (this->InteractorStyle &&
    39. this->InteractorStyle->GetAutoAdjustCameraClippingRange())
    40. {
    41. this->Renderer->ResetCameraClippingRange();
    42. }
    43. else
    44. {
    45. vtkCamera *cam = this->Renderer->GetActiveCamera();
    46. if (cam)
    47. {
    48. double bounds[6];
    49. this->ImageActor->GetBounds(bounds);
    50. double spos = bounds[this->SliceOrientation * 2];
    51. double cpos = cam->GetPosition()[this->SliceOrientation];
    52. double range = fabs(spos - cpos);
    53. double *spacing = outInfo->Get(vtkDataObject::SPACING());
    54. double avg_spacing =
    55. (spacing[0] + spacing[1] + spacing[2]) / 3.0;
    56. cam->SetClippingRange(
    57. range - avg_spacing * 3.0, range + avg_spacing * 3.0);
    58. }
    59. }
    60. }
    61. }

    w_ext是一个指针,指向数据的范围(数据本身的取值范围),switch语句决定具体显示那个方向的那一帧(this->Slice),我们可以通过调用SetSliceOrientation(int orientation)或者SetSliceOrientationToXY(),SetSliceOrientationToYZ(),SetSliceOrientationToXZ()还确定显示的方向。

    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. if (this->SliceOrientation == orientation)
    10. {
    11. return;
    12. }
    13. this->SliceOrientation = orientation;
    14. // Update the viewer
    15. int *range = this->GetSliceRange();
    16. if (range)
    17. {
    18. this->Slice = static_cast<int>((range[0] + range[1]) * 0.5);
    19. }
    20. this->UpdateOrientation();
    21. this->UpdateDisplayExtent();
    22. if (this->Renderer && this->GetInput())
    23. {
    24. double scale = this->Renderer->GetActiveCamera()->GetParallelScale();
    25. this->Renderer->ResetCamera();
    26. this->Renderer->GetActiveCamera()->SetParallelScale(scale);
    27. }
    28. this->Render();
    29. }

     在SetSliceOrientation中使用成员变量保存了参数orientation的值,然后调用UpdateOrientation()来更新显示的切片方向,并更显显示范围。最后将显示的比例调整到显示上一个切片方向是所使用的比例。我们下面重点观察一下UpdateOrientation()函数的功能。

    1. void vtkImageViewer2::UpdateOrientation()
    2. {
    3. // Set the camera position
    4. vtkCamera *cam = this->Renderer ? this->Renderer->GetActiveCamera() : NULL;
    5. if (cam)
    6. {
    7. switch (this->SliceOrientation)
    8. {
    9. case vtkImageViewer2::SLICE_ORIENTATION_XY:
    10. cam->SetFocalPoint(0,0,0);
    11. cam->SetPosition(0,0,1); // -1 if medical ?
    12. cam->SetViewUp(0,1,0);
    13. break;
    14. case vtkImageViewer2::SLICE_ORIENTATION_XZ:
    15. cam->SetFocalPoint(0,0,0);
    16. cam->SetPosition(0,-1,0); // 1 if medical ?
    17. cam->SetViewUp(0,0,1);
    18. break;
    19. case vtkImageViewer2::SLICE_ORIENTATION_YZ:
    20. cam->SetFocalPoint(0,0,0);
    21. cam->SetPosition(1,0,0); // -1 if medical ?
    22. cam->SetViewUp(0,0,1);
    23. break;
    24. }
    25. }
    26. }

    在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. if (this->Slice == slice)
    16. {
    17. return;
    18. }
    19. this->Slice = slice;
    20. this->Modified();
    21. this->UpdateDisplayExtent();
    22. this->Render();
    23. }

    SetSlice()函数中,首先确定了参数传入的切片的位置位于Slice的合理范围内,并将其存入成员变量Slice中,然后调用UpdateDisplayExtent()函数应用新的切片位置,最后更新显示范围和渲染。

  • 相关阅读:
    SpringMVC基础篇(三)
    【Proteus仿真】【STM32单片机】便携式血糖仪
    二十六、文件系统API(设备在应用间的共享;目录和文件API)
    西电计组II 实验二
    建模杂谈系列158 再探函数链的实现
    leetcode 76. 最小覆盖子串
    Spark 中 Aggregate 的实现
    访问网站提示:您未被授权查看该页恢复办法
    移动安全测试框架-MobSF WINDOWS 环境搭建
    vue项目启动npm install和npm run serve时出现错误Failed to resolve loader:node-sass
  • 原文地址:https://blog.csdn.net/laziji/article/details/126952968