【转】dicom网络通讯入门(3)

转自:



dicom网络通讯入门(3) - assassinx - 博客园

接下来可以进行消息传递了 ,也就是dimse ,再来复习下 什么是dimse 。n-set  n-create c-echo 这些都是dimse  他们都是属于一种结构的pdu 那就是tf-pdu(传输数据和命令的都称之为tf-pdu 或者transfer pdu ,协商连接的都称之为associcate pdu) 。dimse 由 许多tag组成,就像文件解析那篇博文一样。
tf-pdu数据结构分析如下:

如果你又要问此图是怎么来的 ,dicom标准第八章 33页。你可能又要问 dimse又是什么 ,dimse全称 dicom 消息服务元素DIMSE(DICOM Message Service Element)
为什么你就说echo属于dimse 。请看dicom标准第七章 的目录: 9     DIMSE-C        9.1.5      C-ECHO SERVICE 。不用我多说了噻。
在这之前还是像以前一样先把tf-pdu的数据结构跟序列化搞了吧:

  1     struct PDVset2     {   3         //0x04 P_DATA-TF4         public byte pduType;5         //pdu长度6         public uint pduLen;7         //pdv长度从作用来看他跟上面有所重复 pdv, presentation data value8         public uint itemLen;9         //这个contextID其实是指协商连接时的presentation context id 10         //最好判断下是否跟协商时的一致11         public byte contextID;12         //消息控制头 确定pdv类型是command 还是data 发送完成与否13         public byte msgControlHeader;14         15         public SortedDictionary<uint, DataElement> elements;16 17         public byte[] serial()18         {19             if ((pduLen != 0 && itemLen != 0) == false)20                 return null;21             //header22             MemoryStream _stream = new MemoryStream((int)pduLen + 6);23             WarpedStream stream = new WarpedStream(_stream);24 25             stream.writeByte(0x04);26             stream.skip_write(1);27             stream.writeUint(pduLen);28             stream.writeUint(itemLen);29             stream.writeByte(contextID);30             stream.writeByte(msgControlHeader);31             //items32             foreach (DataElement item in elements.Values)33             {34                 stream.writeBytes(item.serial());35             }36 37             _stream.Flush();38 39             byte[] data = new byte[_stream.Length];40             Array.Copy(_stream.GetBuffer(), data, _stream.Length);41             stream.close();42             _stream.Close();43             return data;44         }45     }46     enum pdvType47     {48         command, data, commandAndData49     }50 51     struct DataElement52     {53         public uint _tag;54         public WarpedStream.byteOrder bytOrder;55         public bool explicitVR;//是显式VR的 否则隐式VR56         public uint tag57         {58             get { return _tag; }59             set60             {61                 _tag = value;62                 VR = VRs.GetVR(value);63                 uint _len = VRs.getLen(VR);64                 if (_len != 0)65                     len = _len;66             }67         }68         public ushort VR;69         //虽然长度为uint 但要看情况隐式时都是4字节 显式时除ow那几个外都是2字节70         //如果为ow 显示不但长度为4 在之前还要跳过2字节,除ow那几个之外不用跳过71         public uint len;72         public byte[] value;73         public IList<DataElement> items ;//子项74         public bool haveItems;//是否包含子项75 76         //值的显示77         public string showValue()78         {79             if (haveItems )80                 return null;81 82             if (value != null)83                 return Tags.VFdecoding(VR, value, bytOrder);84             else85                 return null;86         }87         //赋值88         public void setValue(string valStr)89         {90             if (haveItems )91                 return;92 93             if (VRs.IsStringValue(VR)) {94                 len = (uint)valStr.Length;95                 value = Tags.VFencoding(VR, valStr, bytOrder, len);96             }97             else if (len != 0)//就是这个破地方 因为element的连续使用 导致会截断字符串98                 value = Tags.VFencoding(VR, valStr, bytOrder, len);99             else
100             {
101                 value = Tags.VFencoding(VR, valStr, bytOrder);
102                 if (VRs.IsStringValue(VR))
103                     len = (uint)value.Length;
104             }
105         }
106         //序列化
107         public byte[] serial()
108         {
109             MemoryStream data_serial = new MemoryStream();
110             serial(this, data_serial);
111             byte[] data = new byte[data_serial.Length];
112             Array.Copy(data_serial.GetBuffer(), data, data.Length);
113             data_serial.Close();
114             return data;
115         }
116         //序列化的递归调用
117         public void serial(DataElement element, MemoryStream data_serial)
118         {
119             //int len_serial = element.getSerialLen();
120             //if ((VR == VRs.SQ && len_serial == UInt32.MaxValue) || (tag == 0xfffee000 && len == UInt32.MaxValue))//靠 遇到文件夹开始标签了
121             if (element.haveItems )
122             {
123                 //开始标记
124                 data_serial.WriteByte((byte)((element._tag & 0x00ff0000) >> 16));
125                 data_serial.WriteByte((byte)((element._tag & 0xff000000) >> 24));
126                 data_serial.WriteByte((byte)(element._tag & 0x000000ff));
127                 data_serial.WriteByte((byte)((element._tag & 0x0000ff00) >> 8));
128                 data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff);
129 
130                 foreach (DataElement item in element.items)
131                 {
132                     item.serial(item, data_serial);
133                 }
134 
135                 //结束标记
136                 if (element.VR == VRs.SQ)
137                 {
138                     data_serial.WriteByte((byte)((0xfffee0dd & 0x00ff0000) >> 16));
139                     data_serial.WriteByte((byte)((0xfffee0dd & 0xff000000) >> 24));
140                     data_serial.WriteByte((byte)(0xfffee0dd & 0x000000ff));
141                     data_serial.WriteByte((byte)((0xfffee0dd & 0x0000ff00) >> 8));
142                 }
143                 else
144                 {
145                     data_serial.WriteByte((byte)((0xfffee00d & 0x00ff0000) >> 16));
146                     data_serial.WriteByte((byte)((0xfffee00d & 0xff000000) >> 24));
147                     data_serial.WriteByte((byte)(0xfffee00d & 0x000000ff));
148                     data_serial.WriteByte((byte)((0xfffee00d & 0x0000ff00) >> 8));
149                 }
150                 data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00);
151             }
152             else
153             {
154                 byte[] data = new byte[element.getSerialLen()];
155                 uint _len = element.len;
156                 if (_len % 2 != 0)
157                     _len++;
158                 if (element.VR == VRs.SQ)
159                     _len = 0xffffffff;
160 
161                 data[0] = (byte)((element._tag & 0x00ff0000) >> 16);
162                 data[1] = (byte)((element._tag & 0xff000000) >> 24);
163                 data[2] = (byte)(element._tag & 0x000000ff);
164                 data[3] = (byte)((element._tag & 0x0000ff00) >> 8);
165 
166                 if (element.explicitVR)//显示VR
167                 {
168                     data[4] = 0x00;
169                     data[5] = 0x00;
170                     if (VRs.IsLengthField16Bit(VR))
171                     {
172                         if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
173                         {
174                             data[6] = (byte)(_len & 0x000000ff);
175                             data[7] = (byte)((_len & 0x0000ff00) >> 8);
176                         }
177                         else
178                         {
179                             data[6] = (byte)((_len & 0x0000ff00) >> 8);
180                             data[7] = (byte)(_len & 0x000000ff);
181                         }
182 
183                         for (int i = 0; i < element.value.Length; i++)
184                             data[8 + i] = element.value[i];
185                     }
186                     else
187                     {
188                         if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
189                         {
190                             data[6] = (byte)((_len & 0xff000000) >> 24);
191                             data[7] = (byte)((_len & 0x00ff0000) >> 16);
192                             data[8] = (byte)((_len & 0x0000ff00) >> 8);
193                             data[9] = (byte)(_len & 0x000000ff);
194 
195                         }
196                         else
197                         {
198                             data[6] = (byte)(_len & 0x000000ff);
199                             data[7] = (byte)((_len & 0x0000ff00) >> 8);
200                             data[8] = (byte)((_len & 0x00ff0000) >> 16);
201                             data[9] = (byte)((_len & 0xff000000) >> 24);
202                         }
203                         if (element.value == null)
204                             throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
205 
206                         for (int i = 0; i < element.value.Length; i++)
207                             data[10 + i] = element.value[i];
208                     }
209                     //len_ser = (int)(4 + 2 + 4 + len);
210                     //len_ser = (int)(4 + 2 + len);
211                 }
212                 else //隐式Vr
213                 {
214                     if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
215                     {
216                         data[4] = (byte)((_len & 0xff000000) >> 24);
217                         data[5] = (byte)((_len & 0x00ff0000) >> 16);
218                         data[6] = (byte)((_len & 0x0000ff00) >> 8);
219                         data[7] = (byte)(_len & 0x000000ff);
220                     }
221                     else
222                     {
223                         data[4] = (byte)(_len & 0x000000ff);
224                         data[5] = (byte)((_len & 0x0000ff00) >> 8);
225                         data[6] = (byte)((_len & 0x00ff0000) >> 16);
226                         data[7] = (byte)((_len & 0xff000000) >> 24);
227 
228                     }
229                     if (element.value == null)
230                         throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
231 
232                     for (int i = 0; i < element.value.Length; i++)
233                         data[8 + i] = element.value[i];
234                 }
235                 data_serial.Write(data, 0, data.Length);
236             }
237         }
238 
239         //获取单个元素序列化长度的递归调用
240         public void getSerialLen_item(DataElement element, ref int len)
241         {
242             if (element.haveItems)
243             {
244                 len += element.getHeaderLen();
245                 foreach (DataElement item in element.items)
246                     getSerialLen_item(item, ref len);
247                 len += 8;
248             }
249             else
250             {
251                 if (element.value != null)
252                     len += element.getHeaderLen() + element.value.Length;
253                 else
254                     len += element.getHeaderLen();
255             }
256             if (len % 2 != 0)//文件元信息元素整体字节数一定是偶数(包括tag VR 数据长度 数据 这些一起)
257                 len++;
258         }
259 
260         //获取序列化后整个元素的长度
261         public int getSerialLen()
262         {
263             int serial_len=0;
264             getSerialLen_item(this, ref serial_len);
265             return serial_len;
266         }
267 
268         //获取item的header长度
269         public int getHeaderLen()
270         {
271             int len_ser = 0;
272             int len_tmp = 0;
273             if (explicitVR)//显示VR
274             {
275                 if (tag == 0xfffee000 || tag == 0xfffee00d || tag == 0xfffee0dd)
276                     len_ser = 4 + 4;
277                 else if (VR == VRs.OB || VR == VRs.OW || VR == VRs.OF ||
278                         VR == VRs.UT || VR == VRs.SQ || VR == VRs.UN)
279                     len_ser = (int)(4 + 2 + 4 + len_tmp);
280                 else
281                     len_ser = (int)(4 + 2 + len_tmp);
282             }
283             else //隐式Vr
284             {
285                 len_ser = (int)(4 + 4 + len_tmp);
286             }
287 
288             return len_ser;
289         }
290     }

