使用DCMTK实现DICOM文件的读写

视频教程Part1:https://www.bilibili.com/video/av66260004
视频教程Part2:https://www.bilibili.com/video/av68004639

创建Dicom_Module的Win32 DLL工程并设置

  • 创建名为Dicom_Module的Win32 DLL工程,并配置include目录和lib目录(dcmtk和boost)
  • 配置依赖库
  • 将工程的字符集改为多字节字符集
  • 为了方便,将Dicom_Module的输出目录设置到自定义的\Learning\DICOM\Module_Sample目录下

创建Dicom_Sample的Win32 Console工程并设置

  • 创建名为Dicom_Sample的Win32 Console工程,并配置include目录和lib目录,用来使用Dicom_Module
  • 为了方便,将Dicom_Sample的输出目录也设置到自定义的E:\Learning\DICOM\Module_Sample目录下
  • 将DCMTK库bin目录下的dcmdata.dll, oflog.dll, ofstd.dll拷贝到E:\Learning\DICOM\Module_Sample目录下,同时拷贝示例Dicom文(brain_005)E:\Learning\DICOM\Module_Sample目录下

Dicom_Module工程里编写所需类和函数

DicomInfo定义DicomModule所需数据结构,常量,枚举等

  • 公有数据结构Patient,Study,Series,Image
  • 私有数据结构PrivateData
  • 常量和枚举

Patient信息

struct Patient
{
    std::string                             PatientsName;
    std::string                             PatientID;
    std::chrono::system_clock::time_point   PatientsBirthDate;
    std::string                             PatientsSex;
    std::string                             OtherPatientNames;
    std::string                             PatientsWeight;
    std::string                             PatientComments;
};

Study信息

struct Study
{
    std::string                             StudyInstanceUID;
    std::string                             StudyID;
    std::chrono::system_clock::time_point   StudyDateTime;
    std::string                             AccessionNumber;
    std::string                             StudyDescription;
};

Series信息

struct Series
{
    std::string                             SeriesInstanceUID;
    std::string                             SeriesNumber;
    std::chrono::system_clock::time_point   SeriesDateTime;
    std::string                             PerformingPhysicianName;
    std::string                             ProtocolName;
    std::string                             BodyPartExamined;
};

Image信息

struct Image
{

    unsigned int                            SamplesPerPixel;
    std::string                             PhotometricInterpretation;
    unsigned int                            Rows; 
    unsigned int                            Columns; 
    unsigned int                            BitsAllocated;
    unsigned int                            BitsStored;
    unsigned int                            HighBit;
    unsigned int                            PixelRepresentation;
    unsigned short*                         PixelData;
};

Private信息

struct PrivateData
{
    uint16_t                                GroupTag;
    uint16_t                                ElementTag;
    std::string                             Name;
    std::string                             VR;
    std::string                             Value;
};

一些常量和枚举

#define PRIVATE_GROUP_NAME "Private Group"

enum UID_Type 
{
    UID_Study = 1,
    UID_Series = 2,
    UID_Image = 3,
    UID_Other = 4,
};

enum DataVR_Type
{
    DataVRType_CS = 1,
    DataVRType_SH,
    DataVRType_LO,
    DataVRType_ST,
    DataVRType_LT,
    DataVRType_UT,
    DataVRType_AE,
    DataVRType_PN,
    DataVRType_UI,
    DataVRType_DA,
    DataVRType_TM,
    DataVRType_DT,
    DataVRType_AS,
    DataVRType_IS,
    DataVRType_DS,
    DataVRType_SS,
    DataVRType_US,
    DataVRType_SL,
    DataVRType_UL,
    DataVRType_AT,
    DataVRType_FL,
    DataVRType_FD,
    DataVRType_OB,
    DataVRType_OW,
    DataVRType_OF,
    DataVRType_SQ,
    DataVRType_UN
};

接口类DicomIF,导出外部可调用的接口类和成员函数

