DCMTK下载DICOM服务器上的DICOM文件-CGET操作

上篇文章搭建好dicom服务器后,搭建Orthanc服务器-DICOM服务器_一只弱鶸的博客-CSDN博客

是时候学习一下与服务器的通讯了,Orthanc提供了一目了然的webui操作界面,查询、上传、查看、下载等操作,只需要点点鼠标就能实现,如下图所示

〇、可视化界面展示

1-主界面

2-病人列表界面

3-Series列表界面

4-viewer界面

 相信这种界面操作对于没有经验的人(比如我),也能很快上手,但这显然不是学习的重点,因为项目中需要完成定制化的界面和操作,接下来就一起学习下使用DCMTK如何完成与DICOM服务器的通讯。

一、环境配置

​​​​​​​DCMTK读取压缩格式的DICOM文件并使用Vtk显示_一只弱鶸的博客-CSDN博客

之前的博客讲到过DCMTK如何读取本地压缩格式的DICOM文件,这里相同的内容就不做赘述了。

二、基础理论知识

1-首先了解PACS服务架构

PACS(picture archiving and communication system)意为影像归档和通信系统。它是应用在医院影像科室的系统,主要的任务就是把日常产生的各种医学影像(包括核磁,CT,超声,各种X光机,各种红外仪、显微仪等设备产生的图像)通过各种接口(模拟,DICOM,网络)以数字化的方式海量保存起来,当需要的时候在一定的授权下能够很快的调回使用,同时增加一些辅助诊断管理功能。它在各种影像设备间传输数据和组织存储数据具有重要作用。 -摘自百度百科

理解PACS的服务架构和作用

医院PACS系统_家玲有点甜哦~~的博客-CSDN博客_pacs系统

 

图片摘自上述博客,侵删。

2- DICOM传输协议理解

1.1 DICOM协议简介及应用_DICOM医学影像的博客-CSDN博客_dicom

 

 相信有HTTP协议基础的读者理解起来会轻松一点。

三、编码

 和之前一样编码环境为WIndows 10、VS 2017、Qt、DCMTK 3.6.7(VS2017 64位下编译)。

参考getscu的实现代码,完成了最简单从DICOM服务器上下载DICOM文件的Demo,仅供参考,如有错误,请指出,谢谢!

#include <dcmtk/dcmnet/scu.h>
#include <dcmtk/dcmdata/dcpath.h>

typedef unsigned long OFCmdUnsignedInt;

typedef enum {
  QMPatientRoot = 0,
  QMStudyRoot = 1,
  QMPatientStudyOnly = 2
} QueryModel;

static const char* querySyntax[3] = {
  UID_GETPatientRootQueryRetrieveInformationModel,
  UID_GETStudyRootQueryRetrieveInformationModel,
  UID_RETIRED_GETPatientStudyOnlyQueryRetrieveInformationModel
};

static OFList<OFString> overrideKeys;

void applyOverrideKeys(DcmDataset *dataset)
{
  /* replace specific keys by those in overrideKeys */
  OFListConstIterator(OFString) path = overrideKeys.begin();
  OFListConstIterator(OFString) endOfList = overrideKeys.end();
  DcmPathProcessor proc;
  proc.setItemWildcardSupport(OFFalse);
  proc.checkPrivateReservations(OFFalse);
  OFCondition cond;
  while (path != endOfList)
  {
    cond = proc.applyPathWithValue(dataset, *path);
    if (cond.bad())
    {
    }
    path++;
  }
}


void prepareTS(E_TransferSyntax ts,
                      OFList<OFString>& syntaxes)
{
  /*
  ** We prefer to use Explicitly encoded transfer syntaxes.
  ** If we are running on a Little Endian machine we prefer
  ** LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
  ** Some SCP implementations will just select the first transfer
  ** syntax they support (this is not part of the standard) so
  ** organize the proposed transfer syntaxes to take advantage
  ** of such behavior.
  **
  ** The presentation contexts proposed here are only used for
  ** C-FIND and C-MOVE, so there is no need to support compressed
  ** transmission.
  */

  switch (ts)
  {
    case EXS_LittleEndianImplicit:
      /* we only support Little Endian Implicit */
      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
      break;
    case EXS_LittleEndianExplicit:
      /* we prefer Little Endian Explicit */
      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
      syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
      break;
    case EXS_BigEndianExplicit:
      /* we prefer Big Endian Explicit */
      syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
      break;
#ifdef WITH_ZLIB
    case EXS_DeflatedLittleEndianExplicit:
      /* we prefer Deflated Little Endian Explicit */
      syntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax);
      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
      syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
      break;
#endif
    default:
      DcmXfer xfer(ts);
      if (xfer.isEncapsulated())
      {
        syntaxes.push_back(xfer.getXferID());
      }
      /* We prefer explicit transfer syntaxes.
       * If we are running on a Little Endian machine we prefer
       * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
       */
      if (gLocalByteOrder == EBO_LittleEndian)  /* defined in dcxfer.h */
      {
        syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
        syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
      } else
      {
        syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
        syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
      }
      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
      break;
  }
}