不要问我pdv是啥 第一篇就出现过,pdv 即p data value ,它包括许多的data element 也就是俗称tag。一个元素接一个元素 直到结束 跟文件解析的时候一样 ,他的vr方式 以及字节序 在协商连接的时候就已确定 你只管读就是了。那么新的问题又来了 echo这个dimse到底包含哪些tag 他们的值又应该各是多少?为了解决你这个疑问我又要翻一个表出来:


你又要问这个表是怎么来的 ,dicom第七章 53页。具体每个tag的作用各是什么 请参照右边的说明,有三个地方我要提一下:

affected sop class uid
受影响的sop uid ,看过第一篇里图的筒子都知道 打印有打印sop uid ,filmbox 有filmboxuid,那么这里echo也有 对应的 他就是 :

1 //SOPClass: Verification SOP Class 
2 public const String Verification = "1.2.840.10008.1.1";

为啥是这个 我不会再说了 你懂的。
command field
这个是作甚的,他的作用是用来区分不同的dimse 比如 c-create  c-find ,不用我多讲了 旁边的说明已经很明显了 echo时他的值应设置成0x0030  。
command data set type
数据集选项 ,说白了就是给个标识 后面有无数据集,我们这里自然是没有 那么应设置成0x0101 。

好了开工吧,打住 不是说还有服务类规范么 ,只有复杂的才有服务类规范 我们这个echo是非常非常非常之简单的 所以没有服务类规范 直接开动吧:

 1         //组织Verification_CECHORSP响应原语2         //rq端无data ,rsp端无data3         public void Verification_CECHORQ()4         {5             PDVset rq = new PDVset();6             rq.pduType = 0x04;7             rq.contextID = pstContextId;8             rq.msgControlHeader = 0x03;9             rq.elements = new SortedDictionary<uint, DataElement>();
