【转】DICOM医学图像处理:基于DCMTK工具包学习和分析worklist_sinolover的博客-程序员ITS304

技术标签: DCMTK  

转自:https://blog.csdn.net/zssureqh/article/details/38775315

背景:

        DICOM3.0协议中有介绍关于worklist的部分。简而言之,worklist可以看做是放射科设备从医院RIS系统中自动读取患者信息的一种“通信协议”,可以指存储在RIS系统中的患者数据库,主要包括患者的基本信息(如年龄、性别、身高、体重、出生年月等),这与DCM文件信息头MetaInfo中的多数字段重合。因此从RIS系统中自动获取worklist是医院信息化的必要组成部分。下面简单的给出几个图像,形象的描述一下worklist的作用。

clip_image001clip_image002

worklist的实例学习:

        在简单的了解了worklist的作用后,下面我们利用DCMTK提供的工具包(wlmscpfs.exe和findscu.exe)来真实模拟一下该场景,从而更深刻的学习worklist的功能。

worklist简单的看做一种“通讯”,那么自然就存在着通讯的两端,暂且称作“服务端”和“客户端”。这里我们用wlmscpfs.exe来作为worklist通讯的服务端,即等待外部访问的终端;用findscu.exe来作为客户端,用来发起worklist访问。

首先简单的介绍一下工具包的指令及使用方式。wlmscpfs.exe是类似于DOS时代的命令,通过设定参数可以实现不同的目的。利用Win+R键开启操作系统的运行窗口,输入cmd后进入到命令提示行窗口。然后进入到DCMTK编译后的bin文件夹(我本机地址是C:\Program Files (x86)\DCMTK\bin)目录,此时直接输入wlmscpfs.exe就可以看到关于该命令工具的各种说明。

>cd C:\Program Files (x86)\DCMTK\bin

>wlmscpfs.exe

image

