- Dicom标准介绍
- Dicom应用场景
- Dicom运用到的库
- OC C++ 混编实现Dicom
Dicom标准
现代医学影像学是基于医学成像、传输、存储与显示等多项技术集成的一门学科。所有经影像设备或后处理软件重建生成的医学影像均应符合相关国际标准,才能满足现代医学影像学的要求。DICOM是美国放射学会( American College of Radiology,ACR)和美国电器制造商协会( National Electrical Manufacturers Association,NEMA) 组织制定的专门用于医学图像存储和传输的标准,是目前国际上公认且通用的医学影像存储与传输标准。DICOM主要涉及信息系统中最主要也是最困难的医学图像的存储和通信,解决在不同地点、不同设备制造商、不同国家等复杂网络环境下的医学图像存储和传输的问题,可直接应用于放射学信息系统( radiology information system,RIS) 和医学影像信息系统(Picture Archiving and Communication Systems,PACS)中。随着我国PACS系统的广泛应用,对医学影像文件格式的要求也越来越集中在DICOM标准遵从性上。只有符合DICOM格式的医学影像数据才能够确保在不同厂家的设备、服务器和工作站之间互相传输及存储。而医学影像在全中文环境下的正确归档与传输仍然得不到测试和验证,因此,为保证医学数字影像中文通信信息的有效交换、分析和共享,医学数字影像通信(DICOM)中文标准符合性测试非常必要。
Dicom应用场景
使用范围
本标准规定了医学数字影像设备和PACS系统中文标准符合性测试方法;
本标准适用于全国各级各类医疗卫生机构、医疗设备生产商、医学影像存储与归档系统(PACS)生产商和放射信息系统(RIS)生产商的软件开发。
医院图像数字浏览通过PC端查看(2D 3D)
Dicom运用到的库 DCMTK(C++)
DCMTK DICOM / MEDICOM标准的一部分的一组软件库和应用程序。
DCMTK包含以下子包,每个子包都位于其自己的子目录中:
- config -DCMTK的配置实用程序
- dcmdata- 数据编码/解码库和实用程序
- dcmect- 用于增强CT对象的库
- dcmfg- 用于功能组的库
- **dcmimage-**向dcmimgle添加对彩色图像的支持
- **dcmimgle-**图像处理库和实用程序
- dcmiod- 用于处理信息对象和模块的库
- dcmjpeg- 压缩/解压缩库和实用程序
- dcmjpls- 压缩/解压缩库和实用程序
- dcmnet- 网络库和实用程序应用程序
- dcmpmap- 用于处理参数化地图对象的库
- **dcmpstat-**演示状态库和实用程序应用
- dcmqrdb- 图像数据库服务器
- dcmrt- 放射治疗库和实用程序
- dcmseg- 用于处理细分对象的库
- dcmsign- 数字签名库和实用程序应用程序
- dcmsr- 结构化的报告库和实用程序
- dcmtls- 网络库的安全扩展
- dcmtract-用于处理体检结果的库
- dcmwlm- 模态工作清单数据库服务器
- oflog- 基于log4cplus的日志记录库
- ofstd- 通用类库
每个子目录(config除外)都包含应用程序源代码(apps),库源代码(libsrc),库包含文件(include),配置数据(etc),文档(docs),样本和支持数据的其他子目录。(数据)以及测试程序(测试)。
OC代码使用
控制器调用包
- (void)viewDidLoad
{
[super viewDidLoad];
//访问本地DCM格式文件 DCM是影像图片格式 需要通过Dicom标准解析才可以显示
NSString * dicomPath = [[NSBundle mainBundle] pathForResource:@"test" ofType: @"dcm"];
//窗宽床位设置
[self decodeAndDisplay:dicomPath];
//图像元数据名字显示
NSString * info = [dicomDecoder infoFor:PATIENT_NAME];
self.patientName.text = [NSString stringWithFormat:@"Patient: %@", info];
info = [dicomDecoder infoFor:MODALITY];
self.modality.text = [NSString stringWithFormat:@"Modality: %@", info];
info = [dicomDecoder infoFor:SERIES_DATE];
self.date.text = info;
info = [NSString stringWithFormat:@"WW/WL: %d / %d", dicom2DView.winWidth, dicom2DView.winCenter];
self.windowInfo.text = info;
panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
panGesture.maximumNumberOfTouches = 1;
[dicom2DView addGestureRecognizer:panGesture];
[panGesture release];
}
其中decodeAndDisplay方法如下 (窗宽床位设置)
- (void) displayWith:(NSInteger)windowWidth windowCenter:(NSInteger)windowCenter
{
if (!dicomDecoder.dicomFound || !dicomDecoder.dicomFileReadSuccess)
{
[dicomDecoder release];
dicomDecoder = nil;
return;
}
NSInteger winWidth = windowWidth;
NSInteger winCenter = windowCenter;
NSInteger imageWidth = dicomDecoder.width;
NSInteger imageHeight = dicomDecoder.height;
NSInteger bitDepth = dicomDecoder.bitDepth;
NSInteger samplesPerPixel = dicomDecoder.samplesPerPixel;
BOOL signedImage = dicomDecoder.signedImage;
BOOL needsDisplay = NO;
if (samplesPerPixel == 1 && bitDepth == 8)
{
Byte * pixels8 = [dicomDecoder getPixels8];
if (winWidth == 0 && winCenter == 0)
{
Byte max = 0, min = 255;
NSInteger num = imageWidth * imageHeight;
for (NSInteger i = 0; i < num; i++)
{
if (pixels8[i] > max) {
max = pixels8[i];
}
if (pixels8[i] < min) {
min = pixels8[i];
}
}
winWidth = (NSInteger)((max + min)/2.0 + 0.5);
winCenter = (NSInteger)((max - min)/2.0 + 0.5);
}
[dicom2DView setPixels8:pixels8
width:imageWidth
height:imageHeight
windowWidth:winWidth
windowCenter:winCenter
samplesPerPixel:samplesPerPixel
resetScroll:YES];
needsDisplay = YES;
}
if (samplesPerPixel == 1 && bitDepth == 16)
{
ushort * pixels16 = [dicomDecoder getPixels16];
if (winWidth == 0 || winCenter == 0)
{
ushort max = 0, min = 65535;
NSInteger num = imageWidth * imageHeight;
for (NSInteger i = 0; i < num; i++)
{
if (pixels16[i] > max) {
max = pixels16[i];
}
if (pixels16[i] < min) {
min = pixels16[i];
}
}
winWidth = (NSInteger)((max + min)/2.0 + 0.5);
winCenter = (NSInteger)((max - min)/2.0 + 0.5);
}
dicom2DView.signed16Image = signedImage;
[dicom2DView setPixels16:pixels16
width:imageWidth
height:imageHeight
windowWidth:winWidth
windowCenter:winCenter
samplesPerPixel:samplesPerPixel
resetScroll:YES];
needsDisplay = YES;
}
if (samplesPerPixel == 3 && bitDepth == 8)
{
Byte * pixels24 = [dicomDecoder getPixels24];
if (winWidth == 0 || winCenter == 0)
{
Byte max = 0, min = 255;
NSInteger num = imageWidth * imageHeight * 3;
for (NSInteger i = 0; i < num; i++)
{
if (pixels24[i] > max) {
max = pixels24[i];
}
if (pixels24[i] < min) {
min = pixels24[i];
}
}
winWidth = (max + min)/2 + 0.5;
winCenter = (max - min)/2 + 0.5;
}
[dicom2DView setPixels8:pixels24
width:imageWidth
height:imageHeight
windowWidth:winWidth
windowCenter:winCenter
samplesPerPixel:samplesPerPixel
resetScroll:YES];
needsDisplay = YES;
}
if (needsDisplay)
{
CGFloat x = (self.view.frame.size.width - imageWidth) /2;
CGFloat y = (self.view.frame.size.height - imageHeight) /2;
dicom2DView.frame = CGRectMake(x, y, imageWidth, imageHeight);
[dicom2DView setNeedsDisplay];
NSString * info = [NSString stringWithFormat:@"WW/WL: %d / %d", dicom2DView.winWidth, dicom2DView.winCenter];
self.windowInfo.text = info;
}
}
本地Dicom解析
- (ushort) getShort
{
Byte b[2];
const NSInteger length = 2;
NSRange range;
range.location = location;
range.length = length;
[dicomData getBytes:b range:range];
location += length;
ushort retValue = 0;
if (littleEndian)
retValue = (ushort)((b[1] << 8) + b[0]);
else
retValue = (ushort)((b[0] << 8) + b[1]);
return retValue;
}
- (int) getInt
{
Byte b[4];
const NSInteger length = 4;
NSRange range;
range.location = location;
range.length = length;
[dicomData getBytes:b range:range];
location += length;
int retValue = 0;
if (littleEndian)
retValue = ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0]);
else
retValue = ((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3]);
return retValue;
}
- (double) getDouble
{
Byte b[8];
const NSInteger length = 8;
NSRange range;
range.location = location;
range.length = length;
[dicomData getBytes:b range:range];
location += length;
double retValue = 0;
if (littleEndian)
{
long long high = (b[7] << 24) + (b[6] << 16) + (b[5] << 8) + b[4];
long long low = (b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0];
retValue = (high << 32) + low;
}
else {
long long high = (b[4] << 24) + (b[5] << 16) + (b[6] << 8) + b[7];
long long low = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
retValue = (high << 32) + low;
}
return retValue;
}
- (float) getFloat
{
Byte b[4];
const NSInteger length = 4;
NSRange range;
range.location = location;
range.length = length;
[dicomData getBytes:b range:range];
location += length;
int retValue = 0;
if (littleEndian)
retValue = ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + (int)b[0]);
else
retValue = (int)((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + (int)b[3]);
return (float)retValue;
}
- (BOOL) getLut:(NSInteger)length buffer:(Byte *)buf
{
if ((length & 1) != 0) {
//NSString *dummy = [self getString:length];
location += length;
return NO;
}
length = length/2;
for (NSInteger i = 0; i < length; ++i) {
buf[i] = (Byte)([self getShort] >> 8);
}
return YES;
}
- (NSInteger) getLength
{
// Get 4 bytes
//
Byte b[4];
const NSInteger length = 4;
NSRange range;
range.location = location;
range.length = length;
[dicomData getBytes:b range:range];
location += length;
// Cannot know whether the VR is implicit or explicit without the
// complete Dicom Data Dictionary.
//
vr = (b[0] << 8) + b[1];
NSInteger retValue = 0;
switch (vr)
{
case OB:
case OW:
case SQ:
case UN:
case UT:
if ((b[2] == 0) || (b[3] == 0))
{
// Explicit VR with 32-bit length if other two bytes are zero
//
retValue = [self getInt];
}
else
{
// Implicit VR with 32-bit length
//
vr = IMPLICIT_VR;
if (littleEndian)
retValue = ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0]);
else
retValue = ((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3]);
}
break;
case AE:
case AS:
case AT:
case CS:
case DA:
case DS:
case DT:
case FD:
case FL:
case IS:
case LO:
case LT:
case PN:
case SH:
case SL:
case SS:
case ST:
case TM:
case UI:
case UL:
case US:
case QQ:
case RT:
// Explicit vr with 16-bit length
//
if (littleEndian)
retValue = ((b[3] << 8) + b[2]);
else
retValue = ((b[2] << 8) + b[3]);
break;
default:
// Implicit VR with 32-bit length...
//
vr = IMPLICIT_VR;
if (littleEndian)
retValue = ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0]);
else
retValue = ((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3]);
break;
}
return retValue;
}