class __declspec(dllexport) IDicom
{
public:
    virtual ~IDicom() {}
    virtual bool Read(std::string filePath) = 0;
    virtual bool Save(std::string filePath) = 0;
    virtual void Reset() = 0;

    virtual void MetaInfoTags() = 0;
    virtual void DataSetTags() = 0;

    virtual std::shared_ptr<Patient>    PatientInfo() = 0;
    virtual std::shared_ptr<Study>      StudyInfo() = 0;
    virtual std::shared_ptr<Series>     SeriesInfo() = 0;
    virtual std::shared_ptr<Image>      ImageInfo() = 0;
    virtual std::list<PrivateData>&     PrivateDataInfo() = 0;
};

实现类DicomProcessor,实现Dicom文件的读写功能

class __declspec(dllexport) DicomProcessor : public IDicom
{
public:
    DicomProcessor();
    ~DicomProcessor();

    bool Read(std::string filePath);
    bool Save(std::string filePath);
    void Reset();

    inline std::shared_ptr<Patient> PatientInfo()
    {
        return m_patient;
    }

    inline std::shared_ptr<Study> StudyInfo()
    {
        return m_study;
    }

    inline std::shared_ptr<Series> SeriesInfo()
    {
        return m_series;
    }

    inline std::shared_ptr<Image> ImageInfo()
    {
        return m_image;
    }

    inline std::list<PrivateData> &PrivateDataInfo()
    {
        return m_privateData;
    }

    void MetaInfoTags();
    void DataSetTags();
private:
    void GetPatient();
    void GetStudy();
    void GetSeries();
    void GetImage();
    void GetPrivateData();

    void FillPatient();
    void FillStudy();
    void FillSeries();
    void FillImage();
    void FillPrivateData();

    void GetInfo(DcmTagKey tagKey, std::string &info);
    void GetInfo(DcmTagKey tagKey, unsigned int &info);
    void GetDateTimeInfo(DcmTagKey tagKey, DcmTagKey tagTimeKey, std::chrono::system_clock::time_point &info);
    void FillInfo(DcmTagKey tagKey, std::string &info);
    void RegisterPrivateTags(PrivateData data);
    void FreePixelData();

    std::string                 m_fileName;
    DcmFileFormat               m_fileformat;
    std::shared_ptr<Patient>    m_patient;
    std::shared_ptr<Study>      m_study;
    std::shared_ptr<Series>     m_series;
    std::shared_ptr<Image>      m_image;
    std::list<PrivateData>      m_privateData;
};
#include "DicomProcessor.h"
#include "DicomUtils.h"

DicomProcessor::DicomProcessor()
{
    Reset();
}


DicomProcessor::~DicomProcessor()
{
    FreePixelData();
}

void DicomProcessor::Reset()
{
    m_patient.reset(new Patient());
    m_study.reset(new Study());
    m_series.reset(new Series());
    m_image.reset(new Image());
    m_privateData.clear();
    FreePixelData();
}

bool DicomProcessor::Read(std::string filePath)
{
    OFCondition status = m_fileformat.loadFile(filePath.c_str());
    if (!status.good())
    {
        std::cout << "Read dimcom file error:" << status.text() << ",file: " << filePath << std::endl;
        return false;
    }
    try
    {
        GetPatient();
        GetStudy();
        GetSeries();
        GetImage();
        GetPrivateData();
    }
    catch (...)
    {
        std::cout << "Get dimcom info error!" << std::endl;
        FreePixelData();
        return false;
    }
    std::cout << "Read dimcom file succeed!" << std::endl;
    return true;
}

bool DicomProcessor::Save(std::string filePath)
{
    try
    {
        FillPatient();
        FillStudy();
        FillSeries();
        FillImage();
        FillPrivateData();
    }
    catch (const std::exception&)
    {
        std::cout << "Fill dimcom info error!" << std::endl;
        return false;
    }

    OFCondition status = m_fileformat.saveFile(filePath.c_str());
    if (!status.good())
    {
        std::cout << "Save dimcom file error, file: " << filePath << std::endl;
        return false;
    }

    std::cout << "Save dimcom file succeed!" << std::endl;
    return true;
}