(注,此处如果为了省事,可以将bin文件夹路径添加到windows的环境变量中,如是就可以在任何目录下使用bin下的各种工具了)

        由上图看到,wlmscpfs.exe工具至少需要给出port一个参数,即开启worklist服务的本机端口号。另外还需要输入worklist数据库文件的地址,用来供客户端查询、访问使用。下面我们正式开启worklist的服务端程序,至于开启全过程,可以跟大家推荐CSDN一位博主的精品文章(http://blog.csdn.net/pachleng/article/details/5800513),博文中给出了具体的操作步骤,这个实例是对DCMTK论坛中的补充和更新。大家可以动手试一下。

第一步:建立各级目录

        以我的电脑为例,我在D盘创建了DCMWorklist文件夹,然后建立了两个子文件夹wlistdb和wlistqry。

image

第二步:准备worklist数据库文件,开启worklist服务端服务。

        然后将worklist的数据库文件拷贝到wlistdb目录下,此处参见冷哥博文,记得建立OFFIS子目录。至于worklist数据库文件通常在dcmtk库的源码中已经给出了,默认目录是dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\datawlistdb(我用的是3.6.0版本)。但是源码中的文件通常是.dump扩展名的文件,也就是我们常见的文本文件(用记事本或者Notepad++等工具双击即可打开)。通过利用dcmtk工具包中的dump2dcm.exe可以将.dump文件转换成.wl文件,转换后的.wl文件就是worklist数据库文件。

>dcmp2dcm.exe .\dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wklistdb\wlist1.dump d:\DCMWorklist\wlistdb\OFFIS\wlist.wl

>……

        有了worklist数据库文件后,我们就可以开启worklist服务了,利用的工具就是前文提到的wlmscpfs.exe(从工具名称中的SCP就可以看出这应该是服务端开启服务的)。

>wlmscpfs.exe –d –dfr –dfp d:\DCMWorklist\wlistdb 104     (注:其中的-d是为了方便我们观察工具运行过程而开启的调试开关)

>……

第三步,准备查询文件,开启worklist查询。

        服务端已经准备就绪,下面就是该发起worklist查询服务啦。利用的工具是findscu.exe(从工具名称中的SCU同样可以猜测出这是客户端)。dcmtk源码包中同样给我们提供了查询worklist的查询文件,默认目录是dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wlistqry,打开后可以看到里面以.dump格式存在的文件,再次利用dump2dcm.exe将.dump转换为.wl文件。

>dcmp2dcm.exe .\dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wklistqrt\wlistqry1.dump d:\DCMWorklist\wlistqry\wlistqry.wl

        然后我们利用findscu发起查询请求,

>findscu.exe –d 127.0.0.1 104 d:\DCMWorklist\wlistqry\wlistqry.wl –aec OFFIS

        其中aec代表的是被请求或者说被呼叫的应用端的名称,即我们wlistdb文件夹内的OFFIS子文件夹。

通过以上三步,我们就简单的利用dcmtk提供的wlmscpf.exe和findscu.exe工具包以及相应的wklistdb数据库文件和wklistqry数据库查询文件模拟了worklist服务开启及查询的整个流程。是不是很简单,很容易上手。

实际结果分析:

        上一部分中提到了在使用wlmscpfs.exe和findscu.exe工具包的时候开启了-d调试模式,目的就是为了方便我们跟踪整个通讯的流程。另外为了方便查看,我们利用重定向技术,将wlmscpfs.exe和findscu.exe工具包的调试信息输出到txt文件,方便我们事后进行再次对比查看。下面将两个工具包的调试信息用Notopad++打开,对比分析一下,见下图:

image

        从调试信息可以清晰的看到wlmscpfs.exe与findscu.exe之间的通信流程,该流程在DICOM3.0标准的第四部分(Service Class Specifications )和第八部分(Network Communication Support for Message Exchange)都有详细的介绍,上述的重定向生成的文本文档就是学习DICOM3.0第四、八部分最好的实例。因为dcmtk是开源的,所以这方便我们分析wlmscpfs.exe和findscu.exe两个工具包的源码。具体分析见下一节。

wlmscpfs.exe和findscu.exe工具包源码分析:

  wlmscpfs.exe findscu.exe
C/S worklist服务端 worklist客户端
源码文件 wlmscpfs.cc
wlcefs.cc
wlmactmg.cc
findscu.cc
内部函数 1)ConnectToDataSource();//开启连接
2)WlmActivityManager();
//函数内部利用WSAStartup()启动了Windows套接字服务
3)StartProvidingService();
//启动worklist管理服务,位于wlmactmg.cc文件。函数内部调用(a)(b)两个函数。
(a)ASC_initializeNetwork()函数
ASC_initializeNetwork函数调用了DICOM协议封装的TCP协议函数DUL_InitializeNetwork(该函数内部就会出现我们在套接字编程中常见的socket、setsockopt、bind和listen函数)
(b)WaitForAssociation();函数
WaitForAssociation函数调用了DICOM协议封装的TCP/IP协议函数receiveTransportConnectionTCP(该函数内部利用的是select端口模式,会出现套接字编程中常见的select、accept函数)
4)disconnectfromDataSource();
//断开连接的函数。
1)WSAStartup();//初始化套接字服务
2)DcmFindSCU::initializeNetWork();
//函数内部调用的也是ASC_initializeNetwork函数。
3)DcmFindSCU::performQuery();
//同样该函数内部封装了很多以DUL开头的协议操作函数。DUL是DICOM  Upper Layer 的缩写。
4)WSACleanup();

        从上述分析中我们基本可以看出,worklist的通讯是建立在TCP/IP这一现有协议之上的,可以说是对协议的二次封装。与我们平时进行套接字编程的基本流程相似,搞清楚了这一点,对于分析工具包、学习工具包的使用都会有很大的帮助。

worklist数据库文件或查询文件(*.wl)的生成:

