dicom读取方法.docx
《dicom读取方法.docx》由会员分享,可在线阅读,更多相关《dicom读取方法.docx(11页珍藏版)》请在冰豆网上搜索。
![dicom读取方法.docx](https://file1.bdocx.com/fileroot1/2023-1/7/5f42363b-3d88-40d3-ab34-aea127b0c36d/5f42363b-3d88-40d3-ab34-aea127b0c36d1.gif)
dicom读取方法
Dicom格式文件解析器
学数字图像与通讯,这里讲的暂不涉及通讯那方面的问题只讲*.dcm也就是diocm格式文件的读取,读取本身是没啥难度的无非就是字节码流处理。
只不过确实比较繁琐。
分析
整体结构先是128字节所谓的导言部分,说俗点就是没啥意义的破数据跳过就是了,然后是dataElement依次排列的方式就是一个dataElement接一个dataElement的方式排到文件结尾通俗的讲dataElement就是指tag就是破Dicom标准里定义的数据字典。
tag是4个字节表示的前两字节是组号后两字节是偏移号比如0008,0018。
所有dataElement在文件中都是按tag排序的比如0002,00010002,00020003,0011
文件整体结构如下:
又把论文里的这图贴上来总结的很好。
单个dataElement的结构如下:
显示VR:
VR为OBOWOFUTSQUN的元素结构
组号
元素号
VR
预留
值长度
数据元素值
2
2
2
2(0x00,0x00)
4
由数据长度决定
显示VR:
VR为普通类型时元素结构(少了预留那一行)
组号
元素号
VR
值长度
数据元素值
2
2
2
4
由数据长度决定
隐式VR时元素结构
组号
元素号
值长度
数据元素值
2
2
4
由数据长度决定
要问VR是啥东东,值表示法啥叫值表示法啊俺不懂intstringshortushort懂不就是这个意思,Dicom标准真坑爹非要整个怪怪的概念。
VR总共27个跟c#值类型对应关系我都写好了:
1stringgetVF(stringVR,byte[]VF)
2{
3stringVFStr=;
4switch(VR)
5{
6case"SS":
7VFStr=(VF,0).ToString();
8break;
9case"US":
10VFStr=(VF,0).ToString();
11
12break;
13case"SL":
14VFStr=(VF,0).ToString();
15
16break;
17case"UL":
18VFStr=(VF,0).ToString();
19
20break;
21case"AT":
22VFStr=(VF,0).ToString();
23
24break;
25case"FL":
26VFStr=(VF,0).ToString();
27
28break;
29case"FD":
30VFStr=(VF,0).ToString();
31
32break;
33case"OB":
34VFStr=(VF,0);
35break;
36case"OW":
37VFStr=(VF,0);
38break;
39case"SQ":
40VFStr=(VF,0);
41break;
42case"OF":
43VFStr=(VF,0);
44break;
45case"UT":
46VFStr=(VF,0);
47break;
48case"UN":
49VFStr=break;
51default:
52VFStr=break;
54}
55returnVFStr;
56}
找个dicom文件在十六进制编辑器下瞧瞧给你整明白:
所有dataElement从前到后按tag又可简单分段:
文件元dataElement
不受传输语法影响总是以显示VR方式表示因为它里面就定义了传输语法
普通dataElement
受传输语法影响显示VR表示方式还是隐式VR表示方式
像素数据dataElement
最重要也是最大的一个数据项其实存储的就是图像数据
几个特殊的tag很重要前面说过了tag就是dicom里定义的字典。
文件元dataElement和跟像素数据相关的dataElement都很重要,其他的很多如果全部照顾完的话估计得写上千行switch语句吧,所以没有必要一般我们一般只关键的tag。
并且在隐式语法下要确定VR也必须根据字典来确定
关键的tag如下:
1stringgetVR(stringtag)
2{
3switch(tag)
4{
5case"0002,0000":
))
59{
60case".jpg":
61(filename,62break;
63case".bmp":
64(filename,65break;
66case".png":
67(filename,68break;
69default:
70break;
71}
72}
73publicboolgetImg()ubstring(5));
82cols=(tags["0028,0011"].Substring(5));
83
84colors=(tags["0028,0002"].Substring(5));
85dataLen=(tags["0028,0100"].Substring(5));
86validLen=(tags["0028,0101"].Substring(5));
87
88gdiImg=newBitmap(cols,rows);
89
90BinaryReaderdicomFile=newBinaryReader(fileName));
91
92;
93
94longreads=0;
95for(inti=0;i<;i++)
96{
97for(intj=0;j<;j++)
98{
99if(reads>=pixDatalen)
100break;
101byte[]pixData=(dataLen/8*colors);
102reads+=;
103
104Colorc=;
105if(colors==1)
106{
107intgrayGDI;
108
109doublegray=(pixData,0);
110oString("x4")+","+
152().ToString("x4");
153
154stringVR=;
155UInt32Len=0;
156//读取VR跟Len
157//对OBOWSQ要做特殊处理先置两个字节0然后4字节值长度
158//------------------------------------------------------这些都是在读取VR一步被阻断的情况
159if(0,4)=="0002")//文件头特殊情况
160{
161VR=newstring
(2));
162
163if(VR=="OB"||VR=="OW"||VR=="SQ"||VR=="OF"||VR=="UT"||VR=="UN")
164{
165;
166Len=();
167}
168else
169Len=();
170}
171elseif(tag=="fffe,e000"||tag=="fffe,e00d"||tag=="fffe,e0dd")//文件夹标签
172{
173VR="**";
174Len=();
175}
176elseif(isExplicitVR==true)//有无VR的情况
177{
178VR=newstring
(2));
179
180if(VR=="OB"||VR=="OW"||VR=="SQ"||VR=="OF"||VR=="UT"||VR=="UN")
181{
182;
183Len=();
184}
185else
186Len=();
187}
188elseif(isExplicitVR==false)
189{
190VR=getVR(tag);//无显示VR时根据tag一个一个去找真烦啊。
191Len=();
192}
193//判断是否应该读取VF以何种方式读取VF
194//-------------------------------------------------------这些都是在读取VF一步被阻断的情况
195byte[]VF={0x00};
196
197if(tag=="7fe0,0010")//图像数据开始了
198{
199pixDatalen=Len;
200pixDataOffset=;
202VR="UL";
203VF=(Len);
204}
205elseif((VR=="SQ"&&Len==||(tag=="fffe,e000"&&Len==)//靠遇到文件夹开始标签了
206{
207if(enDir==false)
208{
209enDir=true;
210(0,;
211folderTag=tag;
212}
213else
214{
215leve++;//VF不赋值
216}
217}
218elseif((tag=="fffe,e00d"&&Len==||(tag=="fffe,e0dd"&&Len==)//文件夹结束标签
219{
220if(enDir==true)
221{
222enDir=false;
223}
224else
225{
226leve--;
227}
228}
229else
230VF=((int)Len);
231
232stringVFStr;
233
234VFStr=getVF(VR,VF);
235
236//----------------------------------------------------------------针对特殊的tag的值的处理
237//特别针对文件头信息处理
238if(tag=="0002,0000")
239{
240fileHeadLen=Len;
241fileHeadOffset=}
243elseif(tag=="0002,0010")//传输语法关系到后面的数据读取
244{
245switch(VFStr)
246{
247case"\0":
//显示little
248isLitteEndian=true;
249isExplicitVR=true;
250break;
251case"\0":
//显示big
252isLitteEndian=false;
253isExplicitVR=true;
254break;
255case"":
//隐式little
256isLitteEndian=true;
257isExplicitVR=false;
258break;
259default:
260break;
261}
262}
263for(inti=1;i<=leve;i++)
264tag="--"+tag;
265//------------------------------------数据搜集代码
266if((VR=="SQ"&&Len==||(tag=="fffe,e000"&&Len==||leve>0)//文件夹标签代码
267{
268(tag+"("+VR+"):
"+VFStr);
269}
270elseif(((tag=="fffe,e00d"&&Len==||(tag=="fffe,e0dd"&&Len==)&&leve==0)//文件夹结束标签
271{
272(tag+"("+VR+"):
"+VFStr);
273(folderTag+"SQ",());
274}
275else
276(tag,"("+VR+"):
"+VFStr);
277}
278}
279}
好了收工。
测试下成果
1if()!
=
2return;
3
4stringfileName=;
5
6handler=newDicomHandler(fileName);
7
8(textBox1);
9
10="DicomViewer-"+;
11
12
13();
这里处理gdi位图的时候直接用的setPix处理速度比较慢所以用了backgroundWorker,实际应用中请使用内存缓冲跟指针的方式
否则效率低了是得不到客户的认可的哦,gdi位图操作可使用lockBits加指针的方式,12位的灰度像素数据可以第一次读取后缓存到内存中以方便后面调窗的快速读取
这点代码也不难哈对指针什么的熟点就行了,前几章都有。
这是ezDicom经过公认测试的软件我们来跟他对比一下,打开
调窗测试,我们注意到两个东西在没有窗宽窗位时默认窗宽是2047+1即2048窗位是2048/2即1024
直观的感受是调窗宽像在调图像对比度,调窗位像在调图像亮度。
窗宽为255的时候图像是最瑞丽的因为255其实就是8位图像的默认窗宽。
注意窗位那里有小小区别,ez窗位显示的是根据1024那里为0开始偏移而我的程序是根据窗宽中间值没有偏移
没有偏移的情况稍微符合逻辑点吧。
但是可以看到原理是一样的结果是一样的。