void DicomProcessor::MetaInfoTags()
{
    std::cout << "Meta Tag-----------------------------Meta Tag" << std::endl;
    DcmObject* item = m_fileformat.getMetaInfo()->nextInContainer(NULL);
    while (item)
    {
        DcmVR valueVR(item->getVR());
        DcmTag tag(item->getTag());

        std::cout << item->getTag().toString().c_str() << "    " << valueVR.getVRName() << "    " << tag.getTagName() << std::endl;
        item = m_fileformat.getMetaInfo()->nextInContainer(item);
    }
}

void DicomProcessor::DataSetTags()
{
    std::cout << "DataSet Tag------------------------------DataSet Tag" << std::endl;
    DcmObject* item = m_fileformat.getDataset()->nextInContainer(NULL);
    while (item)
    {
        DcmVR valueVR(item->getVR());
        DcmTag tag(item->getTag());
        std::cout << item->getTag().toString().c_str() << "    " << valueVR.getVRName() << "    " << tag.getTagName() << std::endl;
        item = m_fileformat.getDataset()->nextInContainer(item);
    }
}


void DicomProcessor::GetPatient()
{
    GetInfo(DCM_PatientID, m_patient->PatientID);
    GetInfo(DCM_PatientName, m_patient->PatientsName);
}

void DicomProcessor::GetStudy()
{
    GetInfo(DCM_StudyInstanceUID, m_study->StudyInstanceUID);
    if (m_study->StudyInstanceUID.empty())
    {
        std::cout << "Get Tag DCM_StudyInstanceUID Error!" << std::endl;
    }
}

void DicomProcessor::GetSeries()
{
    GetDateTimeInfo(DCM_SeriesDate, DCM_SeriesTime, m_series->SeriesDateTime);
    GetInfo(DCM_BodyPartExamined, m_series->BodyPartExamined);
}

void DicomProcessor::GetImage()
{
    GetInfo(DCM_SamplesPerPixel, m_image->SamplesPerPixel);
    GetInfo(DCM_PhotometricInterpretation, m_image->PhotometricInterpretation);
    GetInfo(DCM_Rows, m_image->Rows);
    GetInfo(DCM_Columns, m_image->Columns);
    GetInfo(DCM_BitsAllocated, m_image->BitsAllocated);
    GetInfo(DCM_BitsStored, m_image->BitsStored);
    GetInfo(DCM_HighBit, m_image->HighBit);
    GetInfo(DCM_PixelRepresentation, m_image->PixelRepresentation);

    uint pixelByteCount = (m_image->BitsAllocated <= 8) ? 1 : 2;
    ulong dataLength = m_image->Rows * m_image->Columns * pixelByteCount * m_image->SamplesPerPixel;

    DcmElement* element = NULL;
    OFCondition status = m_fileformat.getDataset()->findAndGetElement(DCM_PixelData, element);
    if (!status.good())
    {
        std::cout<< "Get pixel data element error:" << status.text() << std::endl;
    }
    else
    {
        //std::cout << "Pixel data element's length:" << element->getLength() << std::endl;
        
        unsigned short* pData = NULL;
        status = element->getUint16Array(pData);
        if (!status.good())
        {
            std::cout << "Get pixel data array error:" << status.text() << std::endl;
            return;
        }
        FreePixelData();
        m_image->PixelData = (uint16_t *)malloc(dataLength);
        memcpy(m_image->PixelData, pData, dataLength);
    }
}

