【转】Dicom格式文件解析器!!!!!!!
轉(zhuǎn)自:https://www.cnblogs.com/assassinx/archive/2013/01/09/dicomViewer.html
Dicom全稱是醫(yī)學數(shù)字圖像與通訊,這里講的暫不涉及通訊的問題 只講*.dcm 也就是diocm格式文件的讀取,讀取本身是沒啥難度的 無非就是字節(jié)碼數(shù)據(jù)流處理。只不過確實比較繁瑣。
好了 正題
分析
整體結(jié)構(gòu)先是128字節(jié)所謂的導言部分,說俗點就是沒啥意義的破數(shù)據(jù) 跳過就是了,然后是dataElement依次排列的方式 就是一個dataElement接一個dataElement的方式排到文件結(jié)尾 通俗的講dataElement就是指tag 就是破Dicom標準里定義的數(shù)據(jù)字典。tag是4個字節(jié)表示的 前兩字節(jié)是組號后兩字節(jié)是偏移號 比如0008,0018。所有dataElement在文件中都是按tag排序的 比如0002,0001? 0002,0002? 0003,0011
文件整體結(jié)構(gòu)如下:
論文里的這圖總結(jié)的很好,因此就直接貼上來了。
單個dataElement的結(jié)構(gòu)如下:
顯式VR:VR為OB(Other Byte string) OW(Other Word?string)?OF(Other Float?string)?UT(Unlimited Text) SQ(SeQuence of item) UN(UNkown)的元素結(jié)構(gòu)
| 組號 | 元素號 | VR | 預留 | 值長度 | 數(shù)據(jù)元素值 |
| 2 | 2 | 2 | 2(0x00,0x00) | 4 | 由數(shù)據(jù)長度決定 |
顯式VR:VR為普通類型時元素結(jié)構(gòu)(少了預留那一行)
| 組號 | 元素號 | VR | 值長度 | 數(shù)據(jù)元素值 |
| 2 | 2 | 2 | 4 | 由數(shù)據(jù)長度決定 |
隱式VR時的元素結(jié)構(gòu)
| 組號 | 元素號 | 值長度 | 數(shù)據(jù)元素值 |
| 2 | 2 | 4 | 由數(shù)據(jù)長度決定 |
要問VR是啥東東 ,它是值表示法。那啥叫值表示法啊?俺也不懂。不過 int string short ushort 懂?VR就是這個意思,Dicom標準真坑爹 非要整個怪怪的概念。
VR總共27個 跟c#值類型對應關(guān)系我都寫好了:
?
1 string getVF(string VR, byte[] VF)2 {3 string VFStr = string.Empty;4 switch (VR)5 {6 case "SS":7 VFStr = BitConverter.ToInt16(VF, 0).ToString();8 break;9 case "US": 10 VFStr = BitConverter.ToUInt16(VF, 0).ToString(); 11 12 break; 13 case "SL": 14 VFStr = BitConverter.ToInt32(VF, 0).ToString(); 15 16 break; 17 case "UL": 18 VFStr = BitConverter.ToUInt32(VF, 0).ToString(); 19 20 break; 21 case "AT": 22 VFStr = BitConverter.ToUInt16(VF, 0).ToString(); 23 24 break; 25 case "FL": 26 VFStr = BitConverter.ToSingle(VF, 0).ToString(); 27 28 break; 29 case "FD": 30 VFStr = BitConverter.ToDouble(VF, 0).ToString(); 31 32 break; 33 case "OB": 34 VFStr = BitConverter.ToString(VF, 0); 35 break; 36 case "OW": 37 VFStr = BitConverter.ToString(VF, 0); 38 break; 39 case "SQ": 40 VFStr = BitConverter.ToString(VF, 0); 41 break; 42 case "OF": 43 VFStr = BitConverter.ToString(VF, 0); 44 break; 45 case "UT": 46 VFStr = BitConverter.ToString(VF, 0); 47 break; 48 case "UN": 49 VFStr = Encoding.Default.GetString(VF); 50 break; 51 default: 52 VFStr = Encoding.Default.GetString(VF); 53 break; 54 } 55 return VFStr; 56 }找個dicom文件在十六進制編輯器下瞧瞧 給你整明白:
所有dataElement從前到后按tag又可簡單分段:
| 文件元dataElement | 不受傳輸語法影響 總是以顯式VR方式表示? 因為它里面就定義了傳輸語法 |
| 普通dataElement | 受傳輸語法影響 顯示VR表示方式還是隱式VR表示方式 |
| 像素數(shù)據(jù)dataElement | 最重要也是最大的一個數(shù)據(jù)項 其實存儲的就是圖像數(shù)據(jù) |
幾個特殊的tag很重要 前面說過了tag就是dicom里定義的字典。文件元dataElement 和跟像素數(shù)據(jù)相關(guān)的dataElement 都很重要,其他的很多 如果全部照顧完的話估計得寫上千行switch語句吧,所以沒有必要。一般我們只抓取關(guān)鍵的tag。并且在隱式語法下要確定VR,當然這些都是根據(jù)字典來確定
關(guān)鍵的tag如下:
?
1 string getVR(string tag)2 {3 switch (tag)4 {5 case "0002,0000"://文件元信息長度6 return "UL";7 break;8 case "0002,0010"://傳輸語法9 return "UI";10 break;11 case "0002,0013"://文件生成程序的標題12 return "SH";13 break;14 case "0008,0005"://文本編碼15 return "CS";16 break;17 case "0008,0008":18 return "CS";19 break;20 case "0008,1032"://成像時間21 return "SQ";22 break;23 case "0008,1111":24 return "SQ";25 break;26 case "0008,0020"://檢查日期27 return "DA";28 break;29 case "0008,0060"://成像儀器30 return "CS";31 break;32 case "0008,0070"://成像儀廠商33 return "LO";34 break;35 case "0008,0080":36 return "LO";37 break;38 case "0010,0010"://病人姓名39 return "PN";40 break;41 case "0010,0020"://病人id42 return "LO";43 break;44 case "0010,0030"://病人生日45 return "DA";46 break;47 case "0018,0060"://電壓48 return "DS";49 break;50 case "0018,1030"://協(xié)議名51 return "LO";52 break;53 case "0018,1151":54 return "IS";55 break;56 case "0020,0010"://檢查ID57 return "SH";58 break;59 case "0020,0011"://序列60 return "IS";61 break;62 case "0020,0012"://成像編號63 return "IS";64 break;65 case "0020,0013"://影像編號66 return "IS";67 break;68 case "0028,0002"://像素采樣1為灰度3為彩色69 return "US";70 break;71 case "0028,0004"://圖像模式MONOCHROME2為灰度72 return "CS";73 break;74 case "0028,0010"://row高75 return "US";76 break;77 case "0028,0011"://col寬78 return "US";79 break;80 case "0028,0100"://單個采樣數(shù)據(jù)長度81 return "US";82 break;83 case "0028,0101"://實際長度84 return "US";85 break;86 case "0028,0102"://采樣最大值87 return "US";88 break;89 case "0028,1050"://窗位90 return "DS";91 break;92 case "0028,1051"://窗寬93 return "DS";94 break;95 case "0028,1052":96 return "DS";97 break;98 case "0028,1053":99 return "DS"; 100 break; 101 case "0040,0008"://文件夾標簽 102 return "SQ"; 103 break; 104 case "0040,0260"://文件夾標簽 105 return "SQ"; 106 break; 107 case "0040,0275"://文件夾標簽 108 return "SQ"; 109 break; 110 case "7fe0,0010"://像素數(shù)據(jù)開始處 111 return "OW"; 112 break; 113 default: 114 return "UN"; 115 break; 116 } 117 }最關(guān)鍵的兩個tag:
0002,0010?普通tag的讀取方式 little字節(jié)序還是big字節(jié)序? 隱式VR還是顯示VR。由它的值決定
?
1 switch (VFStr)2 {3 case "1.2.840.10008.1.2.1\0"://顯示little4 isLitteEndian = true;5 isExplicitVR = true;6 break;7 case "1.2.840.10008.1.2.2\0"://顯示big8 isLitteEndian = false;9 isExplicitVR = true; 10 break; 11 case "1.2.840.10008.1.2\0"://隱式little 12 isLitteEndian = true; 13 isExplicitVR = false; 14 break; 15 default: 16 break; 17 }7fe0,0010? 像素數(shù)據(jù)開始處
整理
根據(jù)以上的分析相信解析一個dicom格式文件的過程已經(jīng)很清晰了吧
第一步:跳過128字節(jié)導言部分,并讀取"DICM"4個字符 以確認是dicom格式文件
第二步:讀取第一部分 也就是非常重要的文件元dataElement 。讀取所有0002開頭的tag 并根據(jù)0002,0010的值確定傳輸語法。文件元tag部分的數(shù)據(jù)元素都是以顯式VR的方式表示的 讀取它的值 也就是字節(jié)碼處理 別告訴我說你不會字節(jié)碼處理哈。傳輸語法 說得那么官方,你就忽悠吧 其實就確定兩個東西而已
1字節(jié)序 這個基本上都是little字節(jié)序。舉個例子吧十進制數(shù) 35280 用十六進制表示是0xff00?但是存儲到文件中你用十六進制編輯器打開你看到的是這個樣子00ff 這就是little字節(jié)序。平常我們用的x86PC在windows下都是little字節(jié)序 包括AMD的CPU。別太較真 較真的話這個問題又可以寫篇博客了。
2確定從0002以后的dataElement的VR是顯示還是隱式。說來說去0002,0010的值就 那么固定幾個 并且只能是那么幾個 這些都在那個北美放射學會定義的dicom標準的第六章 有說明 :
| 1.2.840.10008.1.2 | Implicit VR Little Endian: Default Transfer Syntax for DICOM | Transfer Syntax |
| 1.2.840.10008.1.2.1 | Explicit VR Little Endian | Transfer Syntax |
| 1.2.840.10008.1.2.2 | Explicit VR Big Endian | Transfer Syntax |
上面的那段代碼其實就是這個表格的實現(xiàn),講到這里你會覺得多么的坑爹啊 是的dicom面向?qū)ο蟮钠聘拍罘浅┑摹?br />第三步:讀取普通tag 直到搜尋到7fe0,0010 這個最巨體的存儲圖像數(shù)據(jù)的 dataElement 它一個頂別人幾十個 上百個。我們在前一步已經(jīng)把VR是顯示還是隱式確定 通過前面的圖 ,也就是字節(jié)碼處理而已無任何壓力。顯示情況下根據(jù)VR 和Len?確定數(shù)據(jù)類型?跟數(shù)據(jù)長度直接讀取就可以了。隱式情況下這破玩藝兒有點煩,只能根據(jù)tag 字典確定它是什么VR再才能讀取。關(guān)于這個字典也在dicom標準的第六章。上面倒數(shù)第二段代碼已經(jīng)把重要的字典都列了出來。
第四步:讀取灰度像素數(shù)據(jù)并調(diào)窗 以GDI的方式顯示出來。 說實話開始我還以為dicom這種號稱醫(yī)學什么影像的專家制定出來的標準 讀取像素數(shù)據(jù)應該有難度吧 結(jié)果沒想到這么的傻瓜。直接按像素從左到右從上到下 一行行依次掃描。兩個字節(jié)表示1個像素普通Dicom格式存儲的是16位的灰度圖像,其實有效數(shù)據(jù)只有12位,除去0 所以最高值是2047。比如CT值 從-1000到+1000,空氣的密度為-1000 水的密度為0 金屬的密度為+1000 總共的值為2000
調(diào)窗技術(shù):
即把12級灰度的數(shù)據(jù) 通過調(diào)節(jié)窗寬窗位并讓他在RGB模式下顯示出來。還技術(shù)呢 說實話這個也是沒什么技術(shù)含量的所謂的技術(shù),兩句代碼給你整明白。
調(diào)節(jié)窗寬窗位到底什么意思,12位的數(shù)據(jù)那么它總共有2047個等級的灰度 沒有顯示設備可以體現(xiàn)兩千多級的明暗度 就算有我們?nèi)庋垡矡o法分辨更無法診斷。我們要診斷是要提取關(guān)鍵密度值的數(shù)據(jù) 在醫(yī)院放射科呆久了你一定經(jīng)常聽醫(yī)生講什么骨窗 肺窗 之類的詞兒,這就是指的這個“窗”。比如有病人骨折了打了鋼板我們想看金屬部分來診斷 那么我們應該抓取CT值從800到1000 密度的像素 也就是灰度值 然后把它放到RGB模式下顯示,低于800的不論值大小都顯示黑色 高于1000的不論值大小都顯示白色。
通過以上例子那么這個范圍1000-800=200 這個200表示窗寬,800+(200/2)這個表示窗位
一句話,從2047個等級的灰度里選取一個范圍放到0~255的灰度環(huán)境里顯示。
怎樣把12位灰度影射到8位灰度顯示出來呢,還怎么顯示 上面方法都給說明了基本上算半成品了。聯(lián)想到角度制弧度制,設要求的8位灰度值為x 已知的12位灰度值為y那么:x/255=y/2047 那么x=255y/2047 原理不多講 等比中項十字相乘法 這個是初中的知識哈。初中沒讀過的童鞋飄過。。。
原理過程講完了
代碼走起
1 class DicomHandler2 {3 string fileName = "";4 Dictionary<string, string> tags = new Dictionary<string, string>();//dicom文件中的標簽5 BinaryReader dicomFile;//dicom文件流6 7 //文件元信息8 public Bitmap gdiImg;//轉(zhuǎn)換后的gdi圖像9 UInt32 fileHeadLen;//文件頭長度10 long fileHeadOffset;//文件數(shù)據(jù)開始位置11 UInt32 pixDatalen;//像素數(shù)據(jù)長度12 long pixDataOffset = 0;//像素數(shù)據(jù)開始位置13 bool isLitteEndian = true;//是否小字節(jié)序(小端在前 、大端在前)14 bool isExplicitVR = true;//有無VR15 16 //像素信息17 int colors;//顏色數(shù) RGB為3 黑白為118 public int windowWith = 2048, windowCenter = 2048 / 2;//窗寬窗位19 int rows, cols;20 public void readAndShow(TextBox textBox1)21 {22 if (fileName == string.Empty)23 return;24 dicomFile = new BinaryReader(File.OpenRead(fileName));25 26 //跳過128字節(jié)導言部分27 dicomFile.BaseStream.Seek(128, SeekOrigin.Begin);28 29 if (new string(dicomFile.ReadChars(4)) != "DICM")30 {31 MessageBox.Show("沒有dicom標識頭,文件格式錯誤");32 return;33 }34 35 36 tagRead();37 38 IDictionaryEnumerator enor = tags.GetEnumerator();39 while (enor.MoveNext())40 {41 if (enor.Key.ToString().Length > 9)42 {43 textBox1.Text += enor.Key.ToString() + "\r\n";44 textBox1.Text += enor.Value.ToString().Replace('\0', ' ');45 }46 else47 textBox1.Text += enor.Key.ToString() + enor.Value.ToString().Replace('\0', ' ') + "\r\n";48 }49 dicomFile.Close();50 }51 public DicomHandler(string _filename)52 {53 fileName = _filename;54 }55 56 public void saveAs(string filename)57 {58 switch (filename.Substring(filename.LastIndexOf('.')))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":67 gdiImg.Save(filename, System.Drawing.Imaging.ImageFormat.Png);68 break;69 default:70 break;71 }72 }73 public bool getImg( )//獲取圖像 在圖像數(shù)據(jù)偏移量已經(jīng)確定的情況下74 {75 if (fileName == string.Empty)76 return false;77 78 int dataLen, validLen;//數(shù)據(jù)長度 有效位79 int imgNum;//幀數(shù)80 81 rows = int.Parse(tags["0028,0010"].Substring(5));82 cols = int.Parse(tags["0028,0011"].Substring(5));83 84 colors = int.Parse(tags["0028,0002"].Substring(5));85 dataLen = int.Parse(tags["0028,0100"].Substring(5));86 validLen = int.Parse(tags["0028,0101"].Substring(5));87 88 gdiImg = new Bitmap(cols, rows);89 90 BinaryReader dicomFile = new BinaryReader(File.OpenRead(fileName));91 92 dicomFile.BaseStream.Seek(pixDataOffset, SeekOrigin.Begin);93 94 long reads = 0;95 for (int i = 0; i < gdiImg.Height; i++)96 {97 for (int j = 0; j < gdiImg.Width; j++)98 {99 if (reads >= pixDatalen) 100 break; 101 byte[] pixData = dicomFile.ReadBytes(dataLen / 8 * colors); 102 reads += pixData.Length; 103 104 Color c = Color.Empty; 105 if (colors == 1) 106 { 107 int grayGDI; 108 109 double gray = BitConverter.ToUInt16(pixData, 0); 110 //調(diào)窗代碼,就這么幾句而已 111 //1先確定窗口范圍 2映射到8位灰度 112 int grayStart = (windowCenter - windowWith / 2); 113 int grayEnd = (windowCenter + windowWith / 2); 114 115 if (gray < grayStart) 116 grayGDI = 0; 117 else if (gray > grayEnd) 118 grayGDI = 255; 119 else 120 { 121 grayGDI = (int)((gray - grayStart) * 255 / windowWith); 122 } 123 124 if (grayGDI > 255) 125 grayGDI = 255; 126 else if (grayGDI < 0) 127 grayGDI = 0; 128 c = Color.FromArgb(grayGDI, grayGDI, grayGDI); 129 } 130 else if (colors == 3) 131 { 132 c = Color.FromArgb(pixData[0], pixData[1], pixData[2]); 133 } 134 135 gdiImg.SetPixel(j, i, c); 136 } 137 } 138 139 dicomFile.Close(); 140 return true; 141 } 142 void tagRead()//不斷讀取所有tag 及其值 直到碰到圖像數(shù)據(jù) (7fe0 0010 ) 143 { 144 bool enDir = false; 145 int leve = 0; 146 StringBuilder folderData = new StringBuilder();//該死的文件夾標簽 147 string folderTag = ""; 148 while (dicomFile.BaseStream.Position + 6 < dicomFile.BaseStream.Length) 149 { 150 //讀取tag 151 string tag = dicomFile.ReadUInt16().ToString("x4") + "," + 152 dicomFile.ReadUInt16().ToString("x4"); 153 154 string VR = string.Empty; 155 UInt32 Len = 0; 156 //讀取VR跟Len 157 //對OB OW SQ 要做特殊處理 先置兩個字節(jié)0 然后4字節(jié)值長度 158 //------------------------------------------------------這些都是在讀取VR一步被阻斷的情況 159 if (tag.Substring(0, 4) == "0002")//文件頭 特殊情況 160 { 161 VR = new string(dicomFile.ReadChars(2)); 162 163 if (VR == "OB" || VR == "OW" || VR == "SQ" || VR == "OF" || VR == "UT" || VR == "UN") 164 { 165 dicomFile.BaseStream.Seek(2, SeekOrigin.Current); 166 Len = dicomFile.ReadUInt32(); 167 } 168 else 169 Len = dicomFile.ReadUInt16(); 170 } 171 else if (tag == "fffe,e000" || tag == "fffe,e00d" || tag == "fffe,e0dd")//文件夾標簽 172 { 173 VR = "**"; 174 Len = dicomFile.ReadUInt32(); 175 } 176 else if (isExplicitVR == true)//有無VR的情況 177 { 178 VR = new string(dicomFile.ReadChars(2)); 179 180 if (VR == "OB" || VR == "OW" || VR == "SQ" || VR == "OF" || VR == "UT" || VR == "UN") 181 { 182 dicomFile.BaseStream.Seek(2, SeekOrigin.Current); 183 Len = dicomFile.ReadUInt32(); 184 } 185 else 186 Len = dicomFile.ReadUInt16(); 187 } 188 else if (isExplicitVR == false) 189 { 190 VR = getVR(tag);//無顯示VR時根據(jù)tag一個一個去找 真煩啊。 191 Len = dicomFile.ReadUInt32(); 192 } 193 //判斷是否應該讀取VF 以何種方式讀取VF 194 //-------------------------------------------------------這些都是在讀取VF一步被阻斷的情況 195 byte[] VF = { 0x00 }; 196 197 if (tag == "7fe0,0010")//圖像數(shù)據(jù)開始了 198 { 199 pixDatalen = Len; 200 pixDataOffset = dicomFile.BaseStream.Position; 201 dicomFile.BaseStream.Seek(Len, SeekOrigin.Current); 202 VR = "UL"; 203 VF = BitConverter.GetBytes(Len); 204 } 205 else if ((VR == "SQ" && Len == UInt32.MaxValue) || (tag == "fffe,e000" && Len == UInt32.MaxValue))//靠 遇到文件夾開始標簽了 206 { 207 if (enDir == false) 208 { 209 enDir = true; 210 folderData.Remove(0, folderData.Length); 211 folderTag = tag; 212 } 213 else 214 { 215 leve++;//VF不賦值 216 } 217 } 218 else if ((tag == "fffe,e00d" && Len == UInt32.MinValue) || (tag == "fffe,e0dd" && Len == UInt32.MinValue))//文件夾結(jié)束標簽 219 { 220 if (enDir == true) 221 { 222 enDir = false; 223 } 224 else 225 { 226 leve--; 227 } 228 } 229 else 230 VF = dicomFile.ReadBytes((int)Len); 231 232 string VFStr; 233 234 VFStr = getVF(VR, VF); 235 236 //----------------------------------------------------------------針對特殊的tag的值的處理 237 //特別針對文件頭信息處理 238 if (tag == "0002,0000") 239 { 240 fileHeadLen = Len; 241 fileHeadOffset = dicomFile.BaseStream.Position; 242 } 243 else if (tag == "0002,0010")//傳輸語法 關(guān)系到后面的數(shù)據(jù)讀取 244 { 245 switch (VFStr) 246 { 247 case "1.2.840.10008.1.2.1\0"://顯示little 248 isLitteEndian = true; 249 isExplicitVR = true; 250 break; 251 case "1.2.840.10008.1.2.2\0"://顯示big 252 isLitteEndian = false; 253 isExplicitVR = true; 254 break; 255 case "1.2.840.10008.1.2\0"://隱式little 256 isLitteEndian = true; 257 isExplicitVR = false; 258 break; 259 default: 260 break; 261 } 262 } 263 for (int i = 1; i <= leve; i++) 264 tag = "--" + tag; 265 //------------------------------------數(shù)據(jù)搜集代碼 266 if ((VR == "SQ" && Len == UInt32.MaxValue) || (tag == "fffe,e000" && Len == UInt32.MaxValue) || leve > 0)//文件夾標簽代碼 267 { 268 folderData.AppendLine(tag + "(" + VR + "):" + VFStr); 269 } 270 else if (((tag == "fffe,e00d" && Len == UInt32.MinValue) || (tag == "fffe,e0dd" && Len == UInt32.MinValue)) && leve == 0)//文件夾結(jié)束標簽 271 { 272 folderData.AppendLine(tag + "(" + VR + "):" + VFStr); 273 tags.Add(folderTag + "SQ", folderData.ToString()); 274 } 275 else 276 tags.Add(tag, "(" + VR + "):" + VFStr); 277 } 278 } 279 }好了收工。
測試下成果
?
1 if (openFileDialog1.ShowDialog() != DialogResult.OK)2 return;3 4 string fileName = openFileDialog1.FileName;5 6 handler = new DicomHandler(fileName);7 8 handler.readAndShow(textBox1);9 10 this.Text = "DicomViewer-" + openFileDialog1.FileName; 11 12 13 backgroundWorker1.RunWorkerAsync();這里處理gdi位圖的時候直接用的setPix 處理速度比較慢所以用了backgroundWorker,實際應用中請使用內(nèi)存緩沖跟指針的方式,否則效率低了是得不到客戶的認可的哦,gdi位圖操作可使用lockBits加指針的方式 ,12位的灰度像素數(shù)據(jù)可以第一次讀取后緩存到內(nèi)存中 以方便后面調(diào)窗的快速讀取優(yōu)化這點代碼也不難哈 對指針什么的熟點就行了,前幾章都有。
這是ezDicom 經(jīng)過公認測試的軟件 我們來跟他對比一下,打開調(diào)窗測試,我們注意到兩個東西 在沒有窗寬窗位時 默認窗寬是2047+1即2048? 窗位是2048/2即1024。直觀的感受是調(diào)窗寬像在調(diào)圖像對比度 ,調(diào)窗位像在調(diào)圖像亮度。窗寬為255的時候圖像是最瑞麗的,這是因為255其實就是8位圖像的默認窗寬。注意窗位那里有小小區(qū)別,ez窗位顯示的是根據(jù)1024那里為0開始偏移 而我的程序是根據(jù)窗寬中間值沒有偏移,沒有偏移的情況稍微符合邏輯點吧。但是可以看到原理是一樣的 結(jié)果是一樣的。
源碼下載測試dcm文件:?猛擊此處
最近也沒有以前寫的文章那么歡樂了 不知道為什么,長大了 沒有以前開心了 呵呵 。
筒子們2013年新年快樂。
這篇文章發(fā)布很久了 感謝朋友們的關(guān)注,分析講解跟代碼有點混亂 感覺有點敷衍了事純粹賺人氣的感覺 對不住大家了。另外本文的調(diào)窗代碼是有問題的 升級版本請看《醫(yī)學影像調(diào)窗技術(shù)》一文中的改進代碼。
總結(jié)
以上是生活随笔為你收集整理的【转】Dicom格式文件解析器!!!!!!!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转】基于DCMTK的DICOM相关程序
- 下一篇: 【转】DICOM的常用Tag分类和说明!