VTK实现DICOM简单阅片

  • Post author:
  • Post category:其他


60d98f006fa7e3ad2984de8c13d9bf94.png

可以通过鼠标滚轮上下翻滚图像,鼠标移动实时获得坐标位置与像素值。

#include <vtkActor.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkObjectFactory.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkActor2D.h>
#include <vtkDICOMImageReader.h>
#include <vtkInteractorStyleImage.h>    //为自定义交互式类准备
#include <vtkTextMapper.h>
#include <vtkTextProperty.h>
#include <vtkImageData.h>    
#include <vtkImageActor.h>
#include <vtkAutoInit.h>
#include <vtkCamera.h>
#include <vtkImageProperty.h>
#include <vtkImageReslice.h>
#include <vtkImageMapper3D.h>
#include <vtkMatrix4x4.h>
#include <vtkLookupTable.h>
#include <vtkImageMapToColors.h>  
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkRenderingContextOpenGL2);
#include <sstream>


namespace {


  // helper class to format slice status message
  class StatusMessage                                   
  {
  public:
    static std::string Format(int slice, int maxSlice,short pixels,int xx,int yy)
{
      std::stringstream tmp;
      tmp << "X " << xx << "  Y " << yy << "  Pixel " << pixels << "\n" << "Slice Number  " << slice + 1 << "/" << maxSlice + 1;
      return tmp.str();
    }
  };


  // Define own interaction style
  class myVtkInteractorStyleImage2 : public vtkInteractorStyleImage            //自定义交互式类
  {
  public:
    static myVtkInteractorStyleImage2* New();
    vtkTypeMacro(myVtkInteractorStyleImage2, vtkInteractorStyleImage);


  protected:
    vtkImageReslice* _ImageReslice;
    vtkImageMapToColors* _MapToColors;
    vtkTextMapper* _StatusMapper;
    vtkRenderWindowInteractor* _Interactor;
    vtkRenderer* _Renderer;
    vtkImageData* _imageData;
    int _Slice;
    int _MinSlice;
    int _MaxSlice;
    short PixelAndPosition[3] = { 0,-100,-100 };


  public:
    void SetImageReslice(vtkImageReslice* ImageReslice,int MinSlice,int MaxSlice, vtkImageMapToColors* MapToColors, vtkRenderWindowInteractor* Interactor, vtkRenderer* Renderer)
{
      _ImageReslice = ImageReslice;
      _MapToColors = MapToColors;
      _Interactor = Interactor;
      _Renderer = Renderer;
      _MinSlice = MinSlice;
      _MaxSlice = MaxSlice;
      _Slice = _MinSlice;
      cout << _Slice << endl;
      _imageData = ImageReslice->GetOutput();


    }


    void SetStatusMapper(vtkTextMapper* statusMapper)
{
      _StatusMapper = statusMapper;
    }


  protected:
    void MoveSliceForward()
{
      if (_Slice < _MaxSlice)
      {
        _Slice += 1;
        _ImageReslice->Update();
        double spacing = _ImageReslice->GetOutput()->GetSpacing()[2];
        vtkMatrix4x4* matrix = _ImageReslice->GetResliceAxes();
        double centz = matrix->GetElement(2, 3);
        centz += spacing;
        matrix->SetElement(2, 3, centz);
        _MapToColors->Update();
        _Interactor->Render();
        _imageData = _ImageReslice->GetOutput();
        MoveMouse();
        std::string msg = StatusMessage::Format(_Slice, _MaxSlice, PixelAndPosition[0], PixelAndPosition[1], PixelAndPosition[2]);
        _StatusMapper->SetInput(msg.c_str());
        _MapToColors->Update();
        _Interactor->Render();
      }
    }