void DicomProcessor::GetPrivateData()
{
    DcmTagKey tagKey;
    OFString value;
    DcmElement* element = NULL;

    for (auto &data : m_privateData)
    {
        tagKey.set((Uint16)data.GroupTag, (Uint16)data.ElementTag);
        value.clear();
        element = NULL;
        OFCondition status = m_fileformat.getDataset()->findAndGetElement(tagKey, element);

        if (!status.good())
        {
            std::ios::fmtflags fmt(std::cout.flags());
            std::cout << "Get private Tag(" << std::hex << data.GroupTag << "," << data.ElementTag << ") error:" << status.text() << std::endl;
            std::cout.flags(fmt);
        }
        else
        {
            status = element->getOFString(value, 0);
            if (!status.good())
            {
                std::ios::fmtflags fmt(std::cout.flags());
                std::cout << "Get private Tag(" << std::hex << data.GroupTag << "," << data.ElementTag << ") error:" << status.text() << std::endl;
                std::cout.flags(fmt);
            }
            else
            {
                data.Value = std::string(value.c_str());
                DcmVR valueVR(element->getVR());
                data.VR = std::string(valueVR.getVRName());
            }
        }
    }
}


void DicomProcessor::FillPatient()
{
    FillInfo(DCM_PatientID, m_patient->PatientID);
    FillInfo(DCM_PatientName, m_patient->PatientsName);
}

void DicomProcessor::FillStudy()
{
    if (m_study->StudyInstanceUID.empty())
    {
        std::cout << "Study instanceUID is empty!" << std::endl;
    }

    FillInfo(DCM_StudyInstanceUID, m_study->StudyInstanceUID);
}

void DicomProcessor::FillSeries()
{
    FillInfo(DCM_SeriesDate, DatetoString(m_series->SeriesDateTime));
    FillInfo(DCM_SeriesTime, TimetoString(m_series->SeriesDateTime));
}

void DicomProcessor::FillImage()
{
    FillInfo(DCM_SamplesPerPixel, std::to_string(m_image->SamplesPerPixel));
    if (m_image->PhotometricInterpretation.empty())
    {
        std::cout << "No PhotometricInterpretation Info in Fill image Pixel" << std::endl;
    }

    FillInfo(DCM_PhotometricInterpretation, m_image->PhotometricInterpretation);
    FillInfo(DCM_Rows, std::to_string(m_image->Rows));
    FillInfo(DCM_Columns, std::to_string(m_image->Columns));
    FillInfo(DCM_BitsAllocated, std::to_string(m_image->BitsAllocated));
    FillInfo(DCM_BitsStored, std::to_string(m_image->BitsStored));
    FillInfo(DCM_HighBit, std::to_string(m_image->HighBit));
    FillInfo(DCM_PixelRepresentation, std::to_string(m_image->PixelRepresentation));

    uint pixelByteCount = (m_image->BitsAllocated <= 8) ? 1 : 2;
    ulong dataLength = m_image->Rows * m_image->Columns * pixelByteCount * m_image->SamplesPerPixel;

    if (m_image->PixelData != NULL)
    {
        OFCondition status = m_fileformat.getDataset()->putAndInsertUint16Array(DCM_PixelData, m_image->PixelData, dataLength / pixelByteCount);
        if (!status.good())
        {
            std::cout << "Fill pixel data error:" << status.text() << std::endl;
        }
    }
}

void DicomProcessor::FillPrivateData()
{
    for (auto &data : m_privateData)
    {
        if (!data.VR.empty())
        {
            RegisterPrivateTags(data);
            OFCondition status = m_fileformat.getDataset()->putAndInsertString(DcmTag((Uint16)data.GroupTag, (Uint16)data.ElementTag, PRIVATE_GROUP_NAME), data.Value.c_str());
            if (!status.good())
            {
                std::cout << "Fill private Tag(" << data.GroupTag << "," << data.ElementTag << ") error:" << status.text();
            }
        }
    }
    m_privateData.clear();
}

