使用 DCMTK 和 CxImage 将 DICOM 图像转换为通用图形格式(反之亦然) 
将 DICOM 文件转换为 BMP/JPG 及反之
 
 
引言
本文介绍了一个最小的可运行的示例应用程序,作为一个起点,展示如何将 DICOM 图像转换为常见的图形格式(例如 BMP、JPG、TIF 等)以及反之。我们的示例应用程序基于两个开源库,它们是 DCMTK 和 CxImage。
背景
DICOM 标准(医学数字成像和通信)是由美国电气制造商协会 (NEMA) 创建的标准,旨在简化医疗图像(如 CT 扫描、MRI 和超声)的分发和交换。在本文中,我们将重点关注文件格式转换。文件格式在 DICOM 标准的第 10 部分中进行了描述,您可以从这里下载[^]。这里还有一个对文件格式的简短介绍可用。
DCMTK 是一个广泛使用的 DICOM 标准开源实现;它是一组 C/C++ 库和应用程序,带有完整的源代码。要编译本文中的示例,您需要先下载 DCMTK。如果您在构建下载的 DCMTK 包时遇到问题,请参考DCMTK for Dummies。
本文中使用的另一个库是 CxImage,它是一个 C++ 类,可以以一种非常简单快速的方式加载、保存、显示和转换图像。它支持几乎所有常见的图形类型,例如 BMP、JPG、TIF、PNG 等。在本文中,我们将扩展这个库,通过使用 DCMTK 提供的 DICOM 格式编码/解码功能来支持显示和转换 DICOM 图像。下载 CxImage并按照其使用指南确保它可以在您的机器上成功编译。
Using the Code
我们简单地从基类 CxImage 派生我们的 CxImageDCM 类。这样做使 CxImageDCM 类能够使用从基类继承的方法加载和解码通用图形。派生类中有三个额外的方法,LoadDCM(…)、SaveAsDCM(…)、SaveAsJPG(…),它们分别用于解码、编码和转换 DICOM 图像。
//
class CxImageDCM : public CxImage  
{
public:
    CxImageDCM();
    virtual ~CxImageDCM();
    
    bool LoadDCM(const TCHAR* filename);
    bool SaveAsDCM(const TCHAR* filename);
    bool SaveAsJPG(const TCHAR* fileName);
};//
加载 DCM
在示例应用程序中,使用 DCMTK 提供的类加载和解码 DICOM 图像,然后将其转换为临时位图文件以供以后操作
//
bool CxImageDCM::LoadDCM(const TCHAR* filename)
{  
    DcmFileFormat *dfile = new DcmFileFormat();
    OFCondition cond = dfile->loadFile(filename, EXS_Unknown,
                      EGL_withoutGL,DCM_MaxReadLength,OFFalse);
    
    if (cond.bad()) {
        AfxMessageBox(cond.text());
    }
    
    E_TransferSyntax xfer = 
            dfile->getDataset()->getOriginalXfer();
    DicomImage *di = new DicomImage(dfile, xfer, 
                         CIF_AcrNemaCompatibility, 0, 1);
    
    if (di->getStatus() != EIS_Normal)
        AfxMessageBox(DicomImage::getString(di->getStatus()));
    
    di->writeBMP("c:\\from_dicom.bmp",24);
    
    return CxImage::Load("c:\\from_dicom.bmp",CXIMAGE_FORMAT_BMP);
    
}//
从 DCM 转换
在加载 DCM 文件后,您可以使用 CxImage 提供的编码功能将其保存为通用图形文件,或者您也可以使用 DCMTK 的编码插件进行转换(但是,CxImage 支持更多格式)
//
bool CxImageDCM::SaveAsJPG(const TCHAR* fileName)
{//you may also use DCMTK's JPG encoding plug-in
    return CxImage::Save(fileName,CXIMAGE_FORMAT_JPG);
}//
转换为 DCM
要将通用图形文件转换为 DCM 文件,您需要先加载通用图形,然后设置必要的标签并将像素数据复制到目标 DCM 文件
//
bool CxImageDCM::SaveAsDCM(const TCHAR* filename)
{
    CxImageDCM::IncreaseBpp(24);
    char uid[100]; 
    DcmFileFormat fileformat; 
    DcmDataset *dataset = fileformat.getDataset(); 
    dataset->putAndInsertString(DCM_SOPClassUID, 
               UID_SecondaryCaptureImageStorage); 
    /* ... */
    //dataset->putAndInsertUint32(DCM_MetaElementGroupLength,128);
    dataset->putAndInsertUint16(DCM_FileMetaInformationVersion,
                                                          0x0001);
    /* ... */    
    dataset->putAndInsertString(DCM_UID,
        UID_MultiframeTrueColorSecondaryCaptureImageStorage);
    dataset->putAndInsertString(DCM_PhotometricInterpretation,
                                                        "RGB"); 
    //add more tags here
    /* ... */ 
    BYTE* pData=new BYTE[GetHeight()*info.dwEffWidth];
    BYTE* pSrc=GetBits(head.biHeight-1);
    BYTE* pDst=pData;
    for(long y=0; y < head.biHeight; y++){
        memcpy(pDst,pSrc,info.dwEffWidth);
        pSrc-=info.dwEffWidth;
        pDst+=info.dwEffWidth;
    }
    dataset->putAndInsertUint8Array(DCM_PixelData, 
                 pData, GetHeight()*info.dwEffWidth); 
    delete[] pData;
    
    OFCondition status = fileformat.saveFile(filename, 
                           EXS_LittleEndianImplicit,
                           EET_UndefinedLength,EGL_withoutGL); 
    if (status.bad()) 
        AfxMessageBox("Error: cannot write DICOM file ");
    
    return true;     
}//
关注点
在本文中,使用 CxImage 提供的编码功能将 DICOM 图像转换为 JPG 文件(或 CxImage 支持的其他格式)。实际上,DCMTK 已经有一个名为 dcmj2pnm 的成熟实用程序,用于将 DICOM 图像转换为 BMP、PNG、TIF 或 JPG 图像。对于 dcmj2pnm 不支持的其他格式,例如 GIF、TGA、PCX、WBMP 等,您可以使用 CxImage 的编码功能来编写自己的转换函数。这里需要澄清的一件事是,我们的示例应用程序只是一个提供入门点的玩具实用程序。要编写一个像样的 DICOM 图像转换器,您需要考虑更多与 DICOM 相关的选项。有关更多信息,您可以参考 dcmj2pnm 的实现。(它包含在 DCMTK 源代码包中。)
根据我的经验,CxImage 易于使用;它“可以以简单快速的方式加载、保存、显示和转换图像”。但是,当您必须从基类 CxImage 派生一个新的图像编码器/解码器时,我发现它很烦人。基类必须知道所有派生类才能提供多态行为。幸运的是,在我们的示例中,派生类 CxImageDCM 只需要它从基类继承的编码/解码函数,所以我懒得去动 CxImage 的源代码。




