vtk中也有dicom读写类,但是只能完成单帧DICOM数据读取,这对于实际应用是非常不利的。
而ITK可以实现dicom多帧读取,通常情况ITK与VTK也会结合使用(ITK处理,VTK显示),所以就一起学习下吧!
一、编译ITK
因为之后要将ITK与VTK混合编程,所以先编译生成VTK之后再进行ITK编译。
详情参考以下博文,这里不再赘述。
ITK 5.0.1 在Windows10+VS2017+CMake 环境下编译安装
ITK-001-5.2.0版本源码-编译
不过我在这里陷入了一个错误,给大家提个醒:最好不要随意改动生成好的VTK,因为ITK cmake时读取VTK中的cmake文件,会调取一些路径,如果路径被改变则会报错,不好找到原因。
另外需要注意的,在CMAKE_DEBUG_POSTFIX后面添加d这样进行Debug版本编译时会在库文件名后面多个d。
二、利用ITK实现多帧DICOM读取
先贴一下这段的代码:
itk::ImageBase<3>::Pointer DicomReadWrite::DicomReadSeries(const QString &path)
{
itk::ImageBase<3>::Pointer outImage;
// 检查路径是否有效,检查文件是否存在
if (path.isEmpty() || !QFile::exists(path)) {
return outImage;
}
std::string DirectPath = path.toStdString();
// 图像类型
using PixelType = unsigned char; // dicom图像常见像素类型
constexpr unsigned int Dimension = 3; //维度为3
using ImageType = itk::Image<PixelType, Dimension>;
// 创建dicom图像读取器
using ReaderType = itk::ImageSeriesReader<ImageType>;
ReaderType::Pointer reader = ReaderType::New();
reader->SetFileName(DirectPath);
//创建GDCMImageIO对象与reader相连
using ImageIOType = itk::GDCMImageIO;
ImageIOType::Pointer gdcmImageIO = ImageIOType::New();
reader->SetImageIO(gdcmImageIO);
// 读取
try
{
// 用序列图像reader的Update出发读取程序
reader->Update();
// 获取读取的图像
outImage = reader->GetOutput();
}
catch (const itk::ExceptionObject& excp)
{
std::cerr << "Exception thrown while writing the image" << std::endl;
std::cerr << excp << std::endl;
}
return outImage;
}
我读的是一个dicom数据,包含了连续多帧的灰度图序列,非彩色的,上述代码也可以读单帧图像。返回值是一个ImageBase类型。itk::ImageBase是itk::Image的基类,它可以不用事先定义Type类型(但这也是有风险的,尽量用在已知确定Type的地方),不然要作为参数返回的话,还要先定义一下ImageType。
包含的头文件如下:
#include "itkGDCMImageIO.h"
#include "itkGDCMSeriesFileNames.h"
#include "itkImageSeriesReader.h"
#include <QString>
#include<QFile>
ITK读写dicom是基于GDCM库的,GDCMImageIO是一个读取和写入DICOM V3和ACR/NEMA图像的ImageIO类。
在此编译过程中遇到了一个错误,就是“无法解析该符号”,这一般就是添加的库目录以及包含的lib不正确造成的,不想一个个去试了就直接将所有lib都添加到附加依赖项了,解决此问题。
三、ITK数据向VTK数据转换
使用itk::vtkImageToVTKImageFilter可以将itk::Image转换喂vtkImageData,相反的,itk::VTKImageToImageFilter将vtkImageData转换喂itk::Image,这要比访问像素的方法要简单快速的多。
参考链接🔗使用VTK读取DICOM数据及其与DCMTK/ITK数据的转换
vtkSmartPointer<vtkImageData> VtkAndItk::ItkImages2VtkImages(itk::ImageBase<3>::Pointer inputImage)
{
using PixelType = unsigned char;
constexpr unsigned int Dimension = 3;
using ImageType = itk::Image<PixelType, Dimension>;
// 动态类型转换,将 image 转换为 itk::Image 指针
ImageType::Pointer itkImage = dynamic_cast<itk::Image<PixelType, Dimension>*>(inputImage.GetPointer());
if (itkImage.IsNull())
return vtkSmartPointer<vtkImageData>::New();
// 利用tovtkfilter转换至vtk图像
itk::ImageToVTKImageFilter<ImageType>::Pointer itkToVtkFilter = itk::ImageToVTKImageFilter<ImageType>::New();
itkToVtkFilter->SetInput(itkImage);
itkToVtkFilter->UpdateLargestPossibleRegion();
itkToVtkFilter->Update();
vtkSmartPointer<vtkImageData>vtkImage = vtkSmartPointer<vtkImageData>::New();
vtkImage = itkToVtkFilter->GetOutput();
return vtkImage;
}
三、显示VTKImageData三维数据
参考链接
vtk体绘制代码报错的解决办法(代码在vtk7,8,9中都能运行),以及VTK数据集网站
由于我们要显示的试一个3维的数据,所以这里使用了vtkFixedPointVolumeRayCastMapper进行体绘制。
我一开始是直接使用的vtkVolumeMapper,结果在New时报错“无法从“initializer list”转换为“vtkSmartPointer””,这个是不行的,因为vtkVolumeMapper是一个抽象基类,无法创建vtkVolumeMapper实例。应该选择一个具体的子类来创建实例。
void DispOnVtkActor(vtkImageData *imagesData)
{
// 创建一个actor,用于在渲染器中渲染图像
//vtkSmartPointer<vtkImageActor> actor = vtkImageActor::New();
//actor->SetInputData(imagesData);
// 不透明度
vtkSmartPointer<vtkPiecewiseFunction>compoisteOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
compoisteOpacity->AddPoint(70, 0.00);
compoisteOpacity->AddPoint(90, 1.00);
// 梯度不透明度
// 体积属性
vtkSmartPointer<vtkVolumeProperty>volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
//volumeProperty->ShadeOff();//关闭阴影
//volumeProperty->SetAmbient(0.4);// 设置环境光系数
//volumeProperty->SetDiffuse(0.6);// 设置散射光系数
//volumeProperty->SetSpecular(0.2);// 设置反射系数
//volumeProperty->SetSpecularPower(0.1);// 高光强度
volumeProperty->SetScalarOpacity(compoisteOpacity);
// 创建提体积映射器
vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>mapper = vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New();
mapper->SetInputData(imagesData);
// 创建体积
vtkSmartPointer<vtkVolume>volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(mapper);
volume->SetProperty(volumeProperty);
//actor->SetMapper(mapper);
// 创建一个渲染器
vtkSmartPointer<vtkRenderer>render = vtkSmartPointer<vtkRenderer>::New();
//render->AddActor(actor);
render->AddVolume(volume);
render->SetBackground(.1, .2, .3);//设置背景颜色
// 创建一个渲染窗口
vtkSmartPointer<vtkRenderWindow>renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(render);
renderWindow->SetSize(1024,2048);
// 创建一个渲染窗口交互器,并设置渲染窗口
vtkSmartPointer<vtkRenderWindowInteractor>renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
//设置交互器样式,允许3D视角切换
//vtkSmartPointer<vtkInteractorStyleImage> style =vtkSmartPointer<vtkInteractorStyleImage>::New();
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
//开始渲染和交互
renderWindowInteractor->SetInteractorStyle(style);
renderWindowInteractor->SetRenderWindow(renderWindow);
renderWindowInteractor->Initialize();
//renderWindow->Render();
renderWindowInteractor->Start();
}
另外,运行上面的显示程序时,发生了异常中断,“读取访问权限冲突”,
解决办法就是在你的程序中添加以下内容:
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
有点不理解,这几个模块还要单独手动初始化,不咋友好。有知道的朋友可以留言~
问了百度助手,给出的解释如下: