DICOM文件格式与编程(转)
任何圖像文件格式無非是由兩個部分組成:存參數(shù)的 header 和圖點(diǎn)數(shù)據(jù)(pixel data)。
BMP、 JPEG、TIFF 之類的格式的 header 只描述圖像的基本參數(shù):如幾行、幾列、每點(diǎn)用了幾位、有沒有壓縮、調(diào)色板等等。Header 往往是固定長度的。
?而醫(yī)療影像還要許多其它參數(shù),如病人基本資料、檢驗(yàn)基本資料、系列資料、位置資料等等。而且每種Modality 和每種 image 所需要的內(nèi)容不一樣。因此,一般的圖像格式不能使用。
?
一、DICOM 的 4 個內(nèi)容層次
1、 Patient? (病人)
2、 Study?? (檢驗(yàn))
3、 Series?? (系列)
4、 Image?? (圖像)
?盡管頭幾層的內(nèi)容在很多圖像里是相同的,但它們在每個圖像文件里都要有。
?每一層叫一個Information Entity或IE(從relational database schema 設(shè)計(jì)引用而來)。每一層又細(xì)分成Module。每個Module里面的最小單元叫做一個attribute或element。
?現(xiàn)在舉個例子:CR 圖像 (DICOM Part 3, A.2.3, Table A.2-1
1. Patient IE:
?? a. Patient Module? (參考 C.7.1.1)
2. Study IE:
?? a. Study Module??? (參考 C.7.2.1)
?? b. Patient Study Module (參考 C.7.2.2)
3. Series IE:
?? a. General Series (參考 C.7.3.1)
?? b. CR Series (參考 C.8.1..1)
?? c. General Equipment (參考 C.7.5.1)
4. Image IE:
?? a. Genrral Image (C.7.6.1)
?? b. Image Pixel (C.7.6.3)
?? c. Contrast/bolus (C.7.6.4)
?? d. CR Image (C.8.1.2)
??? ...
?? i. SOP Common (C.12.1)
?將這些 modules (tables) 里的所有 elements 都找出來就做成了一個 CR 圖像的架構(gòu)。
要 注意的是這些 module 有些是一定要的 (modatory) 有些是用戶選用的 (user)。到了每個 module 里 attribute/element 表又有分五類 Type 1, 1C, 2, 2C 和 3。 Type 1 是一定要的,2 也是一定要的但是內(nèi)容可以是空的。Type 3 則可要可不要。所以濃縮一下,一個 CR 圖像里的元素 (elements) 也不是太多。把這些表格展開后,這些 elements 組成一個 dataset。
?那么,寫到一個文件里或通過網(wǎng)路傳送又是個什么格式呢?這就要看 Part 5。一個元素 (element) 的結(jié)構(gòu)是:
1. group tag: 16-bit
2. element tag: 16-bit
3. length (or VR/length): 32-bit
4. data (bytes of length)
對應(yīng)每一個用到的 element DICOM 標(biāo)準(zhǔn) Part 6 都定義了一個 group tag 和 element tag。比如說:
patient name:? 0x0010, 0x0010
patient ID:??? 0x0010, 0x0020
...
?VR說的是element格式,比如說patinet name 的VR是PN。格式是 last_name^first_name^middle_name^prefix^surfix。那么我的英文名字就是: Wang^JB^^Dr.^
往外寫時(shí)要做幾個事情:
1. 要把所有元素按 group tage 和 element tag 理一遍 (sort)。從小排到大。
2. 如果是寫 DICOM 介質(zhì)的 DICOM file 還先寫 128 bytes preamble (一般是空白),加 "DICM", 加 group 2 Meta header。(講到 Part 10 時(shí)再細(xì)說)
3. 如果 dataset 里面含有 Sequence elements, sequence 里面每一個 Item 又是一個 dataset。
?有了這些知識,你就可以開始寫一個小小的 BMP 到 DICOM 的轉(zhuǎn)化程序。關(guān)鍵資料:
Modality (0008, 0060): SC
Photometric Interpretation (0028, 0004): RGB
SOP Class UID:? 1.2.840.10008.1.5.4.1.1.7
最簡單的辦法是寫一個 structure 然后一個 array。
typedef struct DicomElem
{
? short int group_tag,
? short int element_tag,
? char VR[4],
? int length,
? char data[128]
} DicomElem;
DicomElem CRDataSet [] =
{
{ 0x0008, 0x0005, "CS", 10, "ISO_IR 100"},
{ 0x0008, 0x0008, "CS", 16, "ORIGINAL\\PRIMARY"},
? ...
{0x0010, 0x0010, "PN", 16, "My^Test^Image^^ "},
{0x0010, 0x0020, "SH", 6, "123456"},
...
{0,0,"",0,""}
};
void WriteCDImage(FILE *fp)
{
? DicomElem elem = CRDataSet[0];
? unsigned long int lComboTag;
? int nCols, nRows;
? unsigned char *pPixelData
? unsigned long int lPixelLength;
? pPixelData = LoadBMPImgeData("MyImage.bmp", nCols, nRows, lPixelLength);
? while(CRDataSet[i].group_tag)
? {
???? lComboTag = (CRDataSet[i].group_tag << 16) | CRDataSet[i].element_tag;
??? tch(lComboTag)
???? {
?????? case 0x00280010:
?????????? *((short int *)CRDataSet[i].data) = nCols;
??????? break;
?????? case 0x00280011:
?????????? *((short int *)CRDataSet[i].data) = nRows;
?????? break;
?????? ...
???? }
????
?? // Write group and element tag
?? fwrite(&lComboTag, 1, sizeof(long), fp);
?? // Write VR
?? fwrite(CRDataSet[i].VR, 1, 2, fp);
?? if (lComboTag != 0x7fe00010)
???? {
??????? fwrite(CRDataSet[i].length, 1, sizeof(short), fp);
??????? fwrite(CRDataSet[i].data, 1, CRDataSet.length, fp);
????? }
?? else
?????? {
????????? fwrite("\0\0", 1, 2, fp); // Two blank bytes after VR
????????? fwrite(&lPixelLength, 1, sizeof(long), fp);? // Length
???????? fwrite(pPixelData, 1, lPixelLength, fp);??
?????? }
? i++;
? }
}
unsigned char *LoadBMPImgeData(char *fileName, int &nCols, int &nRows, unsigned long &lPixelLength)
{
....
}
細(xì)節(jié)自己去寫。
二、DICOM文件讀寫最難的是兩件事情
??? DICOM Sequence
??? DICOM Pixel Data
?Sequence 在 C 里的類比是一個 structure 的 array,是結(jié)構(gòu)套結(jié)構(gòu),所以讀起來難。更麻煩的是 Sequence 還可以不定義長度, 即長度是 -1。要靠你自己去找 (FFFE, E0DD) 來決定 Sequence 是否結(jié)束。
?Array 里面的每個 structure就是 DICOM Sequence 里的一個 Item。Item 的開頭是一個特定 element (FFFE, E000)。 如果 Item 的長度是 -1, 要靠找到 (FFFE, E00D) 來決定 Item 的結(jié)束。
?Pixel Data (7fe0, 0010) 是一個特殊的 DICOM element。總是在所有元素的最后面。它與 Sequence 有兩個相似的地方: 長度區(qū)總是 32-位 (即 explicit VR 的情況下要在 VR 區(qū)后面填兩個 bytes,然后再加 4-bytes 的長度。
??? 如圖像是壓縮的,每幅圖用一個 item 來存。 第一個 item 是個 offset table。每幅圖的 offset 是一個 dword (4 bytes),第一幅圖的 offset 是 0。
三、DICOM file 細(xì)節(jié)問題
??? element 的 data 長度一定要是一個偶數(shù)。
??? 要注意 big endian 和 little endian 的區(qū)別。軟件要自動判斷機(jī)器的 endian 和 DICOM 文件的 transfer syntax。常見的:Intel PC 是 little endian, Sun 是 big endian。
?如果機(jī)器 的 endian 和數(shù)據(jù)的 endian 不對的話要做 byte swap。要做 byte swap 的有所有的根數(shù)字有關(guān)的 binary data, 即 VR = SS, US, SL, UL, FL, FD, OW, AT 等等。DICOM group tag 和 element tag 也要分別做兩個 byte 的 swap。Item 指的是 DICOM Sequence 的 Item。
0x7fe0, 0x0010, "OB", ' '' ', 0xffffffff? // tag, VR, 長度
0xFFFE, 0x0000, 0xffffffff? // Offset item begins
0x00000000? // Offset of first image = 0
0x00001000? // Offset of second image = 4096 (for example)
...
0xFFFE, 0xE00D? // End of offset item
0xFFFE, 0x0000, 0x00001000?? // first image item and length = 4096 (for example)
... // (4096 bytes)
0xFFFE, 0xE00D? // end of first image
...
0xFFFE, 0xE0DD // End o of pixel data sequence
轉(zhuǎn)載于:https://www.cnblogs.com/Chailizi/archive/2007/09/10/888195.html
總結(jié)
以上是生活随笔為你收集整理的DICOM文件格式与编程(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LinuxWorld 2007:Linu
- 下一篇: ***日记(荐)