使用GDCM读取DICOM Series

目录

1-认识gdcm

2-下载、编译

3-HelloWorld

4-包装成Series读取类

5-任务


从之前的文章(DCMTK读取压缩格式的DICOM文件并使用Vtk显示_一只弱鶸的博客-CSDN博客)得知vtk中的DICOM读取类并不能直接读取压缩格式的DICOM文件,所以需要使用其他类库进行读取,然后转化成vtk可以识别的对象格式。上篇中已经介绍了使用DCMTK的实现,本篇将探索使用GDCM类库的实现。

1-认识gdcm

gdcm的官网主页(GDCM Wiki)中是这么介绍的,

Whenever medical data, especially medical image data, is generated in a clinical environment, that data must be stored such that it can be retrieved by the same hospital either immediately, or after several years to determine the effectiveness of a course of treatment and to allow comparisons of multiple images for the same patient.

Digital Imaging and Communications in Medicine (DICOM) is a standard that governs this capability by specifying handling, storing, printing, and transmitting information in medical imaging.

Grassroots DICOM (GDCM) is an implementation of the DICOM standard designed to be open source so that researchers may access clinical data directly. GDCM includes a file format definition and a network communications protocol, both of which should be extended to provide a full set of tools for a researcher or small medical imaging vendor to interface with an existing medical database.

GDCM is an open source implementation of the DICOM standard. It offers some compatibility with ACR-NEMA 1.0 & 2.0 files (raw files). It is written in C++ and offers wrapping to the following target languages (via the use of swig):

  • Python (supported),
  • C# (supported),
  • Java (supported),
  • PHP (experimental),
  • Perl (experimental).

It attempts to support all possible DICOM image encodings, namely:

  • RAW,
  • JPEG lossy 8 & 12 bits (ITU-T T.81, ISO/IEC IS 10918-1),
  • JPEG lossless 8-16 bits (ITU-T T.81, ISO/IEC IS 10918-1),
  • JPEG 2000 reversible & irreversible (ITU-T T.800, ISO/IEC IS 15444-1),
  • RLE,
  • Deflated (compression at DICOM Dataset level),
  • JPEG-LS (ITU-T T.87, ISO/IEC IS 14495-1),
  • JPEG 2000 Multi-component reversible & irreversible (ISO/IEC IS 15444-2) (not supported for now),
  • MPEG-2 (not supported for now).

GDCM is designed under the XP definition and has a nightly dashboard (CMake/CTest/Dart).

对gdcm有了一个大致的了解之后就可以开始动手了。

2-下载、编译

与其他C++类库相同,都是下载源码,使用Cmake进行编译选项的设置,然后根据选择的编译器生成对应的IDE的工程文件,作者这里使用的是Cmake+VS2017,考虑到后期在项目中的性能问题,这里只编译了64位版本的debug和release版本,因为与其他类库操作步骤大致相同,这里就不做详细介绍了,如果编译期间存在问题或者需要编译好的文件请留言。

3-HelloWorld

这里使用的工具是VS2017,具体项目配置参考文章在VS工程中,添加c/c++工程中外部头文件(.h),lib库,dll库的基本步骤 - MaxBruce - 博客园

具体代码如下:

/*=========================================================================

  Program: GDCM (Grassroots DICOM). A DICOM library

  Copyright (c) 2006-2011 Mathieu Malaterre
  All rights reserved.
  See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/*
 * This example is ... guess what this is for :)
 */

#include "gdcmReader.h"
#include "gdcmWriter.h"
#include "gdcmAttribute.h"

#include <iostream>

int main(int argc, char *argv[])
{
  if( argc < 3 )
    {
    std::cerr << argv[0] << " input.dcm output.dcm" << std::endl;
    return 1;
    }
  const char *filename = argv[1];
  const char *outfilename = argv[2];

  // Instanciate the reader:
  gdcm::Reader reader;
  reader.SetFileName( filename );
  if( !reader.Read() )
    {
    std::cerr << "Could not read: " << filename << std::endl;
    return 1;
    }

  // If we reach here, we know for sure only 1 thing:
  // It is a valid DICOM file (potentially an old ACR-NEMA 1.0/2.0 file)
  // (Maybe, it's NOT a Dicom image -could be a DICOMDIR, a RTSTRUCT, etc-)

  // The output of gdcm::Reader is a gdcm::File
  gdcm::File &file = reader.GetFile();

  // the dataset is the the set of element we are interested in:
  gdcm::DataSet &ds = file.GetDataSet();

  // Contruct a static(*) type for Image Comments :
  gdcm::Attribute<0x0020,0x4000> imagecomments;
  imagecomments.SetValue( "Hello, World !" );

  // Now replace the Image Comments from the dataset with our:
  ds.Replace( imagecomments.GetAsDataElement() );

  // Write the modified DataSet back to disk
  gdcm::Writer writer;
  writer.CheckFileMetaInformationOff(); // Do not attempt to reconstruct the file meta to preserve the file
                                        // as close to the original as possible.
  writer.SetFileName( outfilename );
  writer.SetFile( file );
  if( !writer.Write() )
    {
    std::cerr << "Could not write: " << outfilename << std::endl;
    return 1;
    }

  return 0;
}