void DicomProcessor::RegisterPrivateTags(PrivateData data)
{
    DcmDataDictionary &dict = dcmDataDict.wrlock();

    if ((data.VR.compare("OB") == 0) || (data.VR.compare("FL") == 0) || (data.VR.compare("FD") == 0))
    {
        dict.addEntry(new DcmDictEntry((Uint16)data.GroupTag, (Uint16)data.ElementTag, EVR_UT, data.Name.c_str(), 1, 1, "private", OFTrue, PRIVATE_GROUP_NAME));
    }
    else
    {
        dict.addEntry(new DcmDictEntry((Uint16)data.GroupTag, (Uint16)data.ElementTag, DcmVR(data.VR.c_str()), data.Name.c_str(), 1, 1, "private", OFTrue, PRIVATE_GROUP_NAME));
    }
    dcmDataDict.wrunlock();
}


void DicomProcessor::GetInfo(DcmTagKey tagKey, std::string &info)
{
    OFString ofData;
    OFCondition status = m_fileformat.getDataset()->findAndGetOFString(tagKey, ofData);
    if (!status.good())
    {
        std::cout << "Get Tag(" << tagKey.toString().c_str() << ") error: " << status.text() << std::endl;
    }
    info = ofData.c_str();
}

void DicomProcessor::GetInfo(DcmTagKey tagKey, unsigned int &info)
{
    std::string strInfo;
    GetInfo(tagKey, strInfo);
    try
    {
        info = std::atoi(strInfo.c_str());
    }
    catch (...)
    {
        std::cout << "Get Tag(" << tagKey.toString().c_str() << ") error(String to UInt)!" << std::endl;
    }
}

void DicomProcessor::GetDateTimeInfo(DcmTagKey tagDateKey, DcmTagKey tagTimeKey, std::chrono::system_clock::time_point &info)
{
    std::string date;
    std::string time;
    GetInfo(tagDateKey, date);
    GetInfo(tagTimeKey, time);
    info = StringtoDateTime(date, time);
}

void DicomProcessor::FillInfo(DcmTagKey tagKey, std::string &info)
{
    OFCondition status = m_fileformat.getDataset()->putAndInsertString(tagKey, info.c_str());
    if (!status.good())
    {
        std::cout << "Fill Tag(" << tagKey.toString().c_str() << ") error: " << status.text() << std::endl;
    }
}

void DicomProcessor::FreePixelData()
{
    if (m_image->PixelData != NULL)
    {
        //std::cout << "Free pixel data of Dicom Handler" << std::endl;
        free(m_image->PixelData);
        m_image->PixelData = NULL;
    }
}

DicomFactory:实现DicomIF对象指针的管理

static IDicom* s_dicom = nullptr;

__declspec(dllexport) void CreateDicomProcessor();

__declspec(dllexport) IDicom* GetDicomProcessor();

__declspec(dllexport) void DeleteDicomProcessor();

#include "DicomFactory.h"
#include "DicomProcessor.h"
#include <iostream>

void CreateDicomProcessor()
{
    if (nullptr == s_dicom)
    {
        s_dicom = new DicomProcessor();
    }
}

IDicom* GetDicomProcessor()
{
    return s_dicom;
}

void DeleteDicomProcessor()
{
    if (nullptr != s_dicom)
    {
        delete s_dicom;
        s_dicom = nullptr;
    }
}

DicomUtils:实现一些基本函数的功能

#include "DicomInfo.h"

__declspec(dllexport) std::string GenerateUniqueId(UID_Type type);
__declspec(dllexport) std::string GetDataVR(DataVR_Type type);
__declspec(dllexport) std::chrono::system_clock::time_point StringtoDate(std::string date);
__declspec(dllexport) std::chrono::system_clock::time_point StringtoDateTime(std::string date, std::string time);
__declspec(dllexport) std::string DatetoString(std::chrono::system_clock::time_point timePoint);
__declspec(dllexport) std::string TimetoString(std::chrono::system_clock::time_point timePoint);
#include "DicomUtils.h"
#include <boost/format.hpp>
#include "dcmtk/dcmdata/dcuid.h"