    void MoveSliceBackward()
{
      if (_Slice > _MinSlice)
      {
        _Slice -= 1;
        _ImageReslice->Update();
        double spacing = _ImageReslice->GetOutput()->GetSpacing()[2];
        vtkMatrix4x4* matrix = _ImageReslice->GetResliceAxes();
        double centz = matrix->GetElement(2, 3);
        centz -= spacing;
        matrix->SetElement(2, 3, centz);


        _MapToColors->Update();
        _Interactor->Render();


        _imageData = _ImageReslice->GetOutput();
        MoveMouse();
        std::string msg = StatusMessage::Format(_Slice, _MaxSlice, PixelAndPosition[0], PixelAndPosition[1], PixelAndPosition[2]);
        _StatusMapper->SetInput(msg.c_str());
        _MapToColors->Update();
        _Interactor->Render();
      }
    }
    void MoveMouse()
{
      int* pos = this->GetInteractor()->GetEventPosition();   
      int* DataSize = _ImageReslice->GetOutput()->GetDimensions();
      int* rendsize = _Renderer->GetSize();
      int MinSize = 9999;
      for (int i = 0; i < 2; i++)
      {
        if (rendsize[i] < MinSize)
        {
          MinSize = rendsize[i];
        }
      }
      int* windowsize = _Interactor->GetRenderWindow()->GetSize();


      int posmin_x = (windowsize[0] - MinSize) / 2;
      int posmax_x = posmin_x + MinSize;
      int posmin_y = (windowsize[1] - MinSize) / 2;
      int posmax_y = posmin_y + MinSize;
      float scales = float(DataSize[0]) / float(MinSize);


      if (pos[0] >= posmin_x && pos[0] < posmax_x && pos[1] >= posmin_y && pos[1] < posmax_y)
      {
        int index_x = pos[0] - posmin_x;
        int index_y = pos[1] - posmin_y;
        int posxx = int(index_x * scales);
        int posyy = int(index_y * scales);
        if (posxx > DataSize[0] - 1)
        {
          posxx = DataSize[0] - 1;
        }
        if (posyy > DataSize[1] - 1)
        {
          posyy = DataSize[1] - 1;
        }


        short pixelvalue = (short)_imageData->GetScalarComponentAsDouble(posxx, posyy, 0, 0);
        PixelAndPosition[0] = pixelvalue;
        PixelAndPosition[1] = posxx;
        PixelAndPosition[2] = posyy;
      }
      else
      {
        PixelAndPosition[0] = 0;
        PixelAndPosition[1] = -100;
        PixelAndPosition[2] = -100;
      }
    }


    virtual void OnKeyDown()              
{
      std::string key = this->GetInteractor()->GetKeySym();
      if (key.compare("Up") == 0)
      {
        MoveSliceForward();
      }
      else if (key.compare("Down") == 0)
      {
        MoveSliceBackward();
      }
      vtkInteractorStyleImage::OnKeyDown();
    }


    virtual void OnMouseWheelForward()    
{
      MoveSliceForward();
    }


    virtual void OnMouseWheelBackward()    
{
      if (_Slice > _MinSlice)
      {
        MoveSliceBackward();
      }
    }


    virtual void OnMouseMove()   
{
      MoveMouse();


      _MapToColors->Update();
      _Interactor->Render();


      std::string msg = StatusMessage::Format(_Slice, _MaxSlice, PixelAndPosition[0],PixelAndPosition[1], PixelAndPosition[2]);
      _StatusMapper->SetInput(msg.c_str());


      _MapToColors->Update();
      _Interactor->Render();


      vtkInteractorStyleImage::OnMouseMove();   
    }
  };


  vtkStandardNewMacro(myVtkInteractorStyleImage2);   // 宏定义
} // namespace






