使用DCMTK实现DICOM文件浏览器dicom explorer

之前一直使用别人的免费浏览工具来浏览DCM图像,或多或少都存在小的问题,要么完全免费但是功能不全不好用,要么就是收费需要定期下载版本申请试用,折腾来折腾去很是费心,决定最近自己写个简单的,不求功能强大只求自己用起来得心应手。

底层文件的读取使用DCMTK3.6.3的DCMData包;考虑支持跨平台,上层显示使用QT来做。

 在Linux和windows两个平台下编译DCMTK生成的Config文件夹中的头文件内容是不同的,为了更好地组织两个平台的头文件和库文件,在dcmtk文件夹分别新建linux和win文件夹,将ubuntu下编译后的include文件夹拷贝到linux下,将win7下编译后的include文件夹拷贝到win下。读取文件主要使用DCMTK的DcmData库,该库依赖了ofstd,oflog库,为了支持RLE压缩和JPEG压缩,还需要dcmjpeg库。以linux为例,将编译后的config文件夹下的include目录和源码中的ofstd,oflog,dcmjpeg,dcmdata文件夹中的include文件夹拷贝到dcmtk/linux/include目录下,并在工程文件.pro中将这些目录添加到INCLUDEPATH中;

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/config
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/config

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/ofstd
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/ofstd

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/ofstd/diag
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/ofstd/diag

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/ofstd/variadic
else:unix:  INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/ofstd/variadic

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/oflog
else:unix:  INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/oflog

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/oflog/config
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/oflog/config

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/oflog/helpers
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/oflog/helpers

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/oflog/internal
else:unix:  INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/oflog/internal

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/oflog/thread
else:unix:  INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/oflog/thread

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/oflog/spi
else:unix:  INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/oflog/spi

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/dcmjpeg
else:unix:  INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/dcmjpeg

win32: INCLUDEPATH += $$PWD/dcmtk/win/include/dcmtk/dcmdata
else:unix: INCLUDEPATH += $$PWD/dcmtk/linux/include/dcmtk/dcmdata

 

将这几个库文件也拷贝到dcmtk/linux/lib文件夹下,使用到的库文件和.pro代码如下:

DEPENDPATH += C:\Program Files\Microsoft SDKs\Windows\v6.0A\Lib\x64;

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -lcharset_d
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -lcharset_d
else:unix: LIBS += -lcharset

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -llibiconv_d
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -llibiconv_d
else:unix: LIBS += -liconv

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -lofstd
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -lofstd
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -lofstd

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -loflog
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -loflog
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -loflog

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -ldcmjpeg
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -ldcmjpeg
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -ldcmjpeg

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -ldcmimage
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -ldcmimage
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -ldcmimage

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -ldcmimgle
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -ldcmimgle
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -ldcmimgle

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -lijg8
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -lijg8
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -lijg8

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -lijg12
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -lijg12
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -lijg12

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -lijg16
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -lijg16
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -lijg16

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -ldcmdata
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -ldcmdata
else:unix: LIBS += -L$$PWD/dcmtk/linux/lib/ -ldcmdata

win32:CONFIG(release, debug|release): LIBS += -L'C:/Program Files/Microsoft SDKs/Windows/v6.0A/Lib/x64/' -lNetAPI32
else:win32:CONFIG(debug, debug|release): LIBS += -L'C:/Program Files/Microsoft SDKs/Windows/v6.0A/Lib/x64/' -lNetAPI32

win32: LIBS += -L'C:/Program Files/Microsoft SDKs/Windows/v6.0A/Lib/x64/' -lWSock32

win32:CONFIG(release, debug|release): LIBS += -L'C:/Program Files/Microsoft SDKs/Windows/v6.0A/Lib/xl64/' -lWS2_32
else:win32:CONFIG(debug, debug|release): LIBS += -L'C:/Program Files/Microsoft SDKs/Windows/v6.0A/Lib/x64/' -lWS2_32

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/release/ -lzlib_d
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/dcmtk/win/lib/debug/ -lzlib_d
unix:!macx: LIBS += -lz

win32: LIBS += -L'C:/Program Files/Microsoft SDKs/Windows/v6.0A/Lib/' -lIPHlpApi

win32: LIBS += -lAdvAPI32

 

需要添加的头文件如下:

#include "dcdeftag.h"
#include "dcdatset.h"
#include "dcelem.h"
#include "dcfilefo.h"
#include "dcuid.h"
#include "dcrledrg.h"
#include "dcmetinf.h"
#include "djdecode.h"

 