1)问题提出:

        参照冷哥博文(http://blog.csdn.net/pachleng/article/details/5800513http://blog.csdn.net/pachleng/article/details/5827232)中的说明,我们可以很容易的利用DCMTK的工具包学习worklist操作。但是在实际应用模仿过程中,有的人可能会好奇为什么要把wklist.wl和wklistqry.wl文件分别放在不同目录?为什么同为以.wl为扩展名的文件,一个就可以作为worklist的数据库文件放在服务端,而另一个就是客户单的查询文件?如果想发起自己的查询,即C-FIND请求,我们怎么手动生成wklistqry.wl文件?

        针对上述问题,在dcmtk的论坛中也曾经有人提到过,参见(http://forum.dcmtk.org/viewtopic.php?f=1&t=1475&hilit=wlmscpfs.exe%23p5016)。利用UltraEdit工具将wklist.wl和wklistqry.wl文件同时打开,对比如下:

image

        从上图中可以看出,作为worklist客户端数据库文件的wklist.wl中是将患者真实信息以符合DICOM3.0标准字段的形式存储,而客户端发起查询请求的wklistqry.wl文件内部只是简单的需要查询的空字段,即各个字段的值域都为空。参考博文中给出了利用dump2dcm.exe工具包将.dump文件转换成wklistqry.wl文件(dump可以认为是普通的文本文件,可以利用记事本等工具进行直接编辑)。如下图所示:

clip_image002[1]

         例如作为测试用的wlist-2.dump文件中的患者ID为123456,生成后的wlist-2.wl文件中的(0010,0020)字段也是123456。

2)实际测试:

        从dump2dcm.exe工具包的说明我们就可以知道,.wl文件其实就是dcm文件,只是该类文件中并不存在真实的像素信息。通常只包含信息头部分,主要指的是患者的各项信息。因此想利用dcmtk的库函数直接获取.wl文件,其实就是手动构造dcm文件的过程。参见http://support.dcmtk.org/docs/mod_dcmdata.html#Examples中的第二个例子,我们可以手动生成以“.wl”为后缀的dcm文件。具体的代码如下:

 

#include <stdio.h>
#include <tchar.h>
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/dcmdata/dcpxitem.h"
#include "dcmtk/dcmjpeg/djdecode.h"
#include "dcmtk/dcmjpeg/djencode.h"
#include "dcmtk/dcmjpeg/djcodece.h"
#include "dcmtk/dcmjpeg/djrplol.h"
using namespace std;

int main()
{
	char uid[100];
	DcmFileFormat fileformat;
	DcmDataset *dataset = fileformat.getDataset();
	/**********************************************
	*
	*利用下列语句可以生成worklist的数据库文件,即
	*不含有影像信息的dcm文件
	*
	************************************************/
	dataset->putAndInsertString(DCM_SOPClassUID, UID_SecondaryCaptureImageStorage);
	dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));
	dataset->putAndInsertString(DCM_PatientName, "Doe^John");
	OFCondition status = fileformat.saveFile("D:\\DcmWorklist\\worklist\\test.wl", EXS_LittleEndianExplicit);
	if (status.bad())
		cerr << "Error: cannot write DICOM file (" << status.text() << ")" << endl;

	return 0;
}

 

        既然服务端需要的worklist数据库文件和客户端需要的查询文件都是.wl文件,唯一的区别就是一个有值域,一个没有。因此利用上述代码我们既可以生成worklist数据库文件,也可以生成worklist查询文件。

3)测试结果:

        查看findscu.exe工具包,我们可以找到-k选项,也就是在提供了查询文件wklistqry.wl的同时也可以指定限定的查询字段,例如

>findscu 127.0.0.1 104 -v -k 0010,0020="123456" -aec OFFIS wlistqry.wl

        如果不添加-k 0010,0020=“123456”限定选项,查询结果如前文中重定向的结果相同,而添加了限定字段后,我们能够查询到的就只有数据库端中满足PatientID字段为123456的患者数据。具体结果如下:

image

        从中我们可以看到服务端给我们的反馈是PatientID为123456的患者信息,所返回的信息都是wlistqry.wl文件中要求的字段,其中通过-k 0010,0020=“123456”限定项来限定了查询的结果,在服务端的反馈是两个患者中表明有一个匹配的worklist数据库文件,如上图中黄色区域所示。

     猜想:既然我们可以利用dcmtk自由生成客户端的.wl查询文件,而-k 0010,0020=”123456”就是对该查询文件的补充,那么是不是如果我们直接把123456写入到wlistqry.wl中的(0010,0020)字段的值域,而直接利用修改后的查询文件也会得到相同的结果呢?此处利用自己的代码将0010,0020字段的值域填充为123456

 

