醫學影像調窗技術

 

在年初的時候作過一個dicom格式文件解析,當時只是提了下。看着跟別人的顯示出來也差很少 實際上是我想太簡單了。整理了下思路 這裏提供正確的調窗代碼。 醫學影像 說得挺高科技的 其實在這個過程當中自己沒太複雜的圖像處理技術。窗值處理就算是比較「高深」的了 也就是調窗。
網上都是啥基於 DCMTK的DICOM醫學圖像顯示及其調窗方法研究 說得文縐縐的 沒啥鳥用 ,dicom沒你想象的那麼複雜哈 咱這個全是自主代碼 頂多看了點C++的源碼 而後改爲c#版本的 其實都同樣的。html

這中間有幾個 步驟,
1 字節序轉換
2 保留有效位,使用&進行位運算截取有效位
3 根據有無符號進行值轉換
4 針對CT影像的窗值偏移處理
5 窗值映射 也就是映射到256級灰度(參考上一篇c#

而我原來的代碼啥都沒作 直接對兩個字節的數據進行toUint16 而後就進行窗值映射,還有就是也沒有進行預設窗值讀取。那麼這樣作的後果是什麼呢 。
咱們先加上預設窗值讀取,首先咱們加上幾個變量 進行影像顯示的幾個關鍵數據 圖像的長 寬 默認窗值 顏色採樣數 1爲灰度3爲彩色 數據存儲位數 有效位數 最高位數,具體查看dicom標準。
變量聲明 默認窗值讀取代碼 (預設窗寬tag 0028 1051 預設窗位tag 0028 1050):sass

 1 if (fileName == string.Empty)
 2     return false;
 3 
 4 int dataLen, validLen, hibit;//數據長度 有效位
 5 int imgNum;//幀數
 6 
 7 rows = int.Parse(tags["0028,0010"].Substring(5));
 8 cols = int.Parse(tags["0028,0011"].Substring(5));
 9 
10 colors = int.Parse(tags["0028,0002"].Substring(5));
11 dataLen = int.Parse(tags["0028,0100"].Substring(5));//bits allocated
12 validLen = int.Parse(tags["0028,0101"].Substring(5));
13 bool signed = int.Parse(tags["0028,0103"].Substring(5)) == 0 ? false : true;
14 hibit = int.Parse(tags["0028,0102"].Substring(5));
15 float rescaleSlop = 1, rescaleinter = 0;
16 if (tags.ContainsKey("0028,1052") && tags.ContainsKey("0028,1053"))
17 {
18     rescaleSlop = float.Parse(tags["0028,1053"].Substring(5));
19     rescaleinter = float.Parse(tags["0028,1052"].Substring(5));
20 }
21 //讀取預設窗寬窗位
22 //預設窗值讀取代碼......
23 #region//讀取預設窗寬窗位
24 if (windowWith == 0 && windowCenter == 0)
25 {
26     Regex r = new Regex(@"([0-9]+)+");
27     if (tags.ContainsKey("0028,1051"))
28     {
29         Match m = r.Match(tags["0028,1051"].Substring(5));
30 
31         if (m.Success)
32             windowWith = int.Parse(m.Value);
33         else
34             windowWith = 1 << validLen;
35     }
36     else
37     {
38         windowWith = 1 << validLen;
39     }
40 
41     if (tags.ContainsKey("0028,1050"))
42     {
43         Match m = r.Match(tags["0028,1050"].Substring(5));
44         if (m.Success)
45             windowCenter = int.Parse(m.Value);//窗位
46         else
47             windowCenter = windowWith / 2;
48     }
49     else
50     {
51         windowCenter = windowWith / 2;
52     }
53 }
54 
55 #endregion

雖然原理是正確的 但仍是會產生亂七八糟的問題 始終跟別人標準的不同 :

標準的窗值調整請參考這篇論文:醫學圖像的調窗技術及DI   基本上照着他作就OK ,只是有些地方沒講太明白。
那麼我這篇文章基本上就是他通過代碼實踐後的翻版

參考了事後那麼咱們就要照標準的流程來處理 ,字節序轉換 後截取有效位 而後根據有無符號進行值轉換
仍是原來的代碼 中間加上這幾句:測試

1 if (isLitteEndian == false)
2     Array.Reverse(pixData, 0, 2);
3 
4 if (signed == false)
5     gray = BitConverter.ToUInt16(pixData, 0);
6 else
7     gray = BitConverter.ToInt16(pixData, 0);

這麼作了後咱們發現 1.2.840.113619.2.81.290.23014.2902.1.6.20031230.260236.dcm 那幅圖像顯示對了:
優化

可是測試另外一幅 仍是不對 CT.dcm:

這幅圖像看上去是CR的圖,實際上是CT序列圖像裏的一幅 ,由於是CT影像 因此要作值偏移處理 值=值×斜率+截距 這是高中學的,稱爲HU 至於爲何要這樣我也不知道 dicom標準規定的 若是是CT圖像須要進行偏移處理則進行偏移處理 而後進行窗值映射。spa

 1 //字節序翻轉
 2 if (isLitteEndian == false)
 3     Array.Reverse(pixData, 0, 2);
 4 //取值
 5 if (signed == false)
 6     gray = BitConverter.ToUInt16(pixData, 0);
 7 else
 8     gray = BitConverter.ToInt16(pixData, 0);
 9 //特別針對CT圖像 值=值x斜率+截距
10 if ((rescaleSlop != 1.0f) || (rescaleinter != 0.0f))
11 {
12     float fValue = (float)gray * rescaleSlop + rescaleinter;
13     gray = (short)fValue;
14 }


全部的數據都讀取完成後再setPixel 這種操做效率過低了。因此咱們還得優化下代碼 先lockbits 而後一邊讀取一邊更新數據。
這是整理後的標準調窗代碼,有點多哈 慢慢看,我說得挺簡單 中間有各類複雜狀況哈 請參考上面說的步驟及論文裏的說明來:3d

  1 public unsafe Bitmap convertTo8(BinaryReader streamdata, int colors, bool littleEdition, bool signed, short nHighBit,
  2                int dataLen, float rescaleSlope, float rescaleIntercept, float windowCenter, float windowWidth, int width, int height)
  3         {
  4             Bitmap bmp = new Bitmap(width, height);
  5             Graphics gg = Graphics.FromImage(bmp);
  6             gg.Clear(Color.Green);
  7             BitmapData bmpDatas = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
  8                 System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
  9             long numPixels = width * height;
 10 
 11             if (colors == 3)//color Img
 12             {
 13                 byte* p = (byte*)bmpDatas.Scan0;
 14                 int indx = 0;
 15                 for (int i = 0; i < bmp.Height; i++)
 16                 {
 17                     for (int j = 0; j < bmp.Width; j++)
 18                     {
 19                         p[indx + 2] = streamdata.ReadByte();
 20                         p[indx + 1] = streamdata.ReadByte();
 21                         p[indx] = streamdata.ReadByte();
 22                         indx += 3;
 23                     }
 24                 }
 25             }
 26             else if (colors == 1)//grayscale Img
 27             {
 28                 byte* p = (byte*)bmpDatas.Scan0;
 29                 int nMin = ~(0xffff << (nHighBit + 1)), nMax = 0;
 30                 int indx = 0;//byteData index
 31 
 32                 for (int n = 0; n < numPixels; n++)//pixNum index
 33                 {
 34                     short nMask; nMask = (short)(0xffff << (nHighBit + 1));
 35                     short nSignBit;
 36 
 37                     byte[] pixData = null;
 38                     short pixValue = 0;
 39 
 40                     pixData = streamdata.ReadBytes(dataLen / 8 * colors);
 41                     if (nHighBit <= 15 && nHighBit > 7)
 42                     {
 43                         if (littleEdition == false)
 44                             Array.Reverse(pixData, 0, 2);
 45 
 46                         // 1. Clip the high bits.
 47                         if (signed == false)// Unsigned integer
 48                         {
 49                             pixValue = (short)((~nMask) & (BitConverter.ToInt16(pixData, 0)));
 50                         }
 51                         else
 52                         {
 53                             nSignBit = (short)(1 << nHighBit);
 54                             if (((BitConverter.ToInt16(pixData, 0)) & nSignBit) != 0)
 55                                 pixValue = (short)(BitConverter.ToInt16(pixData, 0) | nMask);
 56                             else
 57                                 pixValue = (short)((~nMask) & (BitConverter.ToInt16(pixData, 0)));
 58                         }
 59                     }
 60                     else if (nHighBit <= 7)
 61                     {
 62                         if (signed == false)// Unsigned integer
 63                         {
 64                             nMask = (short)(0xffff << (nHighBit + 1));
 65                             pixValue = (short)((~nMask) & (pixData[0]));
 66                         }
 67                         else
 68                         {
 69                             nMask = (short)(0xffff << (nHighBit + 1));
 70                             nSignBit = (short)(1 << nHighBit);
 71                             if (((pixData[0]) & nSignBit) != 0)
 72                                 pixValue = (short)((short)pixData[0] | nMask);
 73                             else
 74                                 pixValue = (short)((~nMask) & (pixData[0]));
 75                         }
 76 
 77                     }
 78 
 79                     // 2. Rescale if needed (especially for CT)
 80                     if ((rescaleSlope != 1.0f) || (rescaleIntercept != 0.0f))
 81                     {
 82                         float fValue = pixValue * rescaleSlope + rescaleIntercept;
 83                         pixValue = (short)fValue;
 84                     }
 85 
 86                     // 3. Window-level or rescale to 8-bit
 87                     if ((windowCenter != 0) || (windowWidth != 0))
 88                     {
 89                         float fSlope;
 90                         float fShift;
 91                         float fValue;
 92 
 93                         fShift = windowCenter - windowWidth / 2.0f;
 94                         fSlope = 255.0f / windowWidth;
 95 
 96                         fValue = ((pixValue) - fShift) * fSlope;
 97                         if (fValue < 0)
 98                             fValue = 0;
 99                         else if (fValue > 255)
100                             fValue = 255;
101 
102 
103                         p[indx++] = (byte)fValue;
104                         p[indx++] = (byte)fValue;
105                         p[indx++] = (byte)fValue;
106                     }
107                     else
108                     {
109                         // We will map the whole dynamic range.
110                         float fSlope;
111                         float fValue;
112 
113 
114                         int i = 0;
115                         // First compute the min and max.
116                         if (n == 0)
117                             nMin = nMax = pixValue;
118                         else
119                         {
120                             if (pixValue < nMin)
121                                 nMin = pixValue;
122 
123                             if (pixValue > nMask)
124                                 nMask = pixValue;
125                         }
126 
127                         // Calculate the scaling factor.
128                         if (nMax != nMin)
129                             fSlope = 255.0f / (nMax - nMin);
130                         else
131                             fSlope = 1.0f;
132 
133                         fValue = ((pixValue) - nMin) * fSlope;
134                         if (fValue < 0)
135                             fValue = 0;
136                         else if (fValue > 255)
137                             fValue = 255;
138 
139                         p[indx++] = (byte)fValue;
140                     }
141                 }
142             }
143 
144             bmp.UnlockBits(bmpDatas);
145             //bmp.Dispose();
146             return bmp;
147         }

完整源碼及測試數據下載 猛擊此處code

相關文章
相關標籤/搜索