std::string GenerateUniqueId(UID_Type type)
{
    char uid[100];
    switch (type)
    {
    case UID_Study:
        dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
        break;
    case UID_Series:
        dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
        break;
    case UID_Image:
        dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
        break;
    default:
        std::cout << "The type of generate unique id that is not supported! type :" << type << std::endl;
        break;
    }
    return std::string(uid);
}

std::string GetDataVR(DataVR_Type type)
{
    std::string vr = "";
    switch (type)
    {
    case DataVRType_CS:
        vr = "CS";
        break;
    case DataVRType_SH:
        vr = "SH";
        break;
    case DataVRType_LO:
        vr = "LO";
        break;
    case DataVRType_ST:
        vr = "ST";
        break;
    case DataVRType_LT:
        vr = "LT";
        break;
    case DataVRType_UT:
        vr = "UT";
        break;
    case DataVRType_AE:
        vr = "AE";
        break;
    case DataVRType_PN:
        vr = "PN";
        break;
    case DataVRType_UI:
        vr = "UI";
        break;
    case DataVRType_DA:
        vr = "DA";
        break;
    case DataVRType_TM:
        vr = "TM";
        break;
    case DataVRType_DT:
        vr = "DT";
        break;
    case DataVRType_AS:
        vr = "AS";
        break;
    case DataVRType_IS:
        vr = "IS";
        break;
    case DataVRType_DS:
        vr = "DS";
        break;
    case DataVRType_SS:
        vr = "SS";
        break;
    case DataVRType_US:
        vr = "US";
        break;
    case DataVRType_SL:
        vr = "SL";
        break;
    case DataVRType_UL:
        vr = "UL";
        break;
    case DataVRType_AT:
        vr = "AT";
        break;
    case DataVRType_FL:
        vr = "FL";
        break;
    case DataVRType_FD:
        vr = "FD";
        break;
    case DataVRType_OB:
        vr = "OB";
        break;
    case DataVRType_OW:
        vr = "OW";
        break;
    case DataVRType_OF:
        vr = "OF";
        break;
    case DataVRType_SQ:
        vr = "SQ";
        break;
    case DataVRType_UN:
        vr = "UN";
        break;
    default:
        break;
    }
    return vr;
}


std::chrono::system_clock::time_point StringtoDate(std::string date)
{
    std::string year = date.substr(0, 4);
    std::string month = date.substr(4, 2);
    std::string day = date.substr(6, 2);
    tm tmObj;
    tmObj.tm_year = std::atoi(year.c_str()) - 1900;
    tmObj.tm_mon = std::atoi(month.c_str()) - 1;
    tmObj.tm_mday = std::atoi(day.c_str());
    tmObj.tm_hour = std::atoi("0");
    tmObj.tm_min = std::atoi("0");
    tmObj.tm_sec = std::atoi("0");

    //std::time_t tt = mktime(&tmObj);
    return std::chrono::system_clock::from_time_t(mktime(&tmObj));
}

std::chrono::system_clock::time_point StringtoDateTime(std::string date, std::string time)
{
    std::string year = date.substr(0, 4);
    std::string month = date.substr(4, 2);
    std::string day = date.substr(6, 2);
    std::string hour = time.substr(0, 2);
    std::string min = time.substr(2, 2);
    std::string sec = time.substr(4, 2);

    tm tmObj;
    tmObj.tm_year = std::atoi(year.c_str()) - 1900;
    tmObj.tm_mon = std::atoi(month.c_str()) - 1;
    tmObj.tm_mday = std::atoi(day.c_str());
    tmObj.tm_hour = std::atoi(hour.c_str());
    tmObj.tm_min = std::atoi(min.c_str());
    tmObj.tm_sec = std::atoi(sec.c_str());

    size_t pos = time.find(".", 0);
    if (pos == std::string::npos)
    {
        return std::chrono::system_clock::from_time_t(std::mktime(&tmObj));
    }
    else
    {
        std::string strMS = time.substr(pos + 1, time.length() - pos - 1);
        long long ms = std::atoll(strMS.c_str());
        return (std::chrono::system_clock::from_time_t(std::mktime(&tmObj)) + std::chrono::microseconds(ms));
    }   
}


