【转】医学影像调窗技术!!!!

转自:https://www.cnblogs.com/assassinx/p/3139505.html

在年初的时候做过一个dicom格式文件解析,当时只是提了下。看着跟别人的显示出来也差不多 其实是我想太简单了。整理了下思路 这里提供正确的调窗代码。 医学影像 说得挺高科技的 其实在这个过程中本身没太复杂的图像处理技术。窗值处理就算是比较“高深”的了 也就是调窗。
网上都是啥基于 DCMTK的DICOM医学图像显示及其调窗方法研究 说得文绉绉的 没啥鸟用 ,dicom没你想象的那么复杂哈 咱这个全是自主代码 顶多看了点C++的源码 然后改成c#版本的 其实都一样的。

具体流程需要以下几个步骤,
1 字节序转换
2 保留有效位,使用&进行位运算截取有效位
3 根据有无符号进行值转换
4 针对CT影像的窗值偏移处理
5 窗值映射 也就是映射到256级灰度(参考上一篇 )

而我原来的代码啥都没做 直接对两个字节的数据进行toUint16 然后就进行窗值映射,还有就是也没有进行预设窗值读取。那么这样做的后果是什么呢 。
我们先加上预设窗值读取,首先我们加上几个变量 进行影像显示的几个关键数据 图像的长 宽 默认窗值 颜色采样数 1为灰度3为彩色 数据存储位数 有效位数 最高位数,具体查看dicom标准。
变量声明 默认窗值读取代码 (预设窗宽tag 0028 1051 预设窗位tag 0028 1050):

 

 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图像需要进行偏移处理则进行偏移处理 然后进行窗值映射。

 

 1 //字节序翻转2 if (isLitteEndian == false)3     Array.Reverse(pixData, 0, 2);4 //取值5 if (signed == false)6     gray = BitConverter.ToUInt16(pixData, 0);7 else8     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 然后一边读取一边更新数据。