接下来就可以使用DcmFielFormat类来读取图像信息了。

    DcmFileFormat* m_pDcmFile;
    OFCondition result = dcmFile.loadFile(strFileName);
    if (result.bad())
    {
        return false;
    }

    DcmDataset* dataset = dcmFile.getDataset();
    if (dataset == NULL)
    {
        return false;
    }

 

首先,如果读取RLE或者JEPG压缩的图像,需要先使用DcmJpeg库来转换Transfer syntax,方法如下:

    DcmMetaInfo* meta = dcmFile.getMetaInfo();
    DcmElement *element = NULL;

    OFString transferSyntaxUID;
    result = meta->findAndGetElement(DCM_TransferSyntaxUID, element);
    if (result.bad() || element == NULL)
    {
//        assert(false);
//        return false;
    }
    else
    {
        element->getOFString(transferSyntaxUID, 0);
    }

    if (transferSyntaxUID.compare(UID_RLELosslessTransferSyntax)==0)
    {
        DcmRLEDecoderRegistration::registerCodecs();

        result = dataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
        DcmRLEDecoderRegistration::cleanup();
        if (result.bad())
        {
            return false;
        }
    }
    else if ( transferSyntaxUID.compare(UID_JPEGProcess14SV1TransferSyntax)==0
             || transferSyntaxUID.compare(UID_JPEGProcess1TransferSyntax)==0 )
    {
        DJDecoderRegistration::registerCodecs();
        result = dataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
        DJDecoderRegistration::cleanup();
        if (result.bad())
        {
            return false;
        }
    }

 

 

接下来读取像素相关的tag,主要读取C.6.3 Image Pixel Module也就是TableC.7-11a.Image Pixel Module Attributes的Tags。主要Tag的含义如下:

(0028,0002)Samples per Pixel:每个像素的存储单元个数,也就是几个存储单元数据来表示一个像素的信息。值为1或者3,其他值的含义没有定义,对于monochrome和palette color图像值为1,对于RGB或者其他vector color models,值为3.

(0028,0004)Photometric Interpretation:图像的类型,详见C7.6.3.1.2 Photometric Interpretation的解释,黑白灰度图像大多使用MONOCHROME1或者MONOCHROME2。

(0028,0010)Rows:图像的行数

(0028,0011)Columns:图像的列数

(0028,0100)Bits Allocated:每个Sample分配的bit数

(0028,0101)Bits Stored:每个Sample实际存储的bit数

(0028,0102)High Bit:每个sample的最高位

(0028,0103)Pixel Repersentation:Sample的数据表示形式,0表示无符号整型,1表示2的补码。

(7FE0,0010)Pixel Data:像素数据

(0028,0006)Planar Configuration:彩色像素数据的表示形式,是按像素来排布还是按颜色色素来排布,比如RGB图像,0表示RGBRGBRGB……RGB的形式来存储,1表示RRR……RRRGGG……GGGBBB……BBB的形式来存储。

(0028,0106)Smallest Image Pixel:本幅图中像素值的最小值

(0028,0107)Largest Image Pixel Value:本幅图像中像素值的最大值;

(0028,1101-1103)(0028,1201-1203)分别为RGB描述了一个查找表,如果图像是PALETTE COLOR,就利用像素值作为索引来查找这个表,从而得到真正的像素值。Descriptor包含三个数值,第一个值为查找表的元素个数;第二个值为最小的索引数,也就是(7FE0, 0010)中读取的最小数;第三个值为查找表的每一个元素的位数。

 

通过以上Tags就可以解析出来图像中每个像素的具体值,接下来要把这些像素值显示出来,一般需要经过两步转换,Modality LUT和VOI LUT。

Modality LUT: 将设备相关的像素值转换为设备无关的像素值,比如CT图像的Hounsfield units转换为与CT无关的像素值。如果是线性转换就使用Rescale Slope(0028,1053)和Rescale Intercept(0028,1052)来转换,如果是非线性就使用Modality LUT Sequnce(0028, 3000)来转换。

VOI LUT:在进行Modality LUT后,将得到的像素信息转换成显示像素信息,比如将像素信息缩放到可显示的范围内。如果是线性转换就通过Window Center(0028,1050)和Window Width(0028,1051)来转换,如果是非线性就通过VOI LUT Sequence(0028, 3010)来定义。在C11.2.1.2.2中明确提到只有MONO1和MONO2的图像才需要VOI LUT转换。

 

只要按照定义把像素值正确地读出来,再经过正确的LUT,一个完整的图像就得到了。

 

代码托管在github.com上,https://github.com/JorSean/dicom-explorer。欢迎搞DICOM开发的同仁们指点,大家一块学习一块进步。

posted @ 2018-05-08 10:02  JorSean  阅读(1251)  评论(0编辑  收藏  举报