10 
11             int len = 0;
12 
13             DataElement element = new DataElement();
14             element.bytOrder = bytOrder;
15 
16             element.tag = 0x00000002;
17             element.setValue(UIDs.Verification);
18             rq.elements.Add(0x00000002, element);
19             len += (element.getSerialLen());
20 
21             element.tag = 0x00000100;
22             element.setValue(0x0030.ToString());
23             rq.elements.Add(0x00000100, element);
24             len += (element.getSerialLen());
25 
26             element.tag = 0x00000110;
27             element.setValue(0x03.ToString());
28             rq.elements.Add(0x00000110, element);
29             len += (element.getSerialLen());
30 
31             element.tag = 0x00000800;//有无对应的数据段
32             element.setValue(0x0101.ToString());
33             rq.elements.Add(0x00000800, element);
34             len += (element.getSerialLen());
35 
36 
37             element.tag = 0x00000000;//消息原语数据长度
38             element.setValue(len.ToString());
39             rq.elements.Add(0x00000000, element);
40             //len += (element.getSerialLen());
41 
42             rq.itemLen = (uint)(12 + 2 + len);
43 
44             rq.pduLen = rq.itemLen + 4;
45 
46             //进行c-echo-rsp响应
47             stream.writeBytes(rq.serial());
48         }


