CT&MR醫學圖像資料轉換RGBA圖像
簡述
本文主要講述的是把一個CT或者MR的資料轉成RGBA圖像,不解釋dicom檔案的解析。文中的代碼涉及的是2種語言python和javascript,主要是項目需要,這裡就不再做代碼轉換。
DICOM關鍵的參數
Tag | Name | 說明 |
---|---|---|
Meta資訊 | TransferSyntaxUID | 位元組排序,1.2.840.10008.1.2.2:大端排序,其他是小端排序 |
0x0020, 0x0032 | ImagePositionPatient | 指定圖像左上角的x,y和z坐标 |
0x0020, 0x0037 | ImageOrientationPatient | 指定相對于患者的第一行和第一列的方向餘弦 |
0x0020, 0x1041 | SliceLocation | 像面的相對位置,以毫米為機關, |
0x0028, 0x0002 | SamplesPerPixel | 圖像中單獨平面的數量 |
0x0028, 0x0010 | Rows | 圖像中的行數 |
0x0028, 0x0011 | Columns | 圖像中的列數 |
0x0028, 0x0030 | PixelSpacing | 患者體内每個像素中心之間的實體距離,由數字對指定-相鄰行間距(定界符)和相鄰列間距,機關為mm |
0x0028, 0x0100 | BitsAllocated | 為每個像素樣本配置設定的位數 |
0x0028, 0x1050 | WindowCenter | 視窗中心 |
0x0028, 0x1051 | WindowWidth | 視窗寬度 |
0x7fe0, 0x0010 | PixelData | 圖像資料 |
轉換資料
- 判斷TransferSyntaxUID是大端排序,還是小端排序。大端排序的話位元組是正序讀取,小端排序位元組是反序讀取。
判斷是否是小端排序 def is_little_endian(dicom): is_little_endian = True transfer_syntax = dicom.file_meta.get("TransferSyntaxUID") if transfer_syntax == '1.2.840.10008.1.2.2': is_little_endian = False return is_little_endian
- BitsAllocated表示多少位表示一個值,8位等于1個位元組,例如:32位等于4位元組
- 根據TransferSyntaxUID和BitsAllocated把PixelData從位元組數組轉換為int數組,轉換後就是該ct的灰階圖
def getPixelData(is_little_endian, BitsAllocated, PixelData): count = int(BitsAllocated / 8) newPixelData = [] for i in range(0, len(PixelData), count): value = 0 if is_little_endian: for j in range(count): value = value | (PixelData[i + j] << j * 8) else: for j in range(count): value = PixelData[i + j] | (value << j * 8) newPixelData.append(value) return newPixelData
- 根據SamplesPerPixel對圖像資料進行轉換
- 1表示是灰階圖資料,即1個數值表示圖像的顔色,圖像資料為灰階圖的資料的時候,需要根據 WindowWidth 和 WindowCenter計算灰階值的最大值grayEnd和最小值grayStart
const windowWidth = ct.WindowWidth[ct.WindowWidth.length - 1]; const windowCenter = ct.WindowCenter[ct.WindowCenter.length - 1]; const grayStart = (windowCenter - windowWidth / 2); const grayEnd = (windowCenter + windowWidth / 2);
- 3表示是RGB資料,即3個數值表示圖像的顔色
- 1表示是灰階圖資料,即1個數值表示圖像的顔色,圖像資料為灰階圖的資料的時候,需要根據 WindowWidth 和 WindowCenter計算灰階值的最大值grayEnd和最小值grayStart
- 最後把圖像資料轉換成RGBA的圖像資料
export function dicomToImageData(ct) { const Rows = ct.Rows; // 擷取:圖像的總行數,行分辨率 const Columns = ct.Columns; // 擷取:圖像的總列數,列分辨率 const PixelData = ct.PixelData; // 擷取:像素資訊 const buffers = new Uint8ClampedArray(4 * Rows * Columns); let index = 0; const windowWidth = ct.WindowWidth[ct.WindowWidth.length - 1]; const windowCenter = ct.WindowCenter[ct.WindowCenter.length - 1]; const grayStart = (windowCenter - windowWidth / 2); const grayEnd = (windowCenter + windowWidth / 2); for (let i = 0; i < Rows; i++) { for (let j = 0; j < Columns; j++) { if (ct.SamplesPerPixel === 1) { let grayGDI; let gray = PixelData[i * Columns + j]; // 某個點的灰階值 if (gray < grayStart) { grayGDI = 0; } else if (gray > grayEnd) { grayGDI = 255; } else { grayGDI = (gray - grayStart) * 255 / windowWidth; } if (grayGDI > 255) { grayGDI = 255; } else if (grayGDI < 0) { grayGDI = 0; } buffers[index++] = grayGDI; buffers[index++] = grayGDI; buffers[index++] = grayGDI; buffers[index++] = 255; } else if (ct.SamplesPerPixel === 3) { buffers[index++] = PixelData[index - 1]; buffers[index++] = PixelData[index - 1]; buffers[index++] = PixelData[index - 1]; buffers[index++] = 255; } } } return new ImageData(buffers, Columns, Rows); }
這時候就已經都轉換完成,轉換好的是一個一維Columns * Rows * 4的RGBA圖像資料。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPB9keBRlTzUkaNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3kTMyEjNxATM3EDOwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)