std::string DatetoString(std::chrono::system_clock::time_point timePoint)
{
    std::time_t tt = std::chrono::system_clock::to_time_t(timePoint);
    std::stringstream ss;
    ss << std::put_time(std::localtime(&tt), "%Y%m%d");
    return ss.str();
}

std::string TimetoString(std::chrono::system_clock::time_point timePoint)
{
    std::time_t tt = std::chrono::system_clock::to_time_t(timePoint);

    auto ms = timePoint.time_since_epoch();
    auto diff = std::chrono::duration_cast<std::chrono::microseconds>(ms).count();
    auto const mcsecs = diff % 1000000;

    std::stringstream ss;
    if (mcsecs == 0)
    {
        ss << std::put_time(std::localtime(&tt), "%H%M%S");
    }
    else
    {
        ss << std::put_time(std::localtime(&tt), "%H%M%S") << "." << mcsecs;
    }
    return ss.str();
}

Dicom_Sample工程里编写Dicom文件读写的功能

编写RecomDicom函数,读取Dicom文件

  • 首先在main函数开始时,调用CreateDicomProcessor函数生成Dicom文件处理对象
  • 在main函数结束时,调用DeleteDicomProcessor函数释放Dicom文件处理对象
  • 使用GetDicomProcessor函数就可以得到Dicom文件处理对象
// C++_Dicom_Sample.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "DicomIF.h"
#include "DicomFactory.h"
#include "DicomUtils.h"
#include <iostream>

void ReadDicom(std::string file)
{
    if (!GetDicomProcessor()->Read(file))
    {
        std::cout << "Read Dicom File Failed: " << file.c_str() << std::endl;
        return;
    }

    std::cout << "Patient Name:" << GetDicomProcessor()->PatientInfo()->PatientsName << std::endl;
    std::cout << "Patient ID:" << GetDicomProcessor()->PatientInfo()->PatientID << std::endl;

}

int main()
{
    CreateDicomProcessor();

    std::string readFile = "E:\\Learning\\DICOM\\Module_Sample\\brain_005.dcm";

    ReadDicom(readFile);

    DeleteDicomProcessor();
    return 0;
}
  • 如果此Dicom文件没有某Tag信息,则提示出该Tag

  • 也有Tag存在,但值没空的情况


  • 在Pydicom里打开该Dicom文件,查看信息发现确实如上述情况


  • 读取更多信息
// C++_Dicom_Sample.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "DicomIF.h"
#include "DicomFactory.h"
#include "DicomUtils.h"
#include <iostream>

void ReadDicom(std::string file)
{
    if (!GetDicomProcessor()->Read(file))
    {
        std::cout << "Read Dicom File Failed: " << file.c_str() << std::endl;
        return;
    }

    std::cout << "Patient Name:" << GetDicomProcessor()->PatientInfo()->PatientsName << std::endl;
    std::cout << "Patient ID:" << GetDicomProcessor()->PatientInfo()->PatientID << std::endl;

    std::cout << "Study Instance UID:" << GetDicomProcessor()->StudyInfo()->StudyInstanceUID << std::endl;

    std::cout << "Serial Date:" << DatetoString(GetDicomProcessor()->SeriesInfo()->SeriesDateTime) << std::endl;
    std::cout << "Serial Time:" << TimetoString(GetDicomProcessor()->SeriesInfo()->SeriesDateTime) << std::endl;
    std::cout << "Body Part Examined:" << GetDicomProcessor()->SeriesInfo()->BodyPartExamined << std::endl;

    std::cout << "Rows:" << GetDicomProcessor()->ImageInfo()->Rows << std::endl;
    std::cout << "Columns:" << GetDicomProcessor()->ImageInfo()->Columns << std::endl;
}