看看代码里面特定的地方 是不是跟我上面描述的一样?就这样so easy  。看到没 其实我这些都是在dicom文档里翻的 就这样而已没什么神奇的 相信你也能。再来复习下dicom标准跟网络通讯相关的几个章节 :

DICOM Part 4: Service Class Specifications  ,服务类规范

DICOM Part 7: Message Exchange 消息交换

DICOM Part 8: Network Communication Support for Message Exchange 网络通讯对消息交换的支持

按照他们的套路来 就水到渠成 。


这是我的测试结果 不用怀疑哥的水平 哥是拿到医院去测试过的:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435787.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】ubuntu 下 VNCview 远程桌面无法传输文件问题

转自&#xff1a;ubuntu18.04VNCview文件传输问题_gsls200808的专栏-CSDN博客_vnc传输文件按钮不可用 很多文章说VNCview不能传输文件&#xff0c;实际上这是一个误区。 以ubuntu为例&#xff0c;默认使用 sudo apt-get vnc4server 这个命令安装上的vncserver实际是tigerVNC…

在何时该用什么方式编译WinCE

这是一篇很好的文章&#xff0c;很多开发者其实并没有搞清楚这个问题&#xff1a;在何时该用什么方式编译WinCE 导致走了很多弯路&#xff0c;也包括我自己 感谢作者写了这篇文章 这么好的文章&#xff0c;我想应该翻译过来给大家 在何时该用什么方式编译WinCE 在新闻组里&…

【转】Ubuntu 16.04 安装 CUDA10.1 (解决循环登陆的问题)

转自&#xff1a;Ubuntu 16.04 安装 CUDA10.1 &#xff08;解决循环登陆的问题&#xff09; - Hongkai_Ding - 博客园 0. 前言 这里直接用 cuda安装文件同时安装 NVIDIA 驱动和 CUDA&#xff0c;没有单独安装更高版本的 NVIDIA 驱动&#xff1b;此安装是在 Intel 集显下的图形…

【转】Ubuntu 16.04 Nvidia驱动安装(run方式)

转自&#xff1a;Ubuntu 16.04 Nvidia驱动安装(run方式)_lihe的博客-CSDN博客 1.下载驱动程序 Nvidia驱动下载 https://www.geforce.cn/drivers/beta-legacy http://www.nvidia.cn/Download/index.aspx 根据显卡选择适用的驱动版本&#xff0c;下载完之后是一个名称为 NVIDIA…

2008R2Win7管理九DNS冗余和恢复

2008R2Win7管理九DNS冗余和恢复 本文介绍如何在ad中的主dns崩溃后快速利用备份dns将主dns恢复.以及在主dns完好的时候进行dns冗余备份 本文在file这台服务器上安装好dns角色来作为主dns的备份,安装角色的过程不在详细描述,略过.相信大家都知道了. 本问对拓扑图略有改动,故再次更…

【转】DICOM通讯(ACSE->DIMSE->Worklist)

转自&#xff1a;DICOM通讯&#xff08;ACSE-&#xff1e;DIMSE-&#xff1e;Worklist&#xff09; - 知乎 1 DICOM通讯概要介绍 DICOM通讯和TCP IP的设计原理别无二致。从用户数据到网络上传输数据的过程中&#xff0c;要经历多层协议处理&#xff0c;每经一层&#xff0c;就…

[Silverlight]16进制颜色转ARGB及Color转Int32

16进制色字符串转ARGB做Web的都喜欢用16进制字符串标识颜色&#xff0c;比如“#F5F5F5”这类的&#xff0c;自然这个到了Silverlight/WPF中就要用ARGB表示&#xff1a;“#FFF5F5F5”&#xff08;前两位标识Alpha&#xff0c;即透明度&#xff09;。这在XAML中没有任何问题&…

【转】DICOM通信 - PDU数据包(1)

转自&#xff1a;DICOM通信 - PDU数据包(1)_翼遥君的博客-CSDN博客 DICOM上层协议为DIMSE提供透明的网络数据传输服务&#xff0c;即以上层协议规定的协议数据单元传送接收DIMSE的命令流与数据流。上层协议中制定了上层服务以及协议数据单元(Protocol Data Unit&#xff0c;PD…