#include <stdio.h>
#include <tchar.h>
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/dcmdata/dcpxitem.h"
#include "dcmtk/dcmjpeg/djdecode.h"
#include "dcmtk/dcmjpeg/djencode.h"
#include "dcmtk/dcmjpeg/djcodece.h"
#include "dcmtk/dcmjpeg/djrplol.h"
using namespace std;

int main()
{
	char uid[100];
	DcmFileFormat fileformat;
	DcmDataset *dataset = fileformat.getDataset();

	/**********************************************
	*【猜测一】:
	*利用下列语句可以生成worklist的查询文件
	*即,
	*	各个字段数据都为空的dcm文件
	************************************************/
	dataset->putAndInsertString(DCM_SOPClassUID, UID_SecondaryCaptureImageStorage);
	dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));
	dataset->putAndInsertString(DCM_ImplementationVersionName,"OFFIS_DCMTK_361");
	dataset->putAndInsertString(DCM_SpecificCharacterSet,"");
	dataset->putAndInsertString(DCM_PatientName, "");
	dataset->putAndInsertString(DCM_PatientID,"123456");
	dataset->putAndInsertString(DCM_PatientBirthDate,"");
	dataset->putAndInsertString(DCM_PatientSex,"");
	OFCondition status = fileformat.saveFile("D:\\DcmWorklist\\worklist\\testqry.wl", EXS_LittleEndianExplicit);
	if (status.bad())
		cerr << "Error: cannot write DICOM file (" << status.text() << ")" << endl;
	return 0;
}


 

 

        然后利用

>findscu 127.0.0.1 104 -v -aec OFFIS testqry.wl 指令直接发起查询。

        查询结果反馈如下图所示:

image

        上图证明了我们的猜想通过写入wlistqry.wl的相关字段的值域,就等同于在findscu.exe指令中添加-k XXXX,XXXX限定选项

        至此我们详细的介绍了如何模拟worklist的双端服务,如何开启服务端服务、发起客户端查询,关键是对如何利用dcmtk的库函数来生成自定义的查询端.wl文件进行了补充设实例测试。

(完)

 

作者:[email protected]

时间:2014-08-23

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinolover/article/details/115868069

智能推荐

基于多重继承与信息内容的知网词语相似度计算 - 论文及代码讲解_机智翔学长的博客-程序员ITS304

论文:《基于多重继承与信息内容的知网词语相似度计算》-2017-张波,陈宏朝等 查看代码:https://github.com/yaleimeng/Final_word_Similarity总体感受:太乱了,有可能是之前没怎么接触这块。看论文,搞不懂怎么回事,义项、义原是啥,怎么这么多定义,到头来还是不懂两个词的相似度怎么计算,比哈工大词林那篇论文复杂多了。看代码,函数调来调去,一会这个...

《计算机操作系统》重点知识笔记整理(一)_Barry Yan的博客-程序员ITS304_计算机操作系统笔记

《计算机操作系统》重点知识总结1(1-4章)????注意:​ 这篇总结文档参考的配套书籍为《计算机操作系统》(第四版) 相关知识点关联的页码可能只与本书配套。????说明:​ 由于时间关系,该总结的部分知识点可能有所疏落或存在错误,请认真研读不要盲目学习,读者如有补充或问题更正请联系作者[[email protected]],作者将会表示感谢!​ 最后,希望尊重作者劳动成果,请大家转载时注明出处,Thanks!????第一章 操作系统引论1 操作系统的定义

高斯模糊(高斯滤波)原理以及计算过程_StriveZs的博客-程序员ITS304_高斯模糊公式