int main()
{
    CreateDicomProcessor();

    std::string readFile = "E:\\Learning\\DICOM\\Module_Sample\\brain_005.dcm";

    ReadDicom(readFile);

    DeleteDicomProcessor();
    return 0;
}

编写WriteDicom函数,存储Dicom文件

  • 先读取名为brain_005的Dicom文件,改写其中的某些数据,再另存为名为brain_005_New的Dicom文件
void SaveDicom(std::string file)
{
    GetDicomProcessor()->PatientInfo()->PatientID = "88888";
    GetDicomProcessor()->StudyInfo()->StudyInstanceUID = GenerateUniqueId(UID_Study);
    GetDicomProcessor()->SeriesInfo()->SeriesDateTime = std::chrono::system_clock::now();


    if (!GetDicomProcessor()->Save(file))
    {
        std::cout << "Save Dicom File Failed: " << file.c_str() << std::endl;
        return;
    }
}

int main()
{
    CreateDicomProcessor();

    std::string readFile = "E:\\Learning\\DICOM\\Module_Sample\\brain_005.dcm";
    std::string saveFile = "E:\\Learning\\DICOM\\Module_Sample\\brain_005_New.dcm";

    ReadDicom(readFile);
    SaveDicom(saveFile);

    DeleteDicomProcessor();
    return 0;
}
  • 输出保存成功
  • 在Pydicom里打开Dicom文件(brain_005_New),发现修改的值保存成功


读取和存储Private数据

  • 存取私有字段(0x4001,0x1000),私有字段的Group必须是奇数
// C++_Dicom_Sample.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "DicomIF.h"
#include "DicomFactory.h"
#include "DicomUtils.h"
#include <iostream>

void ReadDicom(std::string file)
{
    PrivateData data;
    data.GroupTag = 0x4001;
    data.ElementTag = 0x1000;
    GetDicomProcessor()->PrivateDataInfo().push_back(data);

    if (!GetDicomProcessor()->Read(file))
    {
        std::cout << "Read Dicom File Failed: " << file.c_str() << std::endl;
        return;
    }

    for (auto &item : GetDicomProcessor()->PrivateDataInfo())
    {
        std::ios::fmtflags fmt(std::cout.flags());
        std::cout << "Private Data:(0x" << std::hex << item.GroupTag << ",0x" << item.ElementTag << ")" << item.Value << std::endl;
        std::cout.flags(fmt);
    }
}

void SaveDicom(std::string file)
{
    PrivateData data;
    data.GroupTag = 0x4001;
    data.ElementTag = 0x1000;
    data.Name = "private1";
    data.VR = GetDataVR(DataVRType_UT);
    data.Value = "Private Test...";
    GetDicomProcessor()->PrivateDataInfo().push_back(data);

    if (!GetDicomProcessor()->Save(file))
    {
        std::cout << "Save Dicom File Failed: " << file.c_str() << std::endl;
        return;
    }
}

int main()
{
    CreateDicomProcessor();

    //std::string readFile = "E:\\Learning\\DICOM\\Module_Sample\\brain_005.dcm";
    std::string saveFile = "E:\\Learning\\DICOM\\Module_Sample\\brain_005_New.dcm";

    //ReadDicom(readFile);
    ReadDicom(saveFile);
    SaveDicom(saveFile);

    DeleteDicomProcessor();
    return 0;
}
  • 第二次运行时可显示Private数据已写入
  • 在Pydicom里打开Dicom文件(brain_005_New),发现Private数据存在


  • 编写ReadDicomTags函数,显示所有数据元素的Tag

void ReadDicomTags(std::string file)
{
    if (!GetDicomProcessor()->Read(file))
    {
        std::cout << "Read Dicom File Failed: " << file.c_str() << std::endl;
        return;
    }

    GetDicomProcessor()->MetaInfoTags();
    GetDicomProcessor()->DataSetTags();
}
  • 输出


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容