【转】DICOM通信 - PDU数据包(2)

转自&#xff1a;DICOM通信 - PDU数据包(2)_翼遥君的博客-CSDN博客 DICOM通信-PDU数据包(1)博客中主要分析了DICOM通信协议的连接协商请求和连接协商应答。 在7个PDU数据包中还有5个数据包&#xff0c;从结构上来看&#xff0c;他们比协议的协商过程要简单明了一些。 这篇博客…

“启动Word时提示出错,只能用安全模式才能打开”的解决方法

启动Word时提示出错&#xff0c;只能用安全模式才能打开&#xff0c;在这种模式下&#xff0c;一些功能将被禁用。而且&#xff0c;每次启动都会出现这样的提示信息&#xff0c;同事们很多向我求助。提示窗口如下&#xff1a; 于是我采取了以下一些措施&#xff1a; 1、用Offic…

DotNET内存管理与垃圾回收[转]

1. Stack&#xff08;栈&#xff09;和Heap&#xff08;堆&#xff09;每个线程对应一个stack&#xff0c;线程创建的时候CLR为其创建这个stack&#xff0c;stack主要作用是记录函数的执行情况。值类型变量&#xff08;函数的参数、局部变量等非成员变量&#xff09;都分配在st…

【转】WPF XAML X名称空间详解

转自&#xff1a;WPF XAML X名称空间详解_郎涯技术-CSDN博客 X名称空间里面的成员(如X:Name,X:Class)都是写给XAML编译器看的、用来引导XAML编译器将XAML代码编译为CLR代码。 【X名称空间里面到底都有些什么】 x名称空间映射的是:http://schemas.microsoft.com/winfx/2006/xam…

【转】DICOM医学图像处理:DICOM网络传输

背景&#xff1a; 专栏取名为DICOM医学图像处理原因是&#xff1a;博主是从医学图像处理算法研究时开始接触DICOM协议的。当初认识有局限性&#xff0c;认为DICOM只是一个简单的文件格式约定&#xff0c;简而言之&#xff0c;我当时认为DICOM协议就是扩展名为DCM文件的格式说明…

symbian c++ 开发环境Carbide.c++搭建

需要的东东&#xff1a;JDK1.3.1以上(自带了&#xff0c;不用安装)&#xff0c;SDK 2.0 以上&#xff08;CW版本&#xff09;&#xff0c;perl5.8.x carbide.c 免费版本 所以的东西都默认安装,下一步-->下一步,^_^! 现在开发用的最多的可能就是VC6了&#xff0c;听说以…

【转】9、XAML名称空间详解

转自&#xff1a;9、XAML名称空间详解 - 种花生的读书人 - 博客园 XAML命名空间 <Window xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"> </Window> xmlnshttp:…

wince使用自定义字体

在wince中使用自定义字体其实很简单&#xff0c;只要将自已的字体加到你的程序中就可以使用了&#xff0c;在使用完后再将其清除掉。以wince中加使用"Georgia”字体为例&#xff1a; 在程序初始化的时候将你的字体加载进程序 AddFontResource(L"NandFlash\\Font\\Geo…

【转】ubuntu 开机sudo启动应用程序

转自&#xff1a;ubuntu 开机sudo启动应用程序_Honhy的博客-CSDN博客_sudo 启动应用 第一步&#xff1a;创建一个脚本(run.sh)&#xff0c;我是放在桌面&#xff0c;脚本内容为: echo "hon123"|sudo -S /home/python/qt/Tools/QtCreator/bin/qtcreator exit 0 橙色的…

WinCE Emulator使用介绍

最近用了一下WinCE的模拟器&#xff0c;我做WinCE有几年了&#xff0c;从来没有用过WinCE的模拟器&#xff0c;第一次接触WinCE的时候就是一块S3C2410的板子和PB4.2的开发平台&#xff0c;然后就开始折腾&#xff0c;后来各种开发板都用过&#xff0c;WinCE也从4.2版本用到了6.…

【转】Ubuntu Desktop下自动启动终端并运行脚本

转自&#xff1a;Ubuntu Desktop下自动启动终端并运行脚本 - 简书 alexubuntu19:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 19.04 Release: 19.04 Codename: disco alexubuntu19:~# uname -a Linux ubunt…

【转】DICOM简述!!!!

转自&#xff1a;DICOM简述 - 简书 视频教程&#xff1a;https://www.bilibili.com/video/av66144772 DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;即医学数字成像和通信&#xff0c;是医学图像和相关信息的国际标准&#xff08;ISO 12052&…