这是整理后的标准调窗代码,有点多哈 慢慢看,我说得挺简单 中间有各种复杂情况哈 请参考上面说的步骤及论文里的说明来:

 

  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 Img12             {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 Img27             {28                 byte* p = (byte*)bmpDatas.Scan0;29                 int nMin = ~(0xffff << (nHighBit + 1)), nMax = 0;30                 int indx = 0;//byteData index31 32                 for (int n = 0; n < numPixels; n++)//pixNum index33                 {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 integer48                         {49                             pixValue = (short)((~nMask) & (BitConverter.ToInt16(pixData, 0)));50                         }51                         else52                         {53                             nSignBit = (short)(1 << nHighBit);54                             if (((BitConverter.ToInt16(pixData, 0)) & nSignBit) != 0)55                                 pixValue = (short)(BitConverter.ToInt16(pixData, 0) | nMask);56                             else57                                 pixValue = (short)((~nMask) & (BitConverter.ToInt16(pixData, 0)));58                         }59                     }60                     else if (nHighBit <= 7)61                     {62                         if (signed == false)// Unsigned integer63                         {64                             nMask = (short)(0xffff << (nHighBit + 1));65                             pixValue = (short)((~nMask) & (pixData[0]));66                         }67                         else68                         {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                             else74                                 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-bit87                     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         }

完整源码及测试数据下载 猛击此处

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435975.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】理解字节序 大端字节序和小端字节序

转自&#xff1a;https://www.cnblogs.com/gremount/p/8830707.html 以下内容参考了 http://www.ruanyifeng.com/blog/2016/11/byte-order.html https://blog.csdn.net/yishengzhiai005/article/details/39672529 1. 计算机硬件有两种储存数据的方式&#xff1a;大端字节序…

创建windows服务,定时监控网站应用程序池

最近网站总是报"Timer_Connection"错误,导致该网站所使用的应用程序池由于错误过多停止运行,网站也就出现了service unvaliable,无法访问,在网上查了很多资料,结果很让人无奈,这个问题已经困扰我了很久,一直没有得到解决,后来同事发来一篇文章让我有了新的解决方法,虽…

【转】Qtcreator中常用快捷键和小技巧

转自&#xff1a;https://blog.csdn.net/imxiangzi/article/details/48863855 https://blog.csdn.net/jh1513/article/details/52346802 快捷键及对应含义 下载地址&#xff1a;http://download.csdn.net/detail/jh1513/9615209 快捷键 功能 Esc 切换到代码编辑状态 F1 …

【转】VS编译时自动引用Debug|Release版本的dll

转自&#xff1a;https://www.cnblogs.com/KevinYang/archive/2011/04/10/2011879.html 公司一些早期的项目&#xff0c;把所有工程都放到一个解决方案下了&#xff0c;导致整个解决方案编译很慢&#xff0c;而且也不便于类库的复用和维护。因此我们决定把工程按照功能划分到不…

【转】DICOM之Print!!!!!!!!!

转自&#xff1a;https://blog.csdn.net/weixin_41556165/article/details/81064531 基本概念&#xff1a; Film:在DICOM协议中使用Film来统称不同的Hard Copy&#xff0c;例如photographic film和paper。 DICOM Print的数据流由Print Session、Print Job、Print&#xff08;h…

静态html js文件上传,js实现动态添加上传文件页面

发邮件是需要添加一些文件&#xff0c;每添加一个文件&#xff0c;页面上可以显示一个表单文件上传选项。此功能为&#xff1a;初始时刻只有一个添加按钮&#xff0c;当点击添加文件时&#xff0c;会增加一个选择文件和删除区域&#xff0c;同时显示上传按钮&#xff0c;当点击…

WINCE6.0文件系统及存储管理器

*******************************LoongEmbedded******************************** 作者&#xff1a;LoongEmbedded 时间&#xff1a;2010.12.03 类别&#xff1a;WINCE嵌入式系统 ********************************LoongEmbedded******************************** Filesys.…

【转】Wireshark网络抓包(一)——数据包、着色规则和提示

转自&#xff1a;https://www.cnblogs.com/strick/p/6261463.html 一、数据包详细信息 Packet Details面板内容如下&#xff0c;主要用于分析封包的详细信息。 帧&#xff1a;物理层、链路层 包&#xff1a;网络层 段&#xff1a;传输层、应用层 1&#xff09;Frame 物理层…

【转】Wireshark网络抓包(二)——过滤器

转自&#xff1a;https://www.cnblogs.com/strick/p/6261915.html 一、捕获过滤器 选中捕获选项后&#xff0c;就会弹出下面这个框&#xff0c;在红色输入框中就可以编写过滤规则。 1&#xff09;捕获单个IP地址 2&#xff09;捕获IP地址范围 3&#xff09;捕获广播或多播地址…

html访问虚拟目录路径,IIS7.5虚拟目录物理路径指向共享文件夹详解

本文重点描述如何使用IIS访问共享资源来架设站点或执行 ASP.Net 等脚本。UNC是 Universal Naming Convention 的简称&#xff0c;也叫通用命名规范、通用命名约定。网络(范指局域网)上资源的完整位置名称。通常情况下&#xff0c;拥有多台服务器的朋友在使用IIS建立站点的时候&…

【转】DICOM:DICOM三大开源库对比分析之“数据加载”

背景&#xff1a; 上一篇博文DICOM&#xff1a;DICOM万能编辑工具之Sante DICOM Editor介绍了DICOM万能编辑工具&#xff0c;在日常使用过程中发现&#xff0c;“只要Sante DICOM Editor打不开的数据&#xff0c;基本可以判定此DICOM文件格式错误&#xff08;准确率达99.9999%…

html中点击照片时放大缩小,基于jquery实现一张图片点击鼠标放大再点缩小

. 代码如下:var isopen false;var newImg;var w 200; //将图片宽度200var h 200; // 将图片高度 200$(document).ready(function(){$("img").bind("click", function(){newImg this;if (!isopen){isopen true;$(this).width($(this).width() w);$(th…

css入门之head区设置

收藏夹小图标 如果你将本站加入收藏夹&#xff0c;可以看到在收藏夹网址之前的IE图标变成了本站特别的图标。要实现这样效果很简单&#xff0c;首先制作一个16x16的icon图标&#xff0c;命名为favicon.ico&#xff0c;放在根目录下。然后将下面的代码嵌入head区&#xff1a; &l…

04751计算机网络安全讲解,【19份】04751计算机网络安全自考试卷_历年真题自考答案及解析_湖南080901计算机科学与技术(原B080702计算机及应用)专业-自考生资料网...

1、资料如何使用本商城提供资料为WORD版&#xff0c;可打印成纸质版&#xff0c;结合备考习惯&#xff0c;营造考试氛围。支持手机查看&#xff0c;随时随地&#xff0c;高效学习。WORD文档也可直接用于电脑端学习&#xff0c;快速浏览&#xff0c;永久使用。2、文档无法编辑&a…

【转】Wireshark网络抓包(三)——网络协议

转自&#xff1a;https://www.cnblogs.com/strick/p/6262284.html 一、ARP协议 ARP&#xff08;Address Resolution Protocol&#xff09;地址解析协议&#xff0c;将IP地址解析成MAC地址。 IP地址在OSI模型第三层&#xff0c;MAC地址在OSI第二层&#xff0c;彼此不直接通信…

【转】Wireshark网络抓包(四)——工具

转自&#xff1a;https://www.cnblogs.com/strick/p/6344486.html 一、基本信息统计工具 1&#xff09;捕获文件属性&#xff08;Summary&#xff09; 1. File&#xff1a;了解抓包文件的各种属性&#xff0c;例如抓包文件的名称、路径、文件所含数据包的规模等信息 2. Tim…

silverlight + wcf(json格式) + sqlserver存储过程分页

silverlight并没有提供现成的分页控件&#xff0c;百度了一圈&#xff0c;也没有发现aspx中好用的类似AspNetPager成熟控件&#xff0c;网上现有的一些分页代码&#xff0c;很多也是基于1.0版本的&#xff0c;silverlight2.0的并不多&#xff0c;自个儿琢磨了一下&#xff0c;发…

什么是指利用计算机和现代,现代计算机一般指什么计算机?

现代计算机一般指通用数字电子计算机&#xff0c;它是当今世界电子计算机行业中的主流&#xff0c;其内部处理的是一种称为符号信号或数字信号的电信号&#xff1b;它的主要特点是“离散”&#xff0c;在相邻的两个符号之间不可能有第三种符号存在。电子计算机分为模拟式电子计…

【转】VS中常用图标提示含义

转自&#xff1a;https://www.cnblogs.com/zhjason/articles/14044190.html 有增删 “类视图”和“对象浏览器”图标 “类视图”和“对象浏览器”显示表示代码实体的图标&#xff0c;如命名空间、类、函数和变量 。 下表展示和描述了图标。 图标描述图标描述图标描述图标描述…

二层和三层转发

二层转发的机制是什么?学习线程和报文转发线程。二层只跟MAC地址有关 与IP无关 所以在二层做IP&#xff0d;MAC绑定是无效的。 三层以太网交换机的转发机制主要分为两个部分&#xff1a; 二层转发和三层交换。      先讲二层转发流程。      1、 MAC地址介绍   MA…