正如段子所言,“春节”副本已通关,您的属性有如下变化:力量 0,智力 0,敏捷 0,体重 15,金钱-999。您已被传送回出发点,系统时间从“初八”切换到“周四”,请继续进行主线任务……虽然公司春节多放了几天假,原本以为上班还早的我突然发现原来这个月是2月啊,只有28天,怎奈假期配置测试的DCM4CHEE系列文章还未整理完成,既然到了月底了,就凑一篇水文吧,把春节帮网友写的一个基于fo-dicom的DICOM Viewer贴出来,代码拙劣,仅供参考。
PS:DCM4CHEE是一种基于JAVA的开源DICOM框架,预告一下后续专栏文章会对其进行相关介绍,如果有可能会单独开一个专栏,敬请期待。
这里基于fo-dicom开源库设计一个简单的DICOM Viewer,相较于大家所熟知的Sante DICOM Editor、ImageJ、RadiAnt等DICOM阅读器,本文只是简单的提取DICOM文件中的基本信息和图像。目前仅支持常规和JPEG无损压缩(即,JPEG LossLess,Non-Hierachical(Process 14))两种类型图像
本DICOM Viewer采用C#语言开发,程序主界面如下:
从上图可以看出主界面由三部分构成,菜单栏、DICOM信息列表和DICOM图像显示。各部分的功能简单介绍如下:
1)菜单栏
菜单栏包括‘File’和‘Help’两个选项。‘File’中包含‘Open’和‘Close’两个子项,单击‘Open’会打开“文件打开”对话框,选择要打开的DCM文件( 目前打开对话框中是通过文件扩展名来识别文件的,因此需要明确将DCM文件扩展名设置为DCM或dcm才可以导入 );单击‘Close’选项相当如复原操作,会清除界面中显示的DCM文件的信息列表和图像,返回界面初始状态;按照惯例‘Help’中的‘About’就是一个简单的软件介绍。
2)信息列表
‘信息列表’用于显示DICOM文件中的Patient、Study、Series和Image等相关信息,包含GroupTag、ElementTag和Value三部分
3)图像显示
‘图像显示’区域默认是一副灰色白字背景图,界面初始化以及单击‘File’下的‘Close’按钮都会显示该默认背景图。
直接给出软件的核心部分源码,通过fo-dicom开源库来导入和提取DICOM图像的信息,然后利用ListView和Bitmap分别来实现信息的显示和图像绘制。核心函数主要如下:
1)文件导入
直接使用fo-dicom开源库中DicomFile类的静态方法Open完成DCM文件导入,代码如下:
try
{
DicomFile dcmFile = DicomFile.Open(dcmFileName);
//Load and Show the meta info of the DCM file LoadAndShowDCMMetaInfo(dcmFile);
listView1.Update();
//Drawing the image in your DCM file to screen.
DrawingDCMData2Screen(dcmFile);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
2)信息提取及显示
利用DicomDataset的模板方法Get<>()分别提取DCM文件中的相关信息,通过构造ListViewItem添加到ListView中进行显示,具体代码如下:
/// <summary>
/// Load all of the Elements in the DCM Meta
/// </summary>
/// <param name="dcm">the DICOM file</param>
private void LoadAndShowDCMMetaInfo(DicomFile dcm)
{
//Show the message of DCM file to the listview in the dialog.
DicomDataset dcmDataset = dcm.Dataset;
DicomFileMetaInformation dcmMetaInfo = dcm.FileMetaInfo;
listView1.BeginUpdate();
//DICOM MetaInfo
if (!String.IsNullOrWhiteSpace(dcmMetaInfo.MediaStorageSOPClassUID==null?"":dcmMetaInfo.MediaStorageSOPClassUID.ToString()))
{
//Insert the ImplementationClassUID
ListViewItem newItem = new ListViewItem("0002");
newItem.SubItems.Add("0002");
newItem.SubItems.Add(dcmMetaInfo.MediaStorageSOPClassUID.ToString());
listView1.Items.Add(newItem);
}
if (!String.IsNullOrWhiteSpace(dcmMetaInfo.MediaStorageSOPInstanceUID==null?"":dcmMetaInfo.MediaStorageSOPInstanceUID.ToString()))
{
//Insert the ImplementationClassUID
ListViewItem newItem = new ListViewItem("0002");
newItem.SubItems.Add("0003");
newItem.SubItems.Add(dcmMetaInfo.MediaStorageSOPInstanceUID.ToString());
listView1.Items.Add(newItem);
}
if (!String.IsNullOrWhiteSpace(dcmMetaInfo.TransferSyntax==null?"":dcmMetaInfo.TransferSyntax.ToString()))
{
//Insert the ImplementationClassUID
ListViewItem newItem = new ListViewItem("0002");
newItem.SubItems.Add("0010");
newItem.SubItems.Add(dcmMetaInfo.TransferSyntax.ToString());
listView1.Items.Add(newItem);
}
if (!String.IsNullOrWhiteSpace(dcmMetaInfo.ImplementationClassUID==null?"":dcmMetaInfo.ImplementationClassUID.ToString()))
{
//Insert the ImplementationClassUID
ListViewItem newItem = new ListViewItem("0002");
newItem.SubItems.Add("0012");
newItem.SubItems.Add(dcmMetaInfo.ImplementationClassUID.ToString());
listView1.Items.Add(newItem);
}
if (!String.IsNullOrWhiteSpace(dcmMetaInfo.ImplementationVersionName==null?"":dcmMetaInfo.ImplementationVersionName.ToString()))
{
//Insert the ImplementationClassUID
ListViewItem newItem = new ListViewItem("0002");
newItem.SubItems.Add("0013");
newItem.SubItems.Add(dcmMetaInfo.ImplementationVersionName.ToString());
listView1.Items.Add(newItem);
}
//Patient Info
string patientName = dcmDataset.Get<string>(DicomTag.PatientName, "");
if (!String.IsNullOrWhiteSpace(patientName))
{
ListViewItem newItem = new ListViewItem("0010");
newItem.SubItems.Add("0010");
newItem.SubItems.Add(patientName);
listView1.Items.Add(newItem);
}
string patientID = dcmDataset.Get<string>(DicomTag.PatientID, "");
if (!String.IsNullOrWhiteSpace(patientID))
{
ListViewItem newItem = new ListViewItem("0010");
newItem.SubItems.Add("0020");
newItem.SubItems.Add(patientID);
listView1.Items.Add(newItem);
}
string patientBirth = dcmDataset.Get<string>(DicomTag.PatientBirthDate);
if (!String.IsNullOrWhiteSpace(patientBirth))
{
ListViewItem newItem = new ListViewItem("0010");
newItem.SubItems.Add("0030");
newItem.SubItems.Add(patientBirth);
listView1.Items.Add(newItem);
}
string patientSex = dcmDataset.Get<string>(DicomTag.PatientSex);
if (!String.IsNullOrWhiteSpace(patientSex))
{
ListViewItem newItem = new ListViewItem("0010");
newItem.SubItems.Add("0040");
newItem.SubItems.Add(patientSex);
listView1.Items.Add(newItem);
}
string patientAge = dcmDataset.Get<string>(DicomTag.PatientAge);
if (!String.IsNullOrWhiteSpace(patientAge))
{
ListViewItem newItem = new ListViewItem("0010");
newItem.SubItems.Add("1010");
newItem.SubItems.Add(patientAge);
listView1.Items.Add(newItem);
}
//Study & Series Info
string studyDate = dcmDataset.Get<string>(DicomTag.StudyDate);
if (!String.IsNullOrWhiteSpace(studyDate))
{
ListViewItem newItem = new ListViewItem("0008");
newItem.SubItems.Add("0020");
newItem.SubItems.Add(studyDate);
listView1.Items.Add(newItem);
}
string studyTime = dcmDataset.Get<string>(DicomTag.StudyTime);
if (!String.IsNullOrWhiteSpace(studyTime))
{
ListViewItem newItem = new ListViewItem("0008");
newItem.SubItems.Add("0030");
newItem.SubItems.Add(studyTime);
listView1.Items.Add(newItem);
}
string studyInstanceID = dcmDataset.Get<string>(DicomTag.StudyInstanceUID);
if (!String.IsNullOrWhiteSpace(studyInstanceID))
{
ListViewItem newItem = new ListViewItem("0020");
newItem.SubItems.Add("000D");
newItem.SubItems.Add(studyInstanceID);
listView1.Items.Add(newItem);
}
string seriesInstanceID = dcmDataset.Get<string>(DicomTag.SeriesInstanceUID);
if (!String.IsNullOrWhiteSpace(seriesInstanceID))
{
ListViewItem newItem = new ListViewItem("0020");
newItem.SubItems.Add("000E");
newItem.SubItems.Add(seriesInstanceID);
listView1.Items.Add(newItem);
}
string studyID = dcmDataset.Get<string>(DicomTag.StudyID);
if (!String.IsNullOrWhiteSpace(studyID))
{
ListViewItem newItem = new ListViewItem("0020");
newItem.SubItems.Add("0010");
newItem.SubItems.Add(studyID);
listView1.Items.Add(newItem);
}
//Image Info
string imageHeight = dcmDataset.Get<string>(DicomTag.Rows);
if (!String.IsNullOrWhiteSpace(imageHeight))
{
ListViewItem newItem = new ListViewItem("0028");
newItem.SubItems.Add("0010");
newItem.SubItems.Add(imageHeight);
listView1.Items.Add(newItem);
}
string imageWidth = dcmDataset.Get<string>(DicomTag.Columns);
if (!String.IsNullOrWhiteSpace(imageWidth))
{
ListViewItem newItem = new ListViewItem("0028");
newItem.SubItems.Add("0011");
newItem.SubItems.Add(imageWidth);
listView1.Items.Add(newItem);
}
string bitsAlloctaed = dcmDataset.Get<string>(DicomTag.BitsAllocated);
if (!String.IsNullOrWhiteSpace(bitsAlloctaed))
{
ListViewItem newItem = new ListViewItem("0028");
newItem.SubItems.Add("0100");
newItem.SubItems.Add(bitsAlloctaed);
listView1.Items.Add(newItem);
}
string bitsStored = dcmDataset.Get<string>(DicomTag.BitsStored);
if (!String.IsNullOrWhiteSpace(bitsStored))
{
ListViewItem newItem = new ListViewItem("0028");
newItem.SubItems.Add("0101");
newItem.SubItems.Add(bitsStored);
listView1.Items.Add(newItem);
}
string highBits = dcmDataset.Get<string>(DicomTag.HighBit);
if (!String.IsNullOrWhiteSpace(highBits))
{
ListViewItem newItem = new ListViewItem("0028");
newItem.SubItems.Add("0102");
newItem.SubItems.Add(highBits);
listView1.Items.Add(newItem);
}
listView1.EndUpdate();
}
3)图像提取及绘制
首先根据DCM文件的TransferSyntax字段是否是压缩语义来进行分类提取,获得DCM文件中的原始图像信息。代码如下:
DicomFile newDcmFile = null;
if (dcm.FileMetaInfo.TransferSyntax.IsEncapsulated)//if the data is compressed
{
System.Reflection.Assembly.LoadFrom(Path.Combine(Application.StartupPath,"Dicom.Native64.dll"));
DicomTranscoder.LoadCodecs(Application.StartupPath, "Dicom.Native*.dll");
newDcmFile=dcm.ChangeTransferSyntax(DicomTransferSyntax.ExplicitVRLittleEndian,new DicomJpegLsParams());
}
DicomImage imageDcm=null;
if (newDcmFile != null)
imageDcm = new DicomImage(newDcmFile.Dataset);
else
imageDcm = new DicomImage(dcm.Dataset);
DicomDataset dataset = dcm.Dataset;
byte[] fs = imageDcm.PixelData.NumberOfFrames < 2 ? imageDcm.PixelData.GetFrame(0).Data : imageDcm.PixelData.GetFrame(1).Data;
uint size = (uint)Marshal.SizeOf(typeof(short));
uint padding = (uint)fs.Length % size;
uint count = (uint)fs.Length / size;
short[] values = new short[count];
System.Buffer.BlockCopy(fs, 0, values, 0, (int)(fs.Length - padding));
int height = dataset.Get<int>(DicomTag.Rows);
int width = dataset.Get<int>(DicomTag.Columns);
int windowsWidth = (int)dataset.Get<double>(DicomTag.WindowWidth);
int windowsCenter = (int)dataset.Get<double>(DicomTag.WindowCenter);
//if the windowsWidth = 0, the DCM file is not standard type.
if (windowsWidth == 0 || windowsCenter==0)
{
windowsWidth = values.Max<short>()-values.Min<short>();
windowsCenter = windowsWidth / 2;
}
int low = windowsCenter - windowsWidth / 2;
int high = windowsCenter + windowsWidth / 2;
然后构造Bitmap对象,添加到PictureBox控件中完成图像的显示,代码如下:
Bitmap bitmap = new Bitmap(width, height);
for (int i = 0; i < height; ++i)
{
for (int j = 0; j < width; ++j)
{
int r, g, b;
int temp = (int)values[(width - j - 1) * height + i];
int val = temp > high ? 255 : (temp < low ? 0 : ((temp - low) * 255 / windowsWidth));
r = g = b = val;
bitmap.SetPixel(i, width-j-1, Color.FromArgb(r, g, b));
}
}
picturebox1.Image=bitmap;
DICOM Viewer on Github
作者:[email protected]
时间:2015-02-28