今天的内容分为两个部分,一部分是使用dcmtk对dicom文件进行分析并且提取,第二个部分是使用vtk对dicom文件中的数据进行体绘制。
在读取文件的像素数据(PixelData)之前需要先预读取文件的信息,比如行列大小、像素间距、编码规则等等。
void Read_Dicom_Info(ImageInfo& imageInfo) {
std::string fileName = "Dcm_Data/1.dcm";
shared_ptr myFileFormat = make_shared();
if (myFileFormat->loadFile(fileName.c_str()).good()) {
DcmDataset* metaInfo = myFileFormat->getDataset();
//获取dcm文件中标签内容
metaInfo->findAndGetUint16(DCM_Rows, imageInfo.Rows);
metaInfo->findAndGetUint16(DCM_Columns, imageInfo.Columns);
metaInfo->findAndGetFloat64(DCM_PixelSpacing, imageInfo.Spacing[0]);
imageInfo.Spacing[1] = imageInfo.Spacing[0];
metaInfo->findAndGetFloat64(DCM_SpacingBetweenSlices, imageInfo.Spacing[2]);
imageInfo.TransferSyntaxUID = metaInfo->getOriginalXfer();
}
}
findAndGetUint16意思是获取unsigned short的数据,第一个参数是DcmTagKey,第二个参数是获取的数据。findAndGet+数据类型就会获取对应类型的数据,如可以使用findAndGetOFString来获取想要内容的字符串。
OFString ofData;
OFCondition status = metaInfo->findAndGetOFString(tagKey, ofData);
DcmTagKey现在有四千多个标签,可以去dcdeftag.h头文件中查看,下面只介绍用到的。
DCM_Rows:获取行大小;DCM_Columns:获取列大小;DCM_PixelSpacing:获取像素间距;
DCM_SpacingBetweenSlices:获取切片的间距也就是高度间距;
getOriginalXfer:获取编码规则,用来读像素数据时设置传输语义,目前dcmtk支持的TransferSyntax有41个,简单来说就是数据用的压缩算法。这里需要注意,如果所用的数据是有压缩的需要注册解码器,总结了下dcmtk的解码器有如下三个,英文解释我已注释。
DJLSDecoderRegistration::registerCodecs(); //Singleton class that registers decoders for all supported JPEG-LS processes
DJDecoderRegistration::registerCodecs(); //Singleton class that registers decoders for all supported JPEG processes
DcmRLEDecoderRegistration::registerCodecs(); //Singleton class that registers an RLE decoder
解码器释放:
DJLSDecoderRegistration::cleanup();
DJDecoderRegistration::cleanup();
DcmRLEDecoderRegistration::cleanup();
本篇文章使用的数据是无压缩的所以不需要注册解码器,原数据是EXS_JPEG2000的压缩算法,需要对dcmtk添加对应的支持解析出像素数据,所以我转换成了无压缩算法的数据来使用,原数据以及转码之后的数据我都会上传。
给DCMTK添加JPEG2000支持:给DCMTK添加JPEG2000支持_baiyou78357的博客-CSDN博客
void Read_Dicom_File(shared_ptr& pBuf, E_TransferSyntax transferSyntaxUID,int slice_num, int szie_single_img) {
for (int i = 1; i <= slice_num; i++)
{
std::string fileName = "/Dcm_Data/" + std::to_string(i) + ".dcm";
shared_ptr myFileFormat = make_shared();
if (myFileFormat->loadFile(fileName.c_str()).good()) {
DcmDataset* dataset = myFileFormat->getDataset();
if (dataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL).good()) {
DcmElement* pElement = nullptr;
OFCondition result = dataset->findAndGetElement(DCM_PixelData, pElement);
if (pElement != NULL && result.good()) {
result = pElement->loadAllDataIntoMemory();
if (result.good()) {
Uint16* ptr;
result = pElement->getUint16Array(ptr);
if (result.good()) {
memcpy(pBuf.get() + (i - 1) * szie_single_img, ptr, pElement->getLength());
}
}
}
}
}
}
}
chooseRepresentation:设置对应的传输语义,就是数据编码规则。
DCM_PixelData:获取像素数据。
体绘制是三维数据场产生屏幕上二维图像的技术。与面绘制不同,它不需提取体数据内部的等值面,像之前写的Marching Cube算法就是对等值面提取的一种方法,而体绘制是对三维体数据进行采样和合成的过程。体数据能过通过设置不透明度值和对像素值上色来显示体数据内部的不同成分和细节,体绘制是3D体数据可视化的主要技术,与平面图形相比,体图形在非均匀材料方面具有更大的表现范围。下图是两种渲染管线的对比图:
渲染出来不同的效果主要是vtkVolume中的参数调整。首先可以先设置插值方式、环境系数、反射等参数。
vtkNew volumeProperty;
volumeProperty->SetInterpolationTypeToLinear(); //设置线性插值方式
volumeProperty->ShadeOn(); //打开或者关闭阴影测试
volumeProperty->SetAmbient(0.4); //环境系数
volumeProperty->SetDiffuse(0.6); //漫反射
volumeProperty->SetSpecular(0.2); //镜面反射
设置不透明度,第一个参数为像素值,当前是ct值,第二个参数是透明度。
vtkNew compositeOpacity;
compositeOpacity->AddPoint(60, 0.00);
compositeOpacity->AddPoint(120, 0.50);
compositeOpacity->AddPoint(200, 1.00);
volumeProperty->SetScalarOpacity(compositeOpacity); //设置不透明度传输函数
此数据具体的ct值设置可以根据人体的ct值来设置显示效果
设置梯度不透明度
vtkNew volumeGradientOpacity;
volumeGradientOpacity->AddPoint(10, 0.0);
volumeGradientOpacity->AddPoint(60, 0.3);
volumeGradientOpacity->AddPoint(130, 0.5);
volumeGradientOpacity->AddPoint(200, 0.7);
volumeGradientOpacity->AddPoint(300, 1.0);
volumeProperty->SetGradientOpacity(volumeGradientOpacity);//设置梯度不透明度效果对比
设置颜色属性
vtkNew color;
color->AddRGBPoint(0.0, 0.00, 0.00, 0.00);
color->AddRGBPoint(60.0, 0.86, 0.078, 0.235);
color->AddRGBPoint(100.0, 0.95, 0.078, 0.10);
color->AddRGBPoint(250.0, 0.95, 0.843, 0);
color->AddRGBPoint(300.0, 1.0, 1.0, 1.0);
volumeProperty->SetColor(color);
cpp:
// VTKNII.cpp: 定义应用程序的入口点。
//
#include "VTKNII.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/dcmimgle/dcmimage.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dcrledrg.h"
#include /* for dcmjpeg decoders */
#include
#include //for JPEG-LS decode
#include //for JPEG-LS encode
using namespace std;
struct ImageInfo
{
unsigned int Slice_num;
unsigned short Rows;
unsigned short Columns;
double Spacing[3];
E_TransferSyntax TransferSyntaxUID;
};
void Read_Dicom_Info(ImageInfo& imageInfo) {
std::string fileName = "Dcm_Data/1.dcm";
shared_ptr myFileFormat = make_shared();
if (myFileFormat->loadFile(fileName.c_str()).good()) {
DcmDataset* metaInfo = myFileFormat->getDataset(); // 文件元信息
//获取dcm文件中标签内容
metaInfo->findAndGetUint16(DCM_Rows, imageInfo.Rows);
metaInfo->findAndGetUint16(DCM_Columns, imageInfo.Columns);
metaInfo->findAndGetFloat64(DCM_PixelSpacing, imageInfo.Spacing[0]);
imageInfo.Spacing[1] = imageInfo.Spacing[0];
metaInfo->findAndGetFloat64(DCM_SpacingBetweenSlices, imageInfo.Spacing[2]);
imageInfo.TransferSyntaxUID = metaInfo->getOriginalXfer();
}
}
void Read_Dicom_File(shared_ptr& pBuf, E_TransferSyntax transferSyntaxUID, int slice_num, int szie_single_img) {
for (int i = 1; i <= slice_num; i++)
{
std::string fileName = "Dcm_Data/" + std::to_string(i) + ".dcm";
shared_ptr myFileFormat = make_shared();
if (myFileFormat->loadFile(fileName.c_str()).good()) {
DcmDataset* dataset = myFileFormat->getDataset();
if (dataset->chooseRepresentation(EXS_LittleEndianExplicit/*transferSyntaxUID*/, NULL).good()) {
DcmElement* pElement = nullptr;
OFCondition result = dataset->findAndGetElement(DCM_PixelData, pElement);
if (pElement != NULL && result.good()) {
result = pElement->loadAllDataIntoMemory();
if (result.good()) {
Uint16* ptr;
result = pElement->getUint16Array(ptr);
if (result.good()) {
memcpy(pBuf.get() + (i - 1) * szie_single_img, ptr, pElement->getLength());
}
}
}
}
}
}
}
int main()
{
//DJLSDecoderRegistration::registerCodecs(); //Singleton class that registers decoders for all supported JPEG-LS processes
//DJDecoderRegistration::registerCodecs(); //Singleton class that registers decoders for all supported JPEG processes
//DcmRLEDecoderRegistration::registerCodecs(); //Singleton class that registers an RLE decoder
//DJLSDecoderRegistration::cleanup();
//DJDecoderRegistration::cleanup();
//DcmRLEDecoderRegistration::cleanup();
ImageInfo image_info{};
Read_Dicom_Info(image_info);
image_info.Slice_num = 361; //读入序列数
int single_img = image_info.Rows * image_info.Columns;
int size_all = image_info.Slice_num * single_img;
double origin[3] = { 0 };
shared_ptr pBuf(new short[size_all]);
Read_Dicom_File(pBuf, image_info.TransferSyntaxUID, image_info.Slice_num, single_img);
vtkNew dataArray;
dataArray->SetArray(pBuf.get(), size_all, 1);
vtkNew pImageData;
pImageData->AllocateScalars(VTK_SHORT, 1); // 该函数分配内存,具体的分配大小由两个函数参数共同决定
pImageData->SetDimensions(image_info.Rows, image_info.Columns, image_info.Slice_num); // 设置渲染数据的长宽高,俗称三维
pImageData->GetPointData()->SetScalars(dataArray); // 将目标数据拷贝到vtkImageData中
pImageData->SetSpacing(image_info.Spacing); // 设置三维的间隔,也就是两个像素点之间的间隔
pImageData->SetOrigin(origin); // 设置原点
vtkNew mapper;
mapper->SetInputData(pImageData);
vtkNew volume;
volume->SetMapper(mapper);
vtkNew volumeProperty;
volumeProperty->SetInterpolationTypeToLinear(); //设置线性插值方式
volumeProperty->ShadeOn(); //打开或者关闭阴影测试
volumeProperty->SetAmbient(0.4); //环境系数
volumeProperty->SetDiffuse(0.6); //漫反射
volumeProperty->SetSpecular(0.2); //镜面反射
//设置不透明度
vtkNew compositeOpacity;
compositeOpacity->AddPoint(60, 0.00);
compositeOpacity->AddPoint(120, 0.50);
compositeOpacity->AddPoint(200, 1.00);
volumeProperty->SetScalarOpacity(compositeOpacity); //设置不透明度传输函数
设置梯度不透明属性
vtkNew volumeGradientOpacity;
volumeGradientOpacity->AddPoint(10, 0.0);
volumeGradientOpacity->AddPoint(60, 0.3);
volumeGradientOpacity->AddPoint(130, 0.5);
volumeGradientOpacity->AddPoint(200, 0.7);
volumeGradientOpacity->AddPoint(300, 1.0);
volumeProperty->SetGradientOpacity(volumeGradientOpacity);//设置梯度不透明度效果对比
//设置颜色属性
vtkNew color;
color->AddRGBPoint(0.000, 0.00, 0.00, 0.00);
color->AddRGBPoint(60.0, 0.86, 0.078, 0.235);
color->AddRGBPoint(100.0, 0.95, 0.078, 0.10);
color->AddRGBPoint(250.0, 0.95, 0.843, 0);
color->AddRGBPoint(300.0, 1, 1, 1);
volumeProperty->SetColor(color);
volume->SetProperty(volumeProperty);
vtkNew ren;
ren->AddActor(volume);
ren->SetBackground(0, 0, 0);
vtkNew renWin;
renWin->AddRenderer(ren);
vtkNew iren;
vtkNew style;
iren->SetRenderWindow(renWin);
iren->SetInteractorStyle(style);
renWin->SetSize(600, 600);
renWin->Render();
iren->Start();
return 0;
}
CMakeLists.txt
# CMakeList.txt: VTKNII 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
find_package(VTK REQUIRED QUIET)
find_package(DCMTK REQUIRED)
if (VTK_VERSION VERSION_LESS "8.90.0")
# old system
include(${VTK_USE_FILE})
include_directories(${DCMTK_INCLUDE_DIR})
add_executable (VTKNII "VTKNII.cpp" "VTKNII.h")
target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})
target_link_libraries(${PROJECT_NAME} ${DCMTK_LIBRARIES})
else ()
# include all components
include(${VTK_USE_FILE})
include_directories(${DCMTK_INCLUDE_DIR})
add_executable (VTKNII "VTKNII.cpp" "VTKNII.h")
target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})
target_link_libraries(${PROJECT_NAME} ${DCMTK_LIBRARIES})
# vtk_module_autoinit is needed
vtk_module_autoinit(
TARGETS ${PROJECT_NAME}
MODULES ${VTK_LIBRARIES}
)
endif ()
# TODO: 如有需要,请添加测试并安装目标。
赚点积分,没积分的留下邮箱,hhhh。
DICOM_DARASET.zip-医疗文档类资源-CSDN下载