高斯模糊/高斯滤波通常,图像处理软件会提供模糊滤镜,使图片产生模糊效果。模糊的算法有很多,其中有一种叫高斯模糊(Gaussian Blur),它将正态分布用于图像处理。文本介绍了高斯模糊的算法,你会看到这是一个非常简单易懂的算法。本质上,它是一种数据平滑技术(data smoothing),适用于多个场合,图像处理恰好提供了一个直观的应用实例。高斯模糊的原理所谓模糊,可以理解成每一个像素都取周边像素的平均值。上图中,2是中间点,周边点都是1.中间点取周围点的平均值之后,就会从2变成了1.

ubuntu安装mysql离线包_lmlby的博客-程序员ITS304

Ubuntu安装mysql离线包测试环境:ubuntu12.04-amd64Mysql离线包:mysql-5.5.25-linux2.6-x86_64.tar.gz官方安装步骤如下:注意事项:如果执行scripts/mysql_install_db –user=mysql时出现如下错误:InstallingMySQL system tables..../bin/mysq

MySQL学习之子查询、合并查询结果、别名_MakerGaoGao的博客-程序员ITS304

学习峰哥java教程自学笔记:http://www.java1234.com/javaxuexiluxiantu.html表结构如下:t_book:t_booktype:t_price:1、带in关键字的查询(在后面的查询结果中查询前面的)SELECT * FROM t_book WHE

checkbox实现全选的多种方法_墙角的爬山虎的博客-程序员ITS304_checkbox全选

&lt;script language=javascript&gt; //第一种方法 function selectall1() { var a = document.getElementsByTagName("input"); if(a[0].checked==true){ for (var i=0; i&lt;a.length; i++) if (a[i].type == "checkbox") a[i].checked = false; }

随便推点

Flutter筑基——学好 Dart,才能玩转 Flutter_willwaywang6的博客-程序员ITS304

目录前言正文Dart 开发环境的搭建最后参考前言我们知道 Flutter 这个 UI 框架是使用 Dart 语言开发的,这说明要玩转 Flutter,就要先学好 Dart。那么,怎么学好 Dart 呢?有的同学抱着“不就是一门语言嘛”的心态,直接开始写 Flutter,然后遇到问题了,再去查看 Dart 的文档。这也是一种学习 Dart 的方式,但这种方式可能不适合大多数同学。庆幸地是,可以去查看 Dart 官网上的示例,比如Language-tour,就讲解了 Dart 的语法。但是,官网上的

破解使用radius实现802.1x认证的企业无线网络_Sword-heart的博客-程序员ITS304

0x01前言概述针对开放式(没有密码)无线网络的企业攻击,我个人感觉比较经典的攻击方式有2种,一种是eviltwin,一种是karma。karma应该是eviltwin攻击手法的升级版,攻击者只需要简单的监听客户端发的ssid探测和响应包就可以实现中间人了,受害者很少会有察觉。而且坊间曾有一个错误的认识,认为隐藏的ssid是不受karma影响的。但是实际情况是,客户端如果曾经连接过隐藏的ssid,也会广播这些网络的探测包。尽管karma这种攻击方式已经有10多年的历史了,但是在MAC OSX,ubunt

ffmpeg代码分析(1)--编译裁剪_茜茜她老爹的博客-程序员ITS304_ffmpeg 裁剪编译

我们很少使用到ffmpge完整的功能库,大部分时候,只需要特定的编解码 传输协议,那么该怎么裁剪ffmpeg呢?答案就是configure命令。configure 是一个文本文件,打开以后我们会发现它有很多命令,如下Help options:  --help                   print this message  --list-decoders        

Unity项目常见Lua解决方案性能比较_weixin_33946605的博客-程序员ITS304

测试说明Unity不支持热更新这事情一直是谜一样的痛点,特别是在作者第一个项目上线之后,发现每次更新代价实在太大,可惜官方Roadmap上迟迟没有出现这个功能。UWA之前分享过 Android平台热更新解决方案,直接替换dll是一种解决方式(但iOS上因为使用IL2CPP故而无法实现,而且这事本质上为商业原因而非技术问题)。除此之外还有一个比较常见的...

推荐文章

热门文章

相关标签