DICOM 檔案介紹
DICOM 全名叫 Digital Imaging and Communications in Medicine(醫學數字圖像與通信),是醫學圖像和相關資訊的過節标準,也定義了臨床所需資料醫學圖像格式;
DICOM 資料格式包含大量屬性,例如病人名字、ID、年齡等基本資訊,這些資訊從儲存在 “頭檔案” 中,這種儲存方式避免了病人與圖像誤配;
一個病人樣本 包含多個 DICOM 子檔案,單個 DICOM 對象隻包含具有一幀像素資料,幀與幀之間以序列形式存在,可以來回切換形成類似于電影放映的情景,類似于把一個3D 圖像掃描成像,沿着垂直方向等厚度分割得到

ITK 對 DICOM 資料進行讀取
ITK 根據 DICOM 檔案讀取方式有兩種類型,單張 DICOM 讀取或 序列 DICOM 讀取
ITK 讀取單張 DICOM 檔案
- itkImageSeriesReader;
- itkGDCMImageIO;
讀取後可以通過 itkGCOMImageIO 中 Get*** 函數擷取病人相關資訊,并列印輸出,這裡需要注意一點,DICOM 對應的資料類型為 signed short;核心代碼:
string DirectoryPath = "D:/Data_Training/Dicom_data/215020265/AT000002.152046493.dcm";
typedef signed short InternalPixelType;
const unsigned int Dimension = 2;
using InternalImageType = itk::Image<InternalPixelType, Dimension>;
typedef itk::Image<InternalPixelType, Dimension> ImageType;
typedef itk::ImageSeriesReader<ImageType> ReaderType;
typedef itk::GDCMImageIO ImageIOType;
ImageIOType::Pointer gdcmIO = ImageIOType::New();
ReaderType::Pointer reader = ReaderType::New();
reader->SetImageIO(gdcmIO);
reader->SetFileNames(DirectoryPath);
// Reading Data;
try
{
reader->Update();
reader->GetMetaDataDictionary();
gdcmIO->GetMetaDataDictionary();// 讀取頭檔案資訊;
//資訊指派
char* name = new char[50];
char* patientID = new char[50];
char* time = new char[50];
char* manufacture = new char[50];
char* modility = new char[50];
char* hospital = new char[50];
char* sex = new char[50];
char* age = new char[50];
char* description = new char[100];
int pixelType = gdcmIO->GetPixelType();
int componentType = gdcmIO->GetComponentType();
int fileType = gdcmIO->GetFileType();
ImageIOType::ByteOrder byteOrder;
byteOrder = gdcmIO->GetByteOrder();
unsigned int dim = 0;
gdcmIO->GetDimensions(dim);
ImageIOType::SizeType imgsize;
imgsize = gdcmIO->GetImageSizeInPixels();
int componetSize = gdcmIO->GetComponentSize();
int dimension = gdcmIO->GetNumberOfDimensions();
int ori = 0;
gdcmIO->GetOrigin(ori);
int spa = 0;
gdcmIO->GetPatientSex(sex);
gdcmIO->GetPatientAge(age);
gdcmIO->GetStudyDescription(description);
gdcmIO->GetSpacing(spa);
gdcmIO->GetPatientName(name);
gdcmIO->GetModality(modility);
gdcmIO->GetPatientID(patientID);
gdcmIO->GetManufacturer(manufacture);
gdcmIO->GetStudyDate(time);
ImageIOType::TCompressionType compressType;
compressType = gdcmIO->GetCompressionType();
gdcmIO->GetInstitution(hospital);
ImageType::SpacingType spacetype;
spacetype = reader->GetOutput()->GetSpacing();
ImageType::PointType origin;
origin = reader->GetOutput()->GetOrigin();
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "description:" << description << endl;
cout << "hospital:" << hospital << endl;
cout << "modility:" << modility << endl;
cout << "manfacture:" << manufacture << endl;
cout << "dim:" << dim << endl;
cout << "origin:" << origin << endl;
cout << "Time:" << time << endl;
cout << "patientID:" << patientID << endl;
}
catch (const itk::ExceptionObject& excp)
{
cout << " Reading Exceptaion Caught" << endl;
cout << excp.what() << endl;
return EXIT_FAILURE;
}
ITK 讀取序列 DICOM 檔案
- itkImageSeriesReader;
- itkGDCMImageIO;
- itkGDCMSeriesFileNames;
與單張讀取不同的是,多了一個 itkGDCMSeriesFileNames 類,用于打包 DICOM 序列檔案,再傳入 GDCMImageIO 進行讀取
string DirectoryPath = "D:/Data_Training/Dicom_data/215020265";
typedef signed short InternalPixelType;
const unsigned int Dimension = 3;
using InternalImageType = itk::Image<InternalPixelType, Dimension>;
typedef itk::Image<InternalPixelType, Dimension> ImageType;
typedef itk::ImageSeriesReader<ImageType> ReaderType;
typedef itk::GDCMImageIO ImageIOType;
typedef itk::GDCMSeriesFileNames NamesGeneratorType;
ImageIOType::Pointer gdcmIO = ImageIOType::New();
NamesGeneratorType::Pointer namesGenerator = NamesGeneratorType::New();
namesGenerator->SetInputDirectory(DirectoryPath);
const ReaderType::FileNamesContainer & filenames =
namesGenerator->GetInputFileNames();
std::size_t numberOfFileNames = filenames.size();
std::cout << numberOfFileNames << std::endl;
ReaderType::Pointer reader = ReaderType::New();
reader->SetImageIO(gdcmIO);
reader->SetFileNames(filenames);
// Reading Data;
try
{
reader->Update();
reader->GetMetaDataDictionary();
gdcmIO->GetMetaDataDictionary();// 讀取頭檔案資訊;
//資訊指派
char* name = new char[50];
char* patientID = new char[50];
char* time = new char[50];
char* manufacture = new char[50];
char* modility = new char[50];
char* hospital = new char[50];
char* sex = new char[50];
char* age = new char[50];
char* description = new char[100];
int pixelType = gdcmIO->GetPixelType();
int componentType = gdcmIO->GetComponentType();
int fileType = gdcmIO->GetFileType();
ImageIOType::ByteOrder byteOrder;
byteOrder = gdcmIO->GetByteOrder();
unsigned int dim = 0;
gdcmIO->GetDimensions(dim);
ImageIOType::SizeType imgsize;
imgsize = gdcmIO->GetImageSizeInPixels();
int componetSize = gdcmIO->GetComponentSize();
int dimension = gdcmIO->GetNumberOfDimensions();
int ori = 0;
gdcmIO->GetOrigin(ori);
int spa = 0;
gdcmIO->GetPatientSex(sex);
gdcmIO->GetPatientAge(age);
gdcmIO->GetStudyDescription(description);
gdcmIO->GetSpacing(spa);
gdcmIO->GetPatientName(name);
gdcmIO->GetModality(modility);
gdcmIO->GetPatientID(patientID);
gdcmIO->GetManufacturer(manufacture);
gdcmIO->GetStudyDate(time);
ImageIOType::TCompressionType compressType;
compressType = gdcmIO->GetCompressionType();
gdcmIO->GetInstitution(hospital);
ImageType::SpacingType spacetype;
spacetype = reader->GetOutput()->GetSpacing();
ImageType::PointType origin;
origin = reader->GetOutput()->GetOrigin();
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "description:" << description << endl;
cout << "hospital:" << hospital << endl;
cout << "modility:" << modility << endl;
cout << "manfacture:" << manufacture << endl;
cout << "dim:" << dim << endl;
cout << "origin:" << origin << endl;
cout << "Time:" << time << endl;
cout << "patientID:" << patientID << endl;
}
catch (const itk::ExceptionObject& excp)
{
cout << " Reading Exceptaion Caught" << endl;
cout << excp.what() << endl;
return EXIT_FAILURE;
}
運作結果如下:
除了上面病人年齡、性别、ID 基本資訊之外,用 itkGDCMImageIO 擷取更多病人相關資訊,這裡從itkGDCMImageIO.h 源碼中擷取到了下面函數,大家可以參考一下:
VTK 讀取 DICOM
vtk 讀取 DICOM 就比較簡單了,因為 VTK 封裝的有相關 DICOM 讀取類: vtkDICOMImageReader,可直接進行操作;
VTK讀取單張DICOM檔案并進行預覽
- vtkDICOMImageReader
#include<vtkSmartPointer.h>
#include<vtkImageViewer2.h>
#include<vtkDICOMImageReader.h>
#include<vtkRenderWindow.h>
#include<vtkRenderWindowInteractor.h>
#include<vtkRenderer.h>
#include<iostream>
#include<string.h>
#include<vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
int main()
{
using namespace std;
string OpenPath = "D:/Data_Training/Dicom_data/215020265/AT000001.151955287.dcm";
vtkSmartPointer<vtkDICOMImageReader> reader =
vtkSmartPointer<vtkDICOMImageReader>::New();
reader->SetFileName(OpenPath.c_str());
reader->Update();
vtkSmartPointer<vtkImageViewer2> imageViewer =
vtkSmartPointer<vtkImageViewer2>::New();
imageViewer->SetInputConnection(reader->GetOutputPort());
vtkSmartPointer<vtkRenderWindowInteractor> renWin =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
imageViewer->SetupInteractor(renWin);
imageViewer->Render();
imageViewer->GetRenderer()->ResetCamera();
imageViewer->Render();
renWin->Start();
return EXIT_SUCCESS;
}
因為加入了一個互動視窗,是以可利用放大縮小
VTK讀取序列DICOM檔案并進行預覽
- vtkDICOMImageReader
用法與單張讀取相似,差別是序列圖這裡讀取的檔案夾,而單張讀取的是單張 DICOM;
#include<vtkSmartPointer.h>
#include<vtkImageViewer2.h>
#include<vtkDICOMImageReader.h>
#include<vtkRenderWindow.h>
#include<vtkRenderWindowInteractor.h>
#include<vtkRenderer.h>
#include<vtkTextProperty.h>
#include<vtkTextMapper.h>
#include<vtkActor2D.h>
#include<vtkInteractorStyleImage.h>
#include<vtkObjectFactory.h>
#include<iostream>
#include<string.h>
#include<vtkAutoInit.h>
#include<sstream>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingFreeType)
using namespace std;
class StatusMessage{
public:
static std::string Format(int slice, int maxSlice)
{
std::stringstream tmp;
tmp << "Slice Number " << slice + 1 << "/" << maxSlice + 1;
return tmp.str();
}
};
// Define own interation style;
class myVtkInteractorStyleImage : public vtkInteractorStyleImage
{
public:
static myVtkInteractorStyleImage* New();
vtkTypeMacro(myVtkInteractorStyleImage, vtkInteractorStyleImage);
protected:
vtkImageViewer2* _ImageViewer;
vtkTextMapper* _StatusMapper;
int _Slice;
int _MinSlice;
int _MaxSlice;
public:
void SetImageViewer(vtkImageViewer2* imageViwer)
{
_ImageViewer = imageViwer;
_MinSlice = imageViwer->GetSliceMin();
_MaxSlice = imageViwer->GetSliceMax();
_Slice = _MinSlice;
cout << "Slice : Min = " << _MinSlice << ", Max = " << _MaxSlice << endl;
}
void SetStatusMapper(vtkTextMapper* statusMapper)
{
_StatusMapper = statusMapper;
}
protected:
void MoveSliceForward()
{
if (_Slice<_MaxSlice)
{
_Slice += 1;
cout << " MoveSliceForward::Slice = " << _Slice << endl;
_ImageViewer->SetSlice(_Slice);
std::string msg = StatusMessage::Format(_Slice, _MaxSlice);
_StatusMapper->SetInput(msg.c_str());
_ImageViewer->Render();
}
}
void MoveSliceBackward()
{
if(_Slice>_MinSlice)
{
_Slice -= 1;
cout << "MoveSliceBackward::Slice = " << _Slice << endl;
_ImageViewer->SetSlice(_Slice);
std::string msg = StatusMessage::Format(_Slice, _MaxSlice);
_StatusMapper->SetInput(msg.c_str());
_ImageViewer->Render();
}
}
virtual void OnKeyDown()
{
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();
}
}
};
vtkStandardNewMacro(myVtkInteractorStyleImage);
int main()
{
using namespace std;
string OpenPath = "D:/Data_Training/Dicom_data/215020265/";
vtkSmartPointer<vtkDICOMImageReader> reader =
vtkSmartPointer<vtkDICOMImageReader>::New();
reader->SetDirectoryName(OpenPath.c_str());
reader->Update();
vtkSmartPointer<vtkImageViewer2> imageViewer =
vtkSmartPointer<vtkImageViewer2>::New();
imageViewer->SetInputConnection(reader->GetOutputPort());
//Slice Status
vtkSmartPointer<vtkTextProperty> sliceTextProp =
vtkSmartPointer<vtkTextProperty>::New();
sliceTextProp->SetFontFamilyToCourier();
sliceTextProp->SetFontSize(20);
sliceTextProp->SetVerticalJustificationToBottom();
sliceTextProp->SetJustificationToLeft();
vtkSmartPointer<vtkTextMapper> sliceTextMapper =
vtkSmartPointer<vtkTextMapper>::New();
string msg = StatusMessage::Format(imageViewer->GetSliceMin(), imageViewer->GetSliceMax());
sliceTextMapper->SetInput(msg.c_str());
cout << "msg" << msg.c_str() << endl;
sliceTextMapper->SetTextProperty(sliceTextProp);
vtkSmartPointer<vtkActor2D> sliceTextActor =
vtkSmartPointer<vtkActor2D>::New();
sliceTextActor->SetMapper(sliceTextMapper);
sliceTextActor->SetPosition(15, 10);
//usage hint message;
vtkSmartPointer<vtkTextProperty> usageTextProp =
vtkSmartPointer<vtkTextProperty>::New();
usageTextProp->SetFontFamilyToCourier();
usageTextProp->SetFontSize(14);
usageTextProp->SetVerticalJustificationToTop();
usageTextProp->SetJustificationToLeft();
vtkSmartPointer<vtkTextMapper> usageTextMapper =
vtkSmartPointer<vtkTextMapper>::New();
usageTextMapper->SetInput("Slice with mouse wheel\n or Up/Down-Key\n =Zoom weh pressed right\n mouse button while dragging");
usageTextMapper->SetTextProperty(usageTextProp);
vtkSmartPointer<vtkActor2D> usageTextActor =
vtkSmartPointer <vtkActor2D>::New();
usageTextActor->SetMapper(usageTextMapper);
usageTextActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedDisplay();
usageTextActor->GetPositionCoordinate()->SetValue(0.05, 0.95);
vtkSmartPointer<myVtkInteractorStyleImage> myInteractorStyle =
vtkSmartPointer<myVtkInteractorStyleImage>::New();
myInteractorStyle->SetImageViewer(imageViewer);
myInteractorStyle->SetStatusMapper(sliceTextMapper);
vtkSmartPointer<vtkRenderWindowInteractor> renWin =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
imageViewer->SetupInteractor(renWin);
renWin->SetInteractorStyle(myInteractorStyle);
imageViewer->GetRenderer()->AddActor2D(sliceTextActor);
imageViewer->GetRenderer()->AddActor2D(usageTextActor);
imageViewer->Render();
imageViewer->GetRenderer()->ResetCamera();
imageViewer->Render();
renWin->Start();
return EXIT_SUCCESS;
}
代碼摘抄于官方,這裡定義了一個自定義互動類,可以通過滑動滑鼠或者按鍵來完成切片更換。并且通過類 vtkTextMapper 在界面上添加了兩個文本注釋,友善引導使用者
預覽效果如下:
(文章首發于公衆号:Z先生點記)
Reference:
1,https://itk.org/Doxygen/html/classitk_1_1ImageSeriesReader.html
2,https://lorensen.github.io/VTKExamples/site/Cxx/IO/ReadDICOMSeries/`