




已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Dicom格式文件解析器学数字图像与通讯,这里讲的暂不涉及通讯那方面的问题 只讲*.dcm 也就是diocm格式文件的读取,读取本身是没啥难度的 无非就是字节码数据流处理。只不过确实比较繁琐。分析整体结构先是128字节所谓的导言部分,说俗点就是没啥意义的破数据 跳过就是了,然后是dataElement依次排列的方式 就是一个dataElement接一个dataElement的方式排到文件结尾 通俗的讲dataElement就是指tag 就是破Dicom标准里定义的数据字典。tag是4个字节表示的 前两字节是组号后两字节是偏移号 比如0008,0018。所有dataElement在文件中都是按tag排序的 比如0002,0001 0002,0002 0003,0011文件整体结构如下:又把论文里的这图贴上来 总结的很好。单个dataElement的结构如下:显示VR:VR为OBOWOFUTSQUN的元素结构组号元素号VR预留值长度数据元素值2222(0x00,0x00)4由数据长度决定显示VR:VR为普通类型时元素结构(少了预留那一行)组号元素号VR值长度数据元素值2224由数据长度决定隐式VR时元素结构组号元素号值长度数据元素值224由数据长度决定要问VR是啥东东 ,值表示法 啥叫值表示法啊 俺不懂 int string short ushort 懂不 就是这个意思,Dicom标准真坑爹 非要整个怪怪的概念。VR总共27个 跟c#值类型对应关系我都写好了: 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();1112 break;13 case SL:14 VFStr = BitConverter.ToInt32(VF, 0).ToString();1516 break;17 case UL:18 VFStr = BitConverter.ToUInt32(VF, 0).ToString();1920 break;21 case AT:22 VFStr = BitConverter.ToUInt16(VF, 0).ToString();2324 break;25 case FL:26 VFStr = BitConverter.ToSingle(VF, 0).ToString();2728 break;29 case FD:30 VFStr = BitConverter.ToDouble(VF, 0).ToString();3132 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表示方式像素数据dataElement最重要也是最大的一个数据项 其实存储的就是图像数据几个特殊的tag很重要 前面说过了tag就是dicom里定义的字典。文件元dataElement 和跟像素数据相关的dataElement 都很重要,其他的很多 如果全部照顾完的话估计得写上千行switch语句吧,所以没有必要一般我们一般只抓取关键的tag。并且在隐式语法下要确定VR也必须根据字典来确定关键的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:/病人id 42 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:/协议名 51 return LO; 52 break; 53 case 0018,1151: 54 return IS; 55 break; 56 case 0020,0010:/检查ID 57 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:/单个采样数据长度 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:/像素数据开始处111 return OW;112 break;113 default:114 return UN;115 break;116 117 最关键的两个tag:0002,0010普通tag的读取方式 little字节序还是big字节序 隐式VR还是显示VR。由它的值决定 1 switch (VFStr) 2 3 case 1.2.840.10008.1.2.10:/显示little 4 isLitteEndian = true; 5 isExplicitVR = true; 6 break; 7 case 1.2.840.10008.1.2.20:/显示big 8 isLitteEndian = false; 9 isExplicitVR = true;10 break;11 case 1.2.840.10008.1.20:/隐式little12 isLitteEndian = true;13 isExplicitVR = false;14 break;15 default:16 break;17 7fe0,0010像素数据开始处整理根据以上的分析相信解析一个dicom格式文件的过程已经很清晰了吧第一步:跳过128字节导言部分,并读取DICM4个字符 以确认是dicom格式文件第二步:读 取第一部分 也就是非常重要的文件元dataElement 。读取所有0002开头的tag 并根据0002,0010的值确定传输语法。文件元tag部分的数据元素都是以显示VR的方式表示的 读取它的值 也就是字节码处理 别告诉我说你不会字节码处理哈。传输语法 说得那么官方,你就忽悠吧 其实就确定两个东西而已 1字节序 这个基本上都是little字节序。举个例子吧十进制数 35280 用十六进制表示是0xff00但是存储到文件中你用十六进制编辑器打开你看到的是这个样子00ff 这就是little字节序。平常我们用的x86PC在windows下都是little字节序 包括AMD的CPU。别太较真 较真的话这个问题又可以写篇博客了。2确定从0002以后的dataElement的VR是显示还是隐式。说来说去0002,0010的值就 那么固定几个 并且只能是那么几个 这些都在那个北美放射学会定义的dicom标准的第六章 有说明 :1.2.840.10008.1.2Implicit VR Little Endian: Default Transfer Syntax for DICOMTransfer Syntax1.2.840.10008.1.2.1Explicit VR Little EndianTransfer Syntax1.2.840.10008.1.2.2Explicit VR Big EndianTransfer Syntax上面的那段代码其实就是这个表格的实现,讲到这里你会觉得多么的坑爹啊 是的dicom面向对象的破概念非常烦的。第三步:读 取普通tag 直到搜寻到7fe0,0010 这个最巨体的存储图像数据的 dataElement 它一个顶别人几十个 上百个。我们在前一步已经把VR是显示还是隐式确定 通过前面的图 ,也就是字节码处理而已无任何压力。显示情况下根据VR 和Len确定数据类型跟数据长度直接读取就可以了。隐式情况下这破玩艺儿有点烦,只能根据tag 字典确定它是什么VR再才能读取。关于这个字典也在dicom标准的第六章。上面倒数第二段代码已经把重要的字典都列了出来。第四步:读 取灰度像素数据并调窗 以GDI的方式显示出来。 说实话开始我还以为dicom这种号称医学什么影像的专家制定出来的标准 读取像素数据应该有难度吧 结果没想到这么的傻瓜。直接按像素从左到右从上到下 一行行依次扫描。两个字节表示1个像素普通Dicom格式存储的是16位的灰度图像,其实有效数据只有12位,除去0 所以最高值是2047。比如CT值 从-1000到+1000,空气的密度为-1000 水的密度为0 金属的密度为+1000 总共的值为2000调窗技术:即把12级灰度的数据 通过调节窗宽窗位并让他在RGB模式下显示出来。还技术呢 说实话这个也是没什么技术含量的所谓的技术,两句代码给你整明白。调 节窗宽窗位到底什么意思,12位的数据那么它总共有2047个等级的灰度 没有显示设备可以体现两千多级的明暗度 就算有我们肉眼也无法分辨更无法诊断。我们要诊断是要提取关键密度值的数据 在医院放射科呆久了你一定经常听医生讲什么骨窗 肺窗 之类的词儿,这就是指的这个“窗”。比如有病人骨折了打了钢板我们想看金属部分来诊断 那么我们应该抓取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=255y/2047 原理不多讲 等比中项十字相乘法 这个是初中的知识哈。初中没读过的童鞋飘过。原理过程讲完了代码走起 1 class DicomHandler 2 3 string fileName = ; 4 Dictionary tags = new Dictionary();/dicom文件中的标签 5 BinaryReader dicomFile;/dicom文件流 6 7 /文件元信息/ using System.Drawing;/using System.Drawing.Imaging;/using System.Drawing.Drawing2D; 8 public Bitmap gdiImg;/转换后的gdi图像 9 UInt32 fileHeadLen;/文件头长度 10 long fileHeadOffset;/文件数据开始位置 11 UInt32 pixDatalen;/像素数据长度 12 long pixDataOffset = 0;/像素数据开始位置 13 bool isLitteEndian = true;/是否小字节序(小端在前 、大端在前) 14 bool isExplicitVR = true;/有无VR 15 16 /像素信息 17 int colors;/颜色数 RGB为3 黑白为1 18 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字节导言部分 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() + rn; 44 textBox1.Text += enor.Value.ToString().Replace(0, ); 45 46 else 47 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 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( )/获取图像 在图像数据偏移量已经确定的情况下 74 75 if (fileName = string.Empty) 76 return false; 77 78 int dataLen, validLen;/数据长度 有效位 79 int imgNum;/帧数 80 81 rows = int.Parse(tags0028,0010.Substring(5); 82 cols = int.Parse(tags0028,0011.Substring(5); 83 84 colors = int.Parse(tags0028,0002.Substring(5); 85 dataLen = int.Parse(tags0028,0100.Substring(5); 86 validLen = int.Parse(tags0028,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 = pixDatalen)100 break;101 byte pixData = dicomFile.ReadBytes(dataLen / 8 * colors);102 reads += pixData.Length;103104 Color c = Color.Empty;105 if (colors = 1)106 107 int grayGDI;108109 double gray = BitConverter.ToUInt16(pixData, 0);110 /调窗代码,就这么几句而已111 /1先确定窗口范围 2映射到8位灰度112 int grayStart = (windowCenter - windowWith / 2);113 int grayEnd = (windowCenter + windowWith / 2);114115 if (gray grayEnd)118 grayGDI = 255;119 else120 121 grayGDI = (int)(gray - grayStart) * 255 / windowWith);122 123124 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(pixData0, pixData1, pixData2);133 134135 gdiImg.SetPixel(j, i, c);136 137 138139 dicomFile.Close();140 return true;141 142 void tagRead()/不断读取所有tag 及其值 直到碰到图像数据 (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 /读取tag151 string tag = dicomFile.ReadUInt16().ToString(x4) + , +152 dicomFile.ReadUInt16().ToString(x4);153154 string VR = string.Empty;155 UInt32 Len = 0;156 /读取VR跟Len157 /对OB OW SQ 要做特殊处理 先置两个字节0 然后4字节值长度158 /-这些都是在读取VR一步被阻断的情况159 if (tag.Substring(0, 4) = 0002)/文件头 特殊情况160 161 VR = new string(dicomFile.ReadChars(2);162163 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 else169 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);179180 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 else186 Len = dicomFile.ReadUInt16();187 188 else if (isExplicitVR = false)189 190 VR = getVR(tag);/无显示VR时根据tag一个一个去找 真烦啊。191 Len = dicomFile.ReadUInt32();192 193 /判断是否应该读取VF 以何种方式读取VF194 /-这些都是在读取VF一步被阻断的情况195 byte VF = 0x00 ;196197 if (tag = 7fe0,0010)/图像数据开始了198 199 pixDatalen = Len;200 pixDataOffset = dicomFile.BaseStream.Position;201 dicomFile.B
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论