int main()
{
  // 创建 DICOM 图像阅读器
  vtkSmartPointer<vtkDICOMImageReader> reader =
    vtkSmartPointer<vtkDICOMImageReader>::New();
  reader->SetDirectoryName("/*****");
  reader->Update();


  int dimss[3];
  reader->GetOutput()->GetDimensions(dimss);
  cout << "x:" << dimss[0] << "y:" << dimss[1] << "z:" << dimss[2] << endl;


  //文字渲染器
  // 创建图像 Actor
  //vtkTextProperty是VTK中的一个类,主要用于控制文本(文字)属性的显示,包括文字大小、颜色、字体、对齐方式等。
  vtkNew<vtkTextProperty> sliceTextProp;
  sliceTextProp->SetFontFamilyToCourier();        //设置为Courier字体
  sliceTextProp->SetFontSize(20);                 //字号为20
  sliceTextProp->SetVerticalJustificationToBottom();   //一种对齐方式,默认水平居中对齐。SetVerticalJustificationToTop顶部对齐SetVerticalJustificationToBottom垂直对齐到底部
  sliceTextProp->SetJustificationToLeft();     //左侧对齐(SetJustificationToLeft)或右侧对齐(SetJustificationToRight)。


  //vtkTextProperty类通常与vtkTextMapper一起使用。vtkTextMapper是一个用于将一段字符串渲染成二维纹理贴图的类。
  //使用vtkTextProperty,用户可以控制文本的各种属性,包括字体、颜色、大小、对齐方式等。
  vtkNew<vtkTextMapper> sliceTextMapper;
  std::string msg = StatusMessage::Format(0,
    dimss[2] - 1,0,-100,-100);
  sliceTextMapper->SetInput(msg.c_str());     // 设置要显示的文本内容
  sliceTextMapper->SetTextProperty(sliceTextProp);


  vtkNew<vtkActor2D> sliceTextActor;
  sliceTextActor->SetMapper(sliceTextMapper);
  sliceTextActor->SetPosition(15, 20);            //设置文本位置(坐标左下角为(0,0),(x,y))


  // usage hint message
  vtkNew<vtkTextProperty> usageTextProp;
  usageTextProp->SetFontFamilyToCourier();
  usageTextProp->SetFontSize(14);
  usageTextProp->SetVerticalJustificationToTop();
  usageTextProp->SetJustificationToLeft();


  vtkNew<vtkTextMapper> usageTextMapper;
  usageTextMapper->SetInput(
    "- Slice with mouse wheel\n  or Up/Down-Key\n- Zoom with pressed right\n "
    " mouse button while dragging");
  usageTextMapper->SetTextProperty(usageTextProp);


  vtkNew<vtkActor2D> usageTextActor;
  usageTextActor->SetMapper(usageTextMapper);
  usageTextActor->GetPositionCoordinate()
    ->SetCoordinateSystemToNormalizedDisplay();   //将该行文字的位置坐标系设置为规范化屏幕显示坐标系,其坐标原点位于窗口左下角,坐标范围在[0,1]之间。
  usageTextActor->GetPositionCoordinate()->SetValue(0.05, 0.95);  //根据范围设置位置


  // 创建渲染器
  vtkNew<vtkNamedColors> colors;
  vtkSmartPointer<vtkRenderer> renderertext = vtkSmartPointer<vtkRenderer>::New();
  renderertext->AddActor(usageTextActor);
  renderertext->AddActor(sliceTextActor);
  renderertext->SetBackground(colors->GetColor3d("SlateGray").GetData());


  int extent[6];
  double spacing[3];
  double origin[3];
  double center[3];


  reader->GetOutput()->GetExtent(extent);
  reader->GetOutput()->GetSpacing(spacing);
  reader->GetOutput()->GetOrigin(origin);


  center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
  center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
  center[2] = origin[2] ;
  static double axialElements[16] = {
    -1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, -1, 0,
    0, 0, 0, 1 };


  auto resliceAxes = vtkSmartPointer<vtkMatrix4x4>::New();  
  resliceAxes->DeepCopy(axialElements);
  resliceAxes->SetElement(0, 3, center[0]);  
  resliceAxes->SetElement(1, 3, center[1]);
  resliceAxes->SetElement(2, 3, center[2]);
  vtkSmartPointer<vtkImageReslice> ImageReslice = vtkSmartPointer<vtkImageReslice>::New();
  ImageReslice->SetInputConnection(reader->GetOutputPort());
  ImageReslice->SetOutputDimensionality(2);  
  ImageReslice->AutoCropOutputOn();


  double x[3] = { 1, 0, 0 };
  double y[3] = { 0, -1, 0 };
  double z[3] = { 0, 0, 1 };


#if 1
  ImageReslice->SetResliceAxes(resliceAxes);
#else
  ImageReslice->SetResliceAxesDirectionCosines(x, y, z);
  ImageReslice->SetResliceAxesOrigin(center);
#endif
  ImageReslice->SetInterpolationModeToLinear();
  ImageReslice->Update();


  auto lookupTable = vtkSmartPointer<vtkLookupTable>::New();
  lookupTable->SetRange(-1250, 1250);   //颜色映射的数值范围
  lookupTable->SetValueRange(0.0, 1.0);//设置颜色映射的数值范围对应的颜色值范围,
  lookupTable->SetSaturationRange(0.0, 0.0);//设置颜色映射的饱和度范围,这里将饱和度范围设置为 0.0,即无饱和度。
  lookupTable->SetRampToLinear(); //将颜色映射设置为线性渐变模式,这意味着颜色将在数值范围内均匀分布。
  lookupTable->Build();


  auto mapToColors = vtkSmartPointer<vtkImageMapToColors>::New();
  mapToColors->SetLookupTable(lookupTable);
  mapToColors->SetInputConnection(ImageReslice->GetOutputPort());
  mapToColors->Update();


  auto imageActor = vtkSmartPointer<vtkImageActor>::New();
  imageActor->GetMapper()->SetInputConnection(mapToColors->GetOutputPort());


  auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
  renderWindow->SetSize(800, 800);


  auto renderer = vtkSmartPointer<vtkRenderer>::New();
  renderer->AddActor(imageActor);
  renderer->SetBackground(colors->GetColor3d("SlateGray").GetData());


  // 设置视口大小为512x512,并使图像填充满视口
  double viewportSizeX = 512 / 800.0;
  double viewportSizeY = 512 / 800.0;
  renderer->SetViewport((1.0 - viewportSizeX) / 2.0, (1.0 - viewportSizeY) / 2.0, (1.0 + viewportSizeX) / 2.0, (1.0 + viewportSizeY) / 2.0);


  //设置相机位置,回填充满视口
  vtkCamera* camera = renderer->GetActiveCamera();
  camera->ParallelProjectionOn();     //用于将相机设置为平行投影模式。
  double* V = camera->GetViewUp();  //获取相机视角的上方向向量。默认(0, 1, 0),表示相机视角的上方向为正Y轴方向。
  V[0] = V[0];
  V[1] = -1;
  V[2] = V[2];
  camera->SetViewUp(V);
  int* windowSize = renderWindow->GetSize();


  double xc = center[0];    //计算图像中心位置x
  double yc = center[1];    //计算图像中心位置y
  double xd = (extent[1] - extent[0] + 1) * spacing[0];                  //图像物理宽
  double yd = (extent[3] - extent[2] + 1) * spacing[1];                  //图像物理高
  double d = camera->GetDistance();                                      //获得相机的距离
  double aspectwindow = static_cast<double>(windowSize[0]) / windowSize[1];  //计算窗口宽/高
  double aspectImage = xd / yd;                                              //图像宽/高 
  float RRR = 0;
  if (aspectImage < aspectwindow)           //如果窗口的宽高比较大      
  {
    RRR = xd / aspectImage;               
    camera->SetParallelScale(0.5 * RRR);   
  }
  else
  {
    RRR = xd / aspectwindow;
    camera->SetParallelScale(0.5 * RRR);
  }


  renderWindow->AddRenderer(renderertext);
  renderWindow->AddRenderer(renderer);
  auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
  vtkSmartPointer<myVtkInteractorStyleImage2> imagestyle =vtkSmartPointer<myVtkInteractorStyleImage2>::New();
  imagestyle->SetStatusMapper(sliceTextMapper);
  imagestyle->SetImageReslice(ImageReslice, extent[4], extent[5],mapToColors, interactor, renderer);
  interactor->SetInteractorStyle(imagestyle);
  interactor->SetRenderWindow(renderWindow);
  interactor->Initialize();
  renderWindow->Render();
  interactor->Start();
  return 0;
}

总结:如有哪位看到错误或者有更好的实现方式,请及时提醒我,“柯西的笔”公众号。希望可以获得更多的指导与学习,万分感谢。

以上就是本篇文章的全部内容,最后感谢阅读!



版权声明:本文为weixin_41202834原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。