/*
 * (*) static type, means that extra DICOM information VR & VM are computed at compilation time.
 * The compiler is deducing those values from the template arguments of the class.
 */

测试类库没问题以后就可以开始进行下一阶段的学习了。

4-包装成Series读取类

经过一段时间的学习,尝试包装成一个读取类来使用

头文件:

#ifndef ZSERIESREAD_H
#define ZSERIESREAD_H

#include <string>
#include <vector>

#include <QScopedPointer>
#include <QMutex>
#include <QObject>

#include <gdcmSorter.h>
#include <gdcmIPPSorter.h>
#include <gdcmScanner.h>
#include <gdcmDataSet.h>
#include <gdcmDataElement.h>
#include <gdcmDirectory.h>
#include <gdcmImageReader.h>
#include <gdcmTag.h>

#include "model/zseriesvolumeinfo.h"
#include "zglobal.h"



class ZSeriesRead : public QObject
{
    Q_OBJECT
public:
    static ZSeriesRead *getInstance();
    E_GDCM_Info SetSeriesDir(QString dir);
    bool GetVolumeInfo(ZSeriesVolumeInfo* &info);

private:
    static QScopedPointer<ZSeriesRead> self_;
    QString dicom_dir;
    bool is_dir_update;
    ZSeriesVolumeInfo* SeriesVolumeInfo;
    gdcm::Directory gdcmDirectory;
    gdcm::Scanner scanner;
    gdcm::IPPSorter ippsorter;
    gdcm::Directory::FilenamesType files_scanned;
    gdcm::Directory::FilenamesType files_sorted;
    gdcm::ImageReader gdcmImageReader;
    gdcm::DataSet gdcmDataSet;
    gdcm::Image gdcmImage;

    ZSeriesRead();
};

#endif // ZSERIESREAD_H

源文件:

#include "zseriesread.h"

#include <QFile>

QScopedPointer<ZSeriesRead> ZSeriesRead::self_;


ZSeriesRead *ZSeriesRead::getInstance()
{
    if(self_.isNull())
    {
        static QMutex mutex;
        mutex.lock();
        if(self_.isNull())
        {
            self_.reset(new ZSeriesRead);
        }
        mutex.unlock();
    }
    return self_.data();
}

E_GDCM_Info ZSeriesRead::SetSeriesDir(QString dir)
{

    if(!QFile::exists(dir))
    {
        return  E_GDCM_Info::E_AIMDCM_DIR_NULL;
    }

    //读取头部信息数据
    gdcmDirectory.Load(dir.toStdString());
    scanner.AddTag( gdcm::Tag(0x0020,0x0032));
    scanner.Scan(gdcmDirectory.GetFilenames());
    files_scanned = scanner.GetFilenames();
    //如果没有dicom文件 返回错误
    if(files_scanned.size() <= 0)
    {
        return E_GDCM_Info::E_AIMDCM_SLICE_FAIL;
    }

    //针对slice location
    ippsorter.SetComputeZSpacing(false);
    ippsorter.Sort(files_scanned);
    files_sorted = ippsorter.GetFilenames();

    //读取第一张 获取头部信息
    gdcmImageReader.SetFileName(files_sorted.at(0).c_str());
    gdcmImageReader.Read();
    gdcmDataSet = gdcmImageReader.GetFile().GetDataSet();
    gdcmImage = gdcmImageReader.GetImage();

    //获取图像相关信息
    SeriesVolumeInfo->rescaleIntercept = gdcmImage.GetIntercept();
    SeriesVolumeInfo->rescaleSlope = gdcmImage.GetSlope();
    SeriesVolumeInfo->ySize = gdcmImage.GetRows();
    SeriesVolumeInfo->xSize = gdcmImage.GetColumns();
    SeriesVolumeInfo->zSize = files_scanned.size();
    SeriesVolumeInfo->xSpacing = gdcmImage.GetSpacing()[0];
    SeriesVolumeInfo->ySpacing = gdcmImage.GetSpacing()[1];
    //切片厚度需要从头部中读取
    std::stringstream strstream_zspacing;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0018, 0x0050)).GetValue().Print(strstream_zspacing);
    strstream_zspacing >> SeriesVolumeInfo->zSpacing;
    //获取窗位需要从头部中读取
    std::stringstream strstream_wc;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x1050)).GetValue().Print(strstream_wc);
    strstream_wc >> SeriesVolumeInfo->windowCenter;
    //获取窗宽需要从头部中读取
    std::stringstream strstream_ww;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x1051)).GetValue().Print(strstream_ww);
    strstream_ww >> SeriesVolumeInfo->windowWidth;


/*==============================================以下为走弯路行为=========================================*/

