之前一直使用别人的免费浏览工具来浏览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开发的同仁们指点,大家一块学习一块进步。