使用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开发的同仁们指点,大家一块学习一块进步。https://www.cnblogs.com/JorSean/p/9006664.html