void download()
{

    E_TransferSyntax opt_get_networkTransferSyntax = EXS_Unknown;
    OFCmdUnsignedInt opt_maxPDU = ASC_DEFAULTMAXPDU;
    int opt_dimse_timeout = 0;
    int opt_acse_timeout = 30;
    QueryModel opt_queryModel = QMStudyRoot;
    T_DIMSE_BlockingMode opt_blockMode = DIMSE_BLOCKING;
    const char *opt_peer = "172.16.31.18";
    OFCmdUnsignedInt opt_port = 4242;;
    const char *opt_peerTitle = "ORTHANC";
    const char *opt_ourTitle = "ORTHANC";
    OFList<OFString> fileNameList;
    OFBool opt_showPresentationContexts = OFFalse;
    E_TransferSyntax opt_store_networkTransferSyntax = EXS_Unknown;
    DcmStorageMode opt_storageMode = DCMSCU_STORAGE_DISK;
    OFString opt_outputDirectory = "e:/1/";
    OFCmdUnsignedInt opt_repeatCount = 1;

    overrideKeys.push_back("QueryRetrieveLevel=STUDY");
    overrideKeys.push_back("StudyInstanceUID=1.2.840.113619.186.18420258241203197.20210618135644203.957");

    OFList<OFString> syntaxes;
    prepareTS(opt_get_networkTransferSyntax, syntaxes);
    DcmSCU scu;
    scu.setMaxReceivePDULength(opt_maxPDU);
    scu.setACSETimeout(opt_acse_timeout);
    scu.setDIMSEBlockingMode(opt_blockMode);
    scu.setDIMSETimeout(opt_dimse_timeout);
    scu.setAETitle(opt_ourTitle);
    scu.setPeerHostName(opt_peer);
    scu.setPeerPort(OFstatic_cast(Uint16, opt_port));
    scu.setPeerAETitle(opt_peerTitle);
    scu.setVerbosePCMode(opt_showPresentationContexts);

    /* add presentation contexts for get and find (we do not actually need find...)
    * (only uncompressed)
    */
    scu.addPresentationContext(querySyntax[opt_queryModel], syntaxes);

    /* add storage presentation contexts (long list of storage SOP classes, uncompressed) */
    syntaxes.clear();
    prepareTS(opt_store_networkTransferSyntax, syntaxes);
    for (Uint16 j = 0; j < numberOfDcmLongSCUStorageSOPClassUIDs; j++)
    {
    scu.addPresentationContext(dcmLongSCUStorageSOPClassUIDs[j], syntaxes, ASC_SC_ROLE_SCP);
    }

    /* set the storage mode */
    scu.setStorageMode(opt_storageMode);
    if (opt_storageMode != DCMSCU_STORAGE_IGNORE)
    {
    scu.setStorageDir(opt_outputDirectory);
    }

    /* initialize network and negotiate association */
    OFCondition cond = scu.initNetwork();
    if (cond.bad())
    {
        exit(1);
    }
    cond = scu.negotiateAssociation();
    if (cond.bad())
    {
        exit(1);
    }
    cond = EC_Normal;
    T_ASC_PresentationContextID pcid = scu.findPresentationContextID(querySyntax[opt_queryModel], "");
    if (pcid == 0)
    {
        exit(1);
    }

    /* do the real work, i.e. send C-GET requests and receive objects */
    for (Uint16 repeat = 0; repeat < opt_repeatCount; repeat++)
    {
    size_t numRuns = 1;
    DcmFileFormat dcmff;
    DcmDataset *dset = dcmff.getDataset();
    OFListConstIterator(OFString) it;
    /* load first file, if there is one */
    if (!fileNameList.empty())
    {
      numRuns = fileNameList.size();
      it = fileNameList.begin();
      cond = dcmff.loadFile((*it).c_str());
      if (cond.bad())
      {
        exit(1);
      }
      dset = dcmff.getDataset();
    }
    OFList<RetrieveResponse*> responses;
    /* for all files (or at least one run from override keys) */
    for (Uint16 i = 0; i < numRuns; i++)
    {
      applyOverrideKeys(dset);
      cond = scu.sendCGETRequest(pcid, dset, &responses);
      if (cond.bad())
      {
        exit(1);
      }
      /* load next file if there is one */
      if (numRuns > 1)
      {
        it++;
        cond = dcmff.loadFile((*it).c_str());
        if (cond.bad())
        {
          exit(1);
        }
        dset = dcmff.getDataset();
      }
    }
    if (!responses.empty())
    {
      /* output final status report */
      (*(--responses.end()))->print();
      /* delete responses */
      OFListIterator(RetrieveResponse*) iter = responses.begin();
      OFListConstIterator(RetrieveResponse*) last = responses.end();
      while (iter != last)
      {
        delete (*iter);
        iter = responses.erase(iter);
      }
    }
    }
    scu.abortAssociation();
}

