dicom讀取方法_第1頁
dicom讀取方法_第2頁
dicom讀取方法_第3頁
dicom讀取方法_第4頁
dicom讀取方法_第5頁
已閱讀5頁,還剩12頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、Dicom格式文件解析器學數字圖像與通訊,這里講的暫不涉及通訊那方面的問題 只講*.dcm也就是 diocm格式文件的讀取,讀取本身是沒啥難度的 無非就是字節碼 數據流處理。 只不過確實比較繁瑣。分析整體結構先是128字節所謂的導言部分,說俗點就是沒啥意義的破數據跳過就是了,然后是dataElement依次排列的方式 就是一個dataElement接一個 dataElement的方式排到文件結尾 通俗的講dataElement就是指tag就是破 Dicom標準里定義的數據字典。tag是4個字節表示的 前兩字節是組號后兩字 節是偏移號 比如0008,0018。所有dataElement在文件中都

2、是按tag排序的 比如 0002,00010002,00020003,0011文件整體結構如下:又把論文里的這圖貼上來總結的很好。單個dataElement的結構如下:顯示VR : VR為OB OW OF UT SQ UN的元素結構組號兀系3VR預留值長度數據元素值2222 (0x00,0x00)4由數據長度 決定顯示VR: VR為普通類型時元素結構(少了預留那一行)組號元素號VR值長度數據元素值2224由數據長度 決定隱式VR時元素結構組號值長度:數據元素值224由數據長度 決定123456789101112131415161718192021222324252627282930313233

3、34353637要問VR是啥東東,值表小法 啥叫值表小法啊 ushort懂不 就是這個意思,Dicom標準真坑爹VR總共27個 跟c#值類型對應關系我都寫好了:string getVF( string VR, byte VF)string VFStr = string .Empty; switch (VR)case SS:VFStr = BitConverter.ToInt16(VF, break;case US:VFStr = BitConverter.ToUInt16(VF,break;case SL :VFStr = BitConverter.ToInt32(VF,break;case