//    //获取截距
//    std::stringstream strstream_intercept;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x1052)).GetValue().Print(strstream_intercept);
//    strstream_intercept >> SeriesVolumeInfo->rescaleIntercept;
//    //获取斜率
//    std::stringstream strstream_slope;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x1053)).GetValue().Print(strstream_slope);
//    strstream_slope >> SeriesVolumeInfo->rescaleSlope;
//    //获取窗位
//    std::stringstream strstream_wc;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x1050)).GetValue().Print(strstream_wc);
//    strstream_wc >> SeriesVolumeInfo->windowCenter;
//    //获取窗宽
//    std::stringstream strstream_ww;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x1051)).GetValue().Print(strstream_ww);
//    strstream_ww >> SeriesVolumeInfo->windowWidth;
//    //获取切片厚度
//    std::stringstream strstream_zspacing;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0018, 0x0050)).GetValue().Print(strstream_zspacing);
//    strstream_zspacing >> SeriesVolumeInfo->zSpacing;
//    //获取高度
//    std::stringstream strstream_ysize;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x0010)).GetValue().Print(strstream_ysize);
    std::cout << strstream_ysize.str() << std::endl;
//    strstream_ysize >> SeriesVolumeInfo->ySize;
//    //获取宽度
//    std::stringstream strstream_xsize;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x0011)).GetValue().Print(strstream_xsize);
    std::cout << strstream_xsize.str() << std::endl;
//    strstream_xsize >> SeriesVolumeInfo->xSize;
//    //xSpacing ySpacing
//    std::stringstream strstream_xyspacing;
//    gdcmDataSet.GetDataElement(gdcm::Tag (0x0028, 0x0030)).GetValue().Print(strstream_xyspacing);
//    sscanf_s(strstream_xyspacing.str().c_str(),"%f\\%f",&(SeriesVolumeInfo->xSpacing),&(SeriesVolumeInfo->ySpacing));


/*===================================================以上为走弯路行为============================================*/

    //病人姓名
    std::stringstream strstream_name;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0010, 0x0010)).GetValue().Print(strstream_name);
    strstream_name >> SeriesVolumeInfo->name;

    //病人id
    std::stringstream strstream_id;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0010, 0x0020)).GetValue().Print(strstream_id);
    strstream_id >> SeriesVolumeInfo->admissionid;

    //病人性别
    std::stringstream strstream_gender;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0010, 0x0040)).GetValue().Print(strstream_gender);
    strstream_gender >> SeriesVolumeInfo->gender;

    //date
    std::stringstream strstream_date;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0008, 0x0020)).GetValue().Print(strstream_date);
    strstream_date >> SeriesVolumeInfo->date;

    //modality
    std::stringstream strstream_modality;
    gdcmDataSet.GetDataElement(gdcm::Tag (0x0008, 0x0060)).GetValue().Print(strstream_modality);
    strstream_modality >> SeriesVolumeInfo->modality;

    is_dir_update = true;
    dicom_dir = dir;

    return E_GDCM_Info::E_AIMDCM_OK;
}

bool ZSeriesRead::GetVolumeInfo(ZSeriesVolumeInfo* &info)
{
    //目录更新重新读取数据
    if(is_dir_update)
    {
        // 释放内存
        if(SeriesVolumeInfo->ptrData)
        {
            free(SeriesVolumeInfo->ptrData);
            SeriesVolumeInfo->ptrDataLength = 0;
        }

        //读取图片内容
        long long volume_size = SeriesVolumeInfo->xSize*SeriesVolumeInfo->ySize*SeriesVolumeInfo->zSize*sizeof(short int);
        SeriesVolumeInfo->ptrDataLength = volume_size;
        SeriesVolumeInfo->ptrData = (short int*)malloc(SeriesVolumeInfo->ptrDataLength);
        unsigned long image_buffer_length = SeriesVolumeInfo->xSize*SeriesVolumeInfo->ySize*sizeof(short int);
        char* buffer = (char*)malloc(image_buffer_length);
        for(int i=0;i<files_sorted.size();i++)
        {
            memset(buffer,0,image_buffer_length);
            gdcmImageReader.SetFileName(files_sorted.at(i).c_str());
            gdcmImage = gdcmImageReader.GetImage();
            bool flag = gdcmImage.GetBuffer(buffer);
            if(!flag)
            {
                return flag;
            }
            memcpy(SeriesVolumeInfo->ptrData+(image_buffer_length/2*i),buffer,image_buffer_length);
        }
        free(buffer);

        is_dir_update = false;
    }

    info = this->SeriesVolumeInfo;

    return true;
}

ZSeriesRead::ZSeriesRead()
{
    dicom_dir = "";
    is_dir_update = false;
    SeriesVolumeInfo = new ZSeriesVolumeInfo();
}

以上代码均为本人学习阶段尝试,如有误导倾向或错误内容,欢迎批评指正!

5-任务

测试下来发现几个问题:

1、只能读取一次,再次读取报错;

2、读到的所有图像都是重复(第一张)的;

3、目前还没发现。

以上几个问题,我这边最新版本已经修复了,如果您想使用以上代码,请仔细学习后修改,提示(改变变量作用域),就能修改为可食用代码,祝好运!如果您比较着急,请留言联系我,可提供最新代码或修改建议。

最后附上效果图

 

能克服困难的人,可使困难化为良机。——丘吉尔

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

把我格子衫拿来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值