int main(int argc, char *argv[])
{
    //echoscu 测试

//    T_ASC_Network *net;
//    ASC_initializeNetwork(NET_REQUESTOR, 0, 1000, &net);

//    T_ASC_Parameters *params;
//    ASC_createAssociationParameters(&params, ASC_DEFAULTMAXPDU);

//    ASC_setAPTitles(params, "ECHOSCU", "ANY-SCP", NULL);

//    ASC_setPresentationAddresses(params, "127.0.0.1", "172.16.31.18:4242");

//    const char* ts[] = { UID_LittleEndianImplicitTransferSyntax};

//    ASC_addPresentationContext(params, 1, UID_VerificationSOPClass, ts, 1);

//    T_ASC_Association * assoc;
//    if(ASC_requestAssociation(net, params, &assoc).good())
//    {
//        if(ASC_countAcceptedPresentationContexts(params) == 1)
//        {
//            DIC_US id = assoc->nextMsgID++;
//            DIC_US status;
//            DcmDataset *ds = NULL;
//            DIMSE_echoUser(assoc, id, DIMSE_BLOCKING, 0, &status, &ds);
//            delete ds;
//        }
//    }

//    ASC_releaseAssociation(assoc);
//    ASC_destroyAssociation(&assoc);
//    ASC_dropNetwork(&net);

    download();

    return 0;
}

下载的文件不是dcm格式的,需要手动修改后缀名,才能正常查看。

 这里还需要,搞懂几个名词:

AE Title:AE Title(Application Entity Title)是配置影像检查设备DICOM服务(Worklist、Storage、Print等)必不可少的参数之一。

Application Entity Title - The representation used to identify the DICOM nodes communicating between each other.

对于某一台影像检查设备,其各个DICOM服务可以对应不同的AE Title,当然这些DICOM服务也可以对应同一个AE Title。AE Title是一个字符串,但是这个字符串在我们要配置的RIS/PACS系统的网络中必须是唯一的。因此,AE Title是这个网络中某一个(或几个)DICOM服务的唯一标识。(摘自百度百科)

PeerHostname:服务器的主机名或IP地址

PeerPort:服务器的端口号这里为 4242 默认端口号为 104

 

使用 DCMTK 库读取 DICOM 文件需要进行以下步骤: 1. 引入 DCMTK 库 在 QT 项目中引入 DCMTK 库,可以通过在项目文件中添加以下代码引入静态库: ``` LIBS += -L/path/to/dcmtk/lib -ldcmdata -loflog -lofstd -li2d -ldcmimage -lzlib -lpng -ltiff ``` 注意:需要将 /path/to/dcmtk/lib 替换为 DCMTK 库的安装路径。 2. 初始化 DCMTK 库 在 QT 代码中,需要先初始化 DCMTK 库,可以在 main 函数中添加以下代码: ``` #include <dcmtk/config/osconfig.h> #include <dcmtk/dcmdata/dctk.h> int main(int argc, char *argv[]) { // 初始化 DCMTKDcmInitialize(argc, argv); ... } ``` 3. 读取 DICOM 文件 使用 DCMTK 库读取 DICOM 文件可以使用 DcmFileFormat 类,以下是一个示例代码: ``` #include <dcmtk/config/osconfig.h> #include <dcmtk/dcmdata/dctk.h> void readDICOM(const char* filename) { // 创建 DcmFileFormat 对象 DcmFileFormat fileformat; // 读取 DICOM 文件DcmFileFormat 对象中 OFCondition status = fileformat.loadFile(filename); if (!status.good()) { qDebug() << "Failed to read DICOM file"; return; } // 获取 DICOM 数据集 DcmDataset* dataset = fileformat.getDataset(); // 获取 DICOM 图像数据 Uint16 *pixelData; dataset->findAndGetUint16Array(DCM_PixelData, pixelData); // 获取 DICOM 图像大小 Uint16 rows, cols; dataset->findAndGetUint16(DCM_Rows, rows); dataset->findAndGetUint16(DCM_Columns, cols); } ``` 上述代码中,首先创建 DcmFileFormat 对象,然后使用 loadFile() 方法读取 DICOM 文件,如果读取成功,则可以使用 getDataset() 方法获取 DICOM 数据集,使用 findAndGetUint16Array() 方法获取图像数据,使用 findAndGetUint16() 方法获取图像大小。 注意:DCMTK 库使用 C++98 标准,因此需要在 QT 项目中添加 -std=c++98 编译选项。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

把我格子衫拿来

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

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

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

打赏作者

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

抵扣说明:

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

余额充值