4、UL:VFStr = BitConverter.ToUInt32(VF,break;case AT:VFStr = BitConverter.ToUInt16(VF,break;case FL :VFStr = BitConverter.ToSingle(VF,break;case FD:VFStr = BitConverter.ToDouble(VF,break;case OB:VFStr = BitConverter.ToString(VF, break;case OW:俺不懂 int string short 非要整個怪怪的概念。0).ToString();0).ToString();0

5、).ToString();0).ToString();0).ToString();0).ToString();0).ToString();0);0);VFStr = BitConverter.ToString(VF,383940414243444546474849505152535455break;case SQ:VFStr = BitConverter.ToString(VF, break;case OF:VFStr = BitConverter.ToString(VF, break;case UT:VFStr = BitConverter.ToString(VF, break;case U

6、N:VFStr = Encoding.Default.GetString(VF); break;default :VFStr = Encoding.Default.GetString(VF); break;return VFStr;0);0);0);56 09009009oa 0。 on so oa ua 30 oa so oa ua 00 oo oa qq bu01100901900 0fi 00 09 00 OS 9Q OB 09 09 OS 9Q OB 00 國。OU0(1009029so oo ao oo oa ao no oo oo oa ao 90 00 so 00 aa00000

7、93000 00 00 OS 00 00 30 00 09 00 00 30 00 0000009080UB00 00 00 99 OB 00 網 BO 09 OB 00 網 BO 00 DO 00auoaaasa00 06 09 00 00 00 00 90 00 叫饕 酹那 08 09011309969ao qb aa 09 as 99 aa as oo w講人如 aa0000907099 Q9 的觸 99 09 眄 90 觸 09 09 90 90 0U44 49 45 如呢 90 90 99 55 4C 仙 90 BE 99 00 99OWIWO02 00 01 GO 4F 42 90

8、 90 02 00 00 M 90 01 02 GODuooaoaa02 00 55 4? 1ft UQ 31 NE 32 ZE 3& 34 30 2E 31 3H090090b930 3Q 38 ZE 在F ZE 31 ZE 34 ZE 31 ZE 31 ZE 32 9004哂02 Q0 U3 。件55 4? 28 00黑轉概*弗淳典 39 ZEOOBflOOdO31 31 38 37 30 34 ZE 39 !?嗯格工整慈亨密乂辿江UU0090e931 2E 31 2E 32 30 30 39 32 37 39 34 39 34O9OO90FO0900010031 30 30 39 3S

9、ZE 31 ZE 32 ZE 31 90 92 00 1Z 090000811 55 49 1C 09 31 ZE 32 ZE 32 3T $6 ZE 30 2E 37 3Z0000012033 30 30 31 30 2E 33 2E SO 2E 33 2E 35 2E 3U 000000(113002 DO 13 00 S3 U8 10 00 *F46 49 53 5F HU 43UI. .1 2:27ft.0.7Z 300.EH.UFF1E OCUL|ui J.i .4s40.noOHS 匚 T 1 - * F 9Tat B L滬川 . . .JI 1 .1 .2.113

10、704.9.4021.3.r-iUI.112.8年配1 rw酷 *1I.Z.11.1.2O09279494UO找個dicom文件在十六進制編輯器下瞧瞧給你整明白:所有dataElement從前到后按tag又可簡單分段:文件元dataElement不受傳輸語法影響 總是以顯示VR方式表示 因為它 里面就定義了傳輸語法普通 dataElement受傳輸語法影響 顯示VR表示方式還是隱式VR表示方 式像素數據dataElement最重要也是最大的一個數據項其實存儲的就是圖像數據幾個特殊的tag很重要前面說過了 tag就是dicom里定義的字典。文件元 dataElement和跟像素數據相關的data

11、Element都很重要,其他的很多 如果 全部照顧完的話估計得寫上千行 switch語句吧,所以沒有必要一般我們一般只 抓更關鍵的tag。并且在隱式語法下要確定VR也必須根據字典來確定 關鍵的tag如下:1string getVR( string tag)23switch (tag)45case 0002,0000 : /文件元信息長度6return UL;7break;8case 0002,0010 :/ 傳輸語法9return UI;10break;11case 0002,0013 :/文件生成程序的標題12return SH;13break;14case 0008,0005 : / 文本

12、編碼15return CS;16break;17case 0008,0008:18return CS;19break;20case 0008,1032 :/ 成像時間21return SQ;22break;23case 0008,1111:24return SQ;25break;26case 0008,0020 :/ 檢查日期27return DA;28break;29case 0008,0060 : / 成像儀器30return CS;31break;32case 0008,0070 : / 成像儀廠商33return LO;343536373839404142434445464748495

13、0515253545556575859606162636465666768697071727374757677break;case 0008,0080 :return LO;break;case 0010,0010 :/ 病人姓名return PN;break;case 0010,0020 : / 病人 idreturn LO;break;case 0010,0030 : / 病人生日return DA;break;case 0018,0060 : / 電壓return DS;break;case 0018,1030 :/ 協議名return LO;break;case 0018,1151 :

14、return IS ;break;case 0020,0010 :/ 檢查 IDreturn SH;break;case 0020,0011 :/ 序列return IS ;break;case 0020,0012 :/ 成像編號return IS ;break;case 0020,0013 :/ 影像編號return IS ;break;case 0028,0002 : / 像素采樣 1 為灰度 3 為彩色return US;break;case ”0028,0004”: 圖像模式 MONOCHROME2度return CS;break;case ”0028,0010” : /row 高re

15、turn ”US”;break;case ”0028,0011” : /col 寬78return US;79break;80case 0028,0100 :/ 單個采樣數據長度81return US;82break;83case 0028,0101 :/ 實際長度84return US;85break;86case 0028,0102 :/ 采樣最大值87return US;88break;89case 0028,1050 :/ 窗位90return DS;91break;92case 0028,1051 :/ 窗寬93return DS;94break;95case 0028,1052 :

16、96return DS;97break;98case 0028,1053 :99return DS;100break;101case 0040,0008 : / 文件夾標簽102return SQ;103break;104case 0040,0260 : / 文件夾標簽105return SQ;106break;107case 0040,0275 : / 文件夾標簽108return SQ;109break;110case 7fe0,0010 :/ 像素數據開始處111return OW;112break;113default :114return UN;115break;116 117 最關

17、鍵的兩個tag :0002,0010普通tag的讀取方式little字節序還是big字節序 隱式VR還是顯示VR由它的值決定1 switch (VFStr)2 3 case 12840.10000”: 顯示 little4 isLitteEndian =true ;5 isExplicitVR =true ;6 break;7 case 12840.10000”: 顯示 big8 isLitteEndian =false ;9 isExplicitVR =true ;10 break;11 case 1.2.840.10008.1.20: 隱式 little12 i

18、sLitteEndian =true;13 isExplicitVR =false;14 break;15 default :16 break;17 7fe0,0010像素數據開始處整理根據以上的分析相信解析一個dicom格式文件的過程已經很清晰了吧第一步:跳過128字節導言部分,并讀取DICM4個字符 以確認是dicom格式文第二步:讀取第一部分也就是非常重要的文件元dataElement。讀取所有 0002開頭的tag并根據0002, 0010的值確定傳輸語法。文件元tag部分的數 據元素都是以顯示VR的方式表示的讀取它的值也就是字節碼處理別告訴我 說你不會字節碼處理哈。傳輸語法說得那么官

19、方,你就忽悠吧 其實就確定兩個東西而已1字節序這個基本上都是little 字節序。舉個例子吧十進制數 35280用十六 進制表示是0xff00但是存儲到文件中你用十六進制編輯器打開你看到的是這個樣子00ff這就是little字節序。平常我們用的x86PCft windows下都是little 字節序 包括AMD勺CPU別太較真 較真的話這個問題又可以寫篇 推客2確定從0002以后的dataElement的VR是顯示還是隱式。說來說去 0002, 0010的值就那么固定幾個并且只能是那么幾個這些都在那個北美放射學會 定義的dicom標準的第六章 有說明:1.2.840.10008.1.2Impl

20、icit VR Little Endian: Default Transfer Syntax for DICOMTransfer Syntax1.2.840.1000Explicit VR Little EndianTransfer Syntax1.2.840.1000Explicit VR Big EndianTransfer Syntax上面的那段代碼其實就是這個表格的實現,講到這里你會覺得多么的坑爹啊是的dicom面向對象的破概念非常煩的。第三步:讀 取普通tag直到搜尋到7fe0,0010這個最巨體的存儲圖像數據的 dataElement它一個頂別人幾十個

21、上百個。我們在前一步已經把 VR是顯示還 是隱式確定 通過前面的圖,也就是字節碼處理而已無任何壓力。顯示情況下 根據VR和Len確定數據類型 跟數據長度直接讀取就可以了。隱式情況下這 破玩藝兒有點煩,只能根據tag字典確定它是什么VR再才能讀取。關于這個 字典也在dicom標準的第六章。上面倒數第二段代碼已經把重要的字典都列了 出來。第四步:讀取灰度像素數據并調窗 以GDI的方式顯示出來。 說實話開始我還 以為dicom這種號稱醫學什么影像的專家制定出來的標準 讀取像素數據應該有 難度吧結果沒想到這么的傻瓜。直接按像素從左到右從上到下 一行行依次掃 描。兩個字節表示1個像素普通Dicom格式存

22、儲的是16位的灰度圖像,其實有 效數據只有12位,除去0所以最高值是2047。比如CT值 從-1000到+1000, 空氣的密度為-1000水的密度為0金屬的密度為+1000總共的值為2000調窗技術:即把12級灰度的數據通過調節窗寬窗位并讓他在RGB奠式下顯示出來。還技 術呢 說實話這個也是沒什么技術含量的所謂的技術,兩句代碼給你整明白。調 節窗寬窗位到底什么意思,12位的數據那么它總共有2047個等級的灰度 沒有顯示設備可以體現兩千多級的明暗度就算有我們肉眼也無法分辨更無法診斷。我們要診斷是要提取關鍵密度值的數據在醫院放射科呆久了你一定經常聽醫生講什么骨窗 肺窗 之類的詞兒,這就是指的這個

23、“窗”。比如有病人骨折 了打了鋼板我們想看金屬部分來診斷那么我們應該抓取CT值從800到1000密度的像素也就是灰度值然后把它放到RGB真式下顯示,低于800的不論值 大小都顯示黑色 高于1000的不論值大小都顯示白色。通過以上例子那么這個范圍1000-800=200這個200表示窗寬,800+ (200/2) 這個表示窗位一句話,從2047個等級的灰度里選取一個范圍放到 0255的灰度環境里顯示。怎樣把12位灰度影射到8位灰度顯示出來呢,還怎么顯示上面方法都給說明了基本上算半成品了。聯想到角度制弧度制.設要求的8位灰度值為x已知的 12位灰度值為y那么:x/255=y/2047那么x=255

24、y/2047原理不多講 等比中項 十字相乘法 這個是初中的知識哈。初中沒讀過的童鞋飄過。原理過程講完了代碼走起1 class DicomHandler2 3 string fileName = ;4 Dictionary tags = newDictionary(); /dicom 文件中的標簽5 BinaryReader dicomFile; /dicom 文件流67 / 文件元信息/ using System.Drawing;/using System.Drawing.Imaging;/using System.Drawing.Drawing2D;8 public Bitmap gdiIm

25、g; / 轉換后的 gdi 圖像9 UInt32 fileHeadLen; / 文件頭長度10 long fileHeadOffset; / 文件數據開始位置11 UInt32 pixDatalen; / 像素數據長度12 long pixDataOffset =0; / 像素數據開始位置13 bool isLitteEndian =true ; / 是否小字節序(小端在前 、大端在前)14 bool isExplicitVR = true ; / 有無 VR1516 / 像素信息17 int colors;/顏色數RGB為3黑白為118 public intwindowWith = 2048

26、, windowCenter =2048 / 2;/窗寬窗位19 int rows, cols;20 public void readAndShow(TextBox textBox1)21 22 if (fileName = string .Empty)23 return ;24 dicomFile =newBinaryReader(File.OpenRead(fileName);2526/ 跳過 128 字節導言部分27 dicomFile.BaseStream.Seek(128, SeekOrigin.Begin);2829if ( new string (dicomFile.ReadCh

27、ars( 4) != DICM)3031 MessageBox.Show( 沒有 dicom 標識頭,文件格式錯誤);32 return ;33 343536 tagRead();3738 IDictionaryEnumerator enor = tags.GetEnumerator();39 while (enor.MoveNext()40 41 if (enor.Key.ToString().Length 9)42 43 textBox1.Text += enor.Key.ToString() +rn ;44 textBox1.Text +=enor.Value.ToString().Re

28、place( 0 , );45 46 else47 textBox1.Text += enor.Key.ToString() +enor.Value.ToString().Replace( 0 , ) + rn ;48 49 dicomFile.Close();50 51 public DicomHandler( string _filename)52 53 fileName = _filename;54 5556 public void saveAs( string filename)57 58 switch(filename.Substring(filename.LastIndexOf(

29、. )59 60 case .jpg :61 gdiImg.Save(filename,System.Drawing.Imaging.ImageFormat.Jpeg);62 break;63 case .bmp :64 gdiImg.Save(filename,System.Drawing.Imaging.ImageFormat.Bmp);65 break;66 case .png :67gdiImg.Save(filename,System.Drawing.Imaging.ImageFormat.Png);68break;69default :70break;717273public bo

30、ol getImg( ) / 獲取圖像 在圖像數據偏移量已經確定的情況下74 75 if (fileName = string .Empty)76 return false ;7778 int dataLen, validLen;/ 數據長度 有效位79 int imgNum;/ 幀數818283848586878880rows =int .Parse(tags 0028,0010 .Substring(5);cols =int .Parse(tags 0028,0011 .Substring(5);colors =int .Parse(tags 0028,0002 .Substring( 5

31、);dataLen =int .Parse(tags 0028,0100 .Substring( 5);validLen =int .Parse(tags 0028,0101 .Substring( 5);gdiImg =newBitmap(cols, rows);8990BinaryReader dicomFile =newBinaryReader(File.OpenRead(fileName);91 92 dicomFile.BaseStream.Seek(pixDataOffset,SeekOrigin.Begin);9394 long reads = 0;95 for ( int i

32、=0; i gdiImg.Height; i+)96 97 for ( int j = 0; j = pixDatalen)100break;101byte pixData = dicomFile.ReadBytes(dataLen/ 8 * colors);102reads += pixData.Length;103104 Color c = Color.Empty;105 if (colors =1)109double gray =BitConverter.ToUInt16(pixData,0);110/ 調窗代碼,就這么幾句而已111/1 先確定窗口范圍 2 映射到 8 位灰度112in

33、t grayStart = (windowCenter -windowWith /2);113int grayEnd = (windowCenter + windowWith/ 2);114115if (gray grayEnd)118grayGDI =255;119else120121grayGDI =(int )(gray - grayStart) *int grayGDI;106107108255 / windowWith);122123124if (grayGDI 255)125grayGDI =255;126else if (grayGDI 0)127grayGDI =0;128c

34、= Color.FromArgb(grayGDI, grayGDI,grayGDI);129130else if (colors =3)131132c = Color.FromArgb(pixData0,pixData 1, pixData 2);133134135gdiImg.SetPixel(j, i, c);136137138139dicomFile.Close();140return true ;141142void tagRead() / 不斷讀取所有 tag 及其值 直到碰到圖像數據(7fe0 0010 )143144bool enDir = falsenewStringBuild

35、er(); / 該死145int leve = 0;146 StringBuilder folderData =的文件夾標簽147string folderTag =148while (dicomFile.BaseStream.Position +6 dicomFile.BaseStream.Length)149150/ 讀取 tag151string tag =dicomFile.ReadUInt16().ToString( x4 ) + , +152dicomFile.ReadUInt16().ToString(x4 );153154string VR = string .Empty;15

36、5UInt32 Len =0;156/ 讀取 VR跟 Len157/ Xt OB OW SC做特殊處理 先置兩個字節0然后4字節值長度158/這些都是在讀取VR一步被阻斷的情況159況160161162163VR =VR = OF | VR =164if (tag.Substring( 0, 4) = 0002 )/ 文件頭 特殊情new string (dicomFile.ReadChars( 2);if (VR = OB | VR =OW| VR = SQ |UT | VR =UN)165dicomFile.BaseStream.Seek(2,SeekOrigin.Current);166

37、Len = dicomFile.ReadUInt32();167168else169Len = dicomFile.ReadUInt16();170171else if (tag =fffe,e000| tag = fffe,e00d| tag = fffe,e0dd )/ 文件夾標簽172173VR =* ;174Len = dicomFile.ReadUInt32();175176else if (isExplicitVR = true ) 有無 VR的情況177178179VR =new string (dicomFile.ReadChars( 2);180if (VR = OB | V

38、R =OW| VR = SQ |VR = OF | VR = UT | VR =UN)181182dicomFile.BaseStream.Seek(2,SeekOrigin.Current);183Len = dicomFile.ReadUInt32();184185else186Len = dicomFile.ReadUInt16();187188else if (isExplicitVR = false )189190VR = getVR(tag);/ 無顯示 VR時根據 tag 一個一個去找 真煩啊。191Len = dicomFile.ReadUInt32();192193/ 判斷是

39、否應該讀取VF 以何種方式讀取VF194/ 這些都是在讀取VF一步被阻斷的情況195byte VF = 0x00 ;196197if (tag =7fe0,0010 )/ 圖像數據開始了198199pixDatalen = Len;200pixDataOffset =dicomFile.BaseStream.Position;201dicomFile.BaseStream.Seek(Len,SeekOrigin.Current);202VR =UL ;203VF = BitConverter.GetBytes(Len);204205else if (VR = SQ & Len = UInt32

40、.MaxValue)| (tag =fffe,e000& Len = UInt32.MaxValue) / 靠 遇到文件夾開始標簽了206207if (enDir = false )208209enDir =true ;210folderData.Remove(0, folderData.Length);211folderTag = tag;212213else214215216217218leve+;/VF 不賦值else if (tag = fffe,e00d & Len =UInt32.MinValue) | (tag = fffe,e0dd & Len = UInt32.MinValu

41、e) / 文件夾結束標簽219220221222223224225226227228229230231232233234235236if (enDir = true )enDir =false ;elseleve-;elseVF = dicomFile.ReadBytes(int )Len);string VFStr;VFStr = getVF(VR, VF);/針對特殊的 tag 的值的處理237238239240241/ 特別針對文件頭信息處理if (tag =0002,0000 )fileHeadLen = Len;fileHeadOffset =dicomFile.BaseStream.Position;242 243 的數據讀取244 245 246 247 little 248249 250 251else if (tag =0002,0010 )/ 傳輸語法 關系到后面switch (VFStr)case 1.2.840.10000: / 顯示isLitteEndian =true ;isExplicitVR =true ;break;case 1.2.8

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論