本文由Markdown语法编辑器编辑完成。
DICOM是所有医疗行业工作者都熟知的标准医学图像格式,但它又不止是一个图像格式那么简单。DICOM标准是一套医疗影像行业内必须遵守的标准,不管是医学影像设备制造商,还是医疗软件公司,医院的PACS等等,都必须遵守。DICOM标准共分为十六个章节。
http://www.cnblogs.com/okaimee/archive/2010/07/19/1780863.html,这位博主对DICOM的相关知识点和研究非常全面,值得借鉴和学习。
目前,多种主流编程语言都有相应的开源包提供对DICOM标准的解释和执行,通过对这些开源库的了解和运用,可以很方便地实现对DICOM图像的解析和处理。C++中比较著名的DICOM标准库是DCMTK,而Java中dcm4che是一个比较全面的函数库。
由于目前项目中用Java进行服务器端编程。在进行影像导入前要对DICOM图像进行解析,以分析图像格式是否合法以及其所含有的TAG信息是否符合软件的需求。项目经理经过调研后让我使用dcm4che进行解析和图像压缩等。因此有必要对dcm4che进行了解。
dcm4che的官网是这样描述的:
DICOM:
Dicom is a specification for the creation, transmission, and storage of digital medical image and report data. It defines a data dictionary, data structures, file format, client and server services, workflow, and compression, among other things. (Dicom是专门用来创建、传输和存储医学图像和报告数据。它定义了数据字典、数据结构、文件格式、客户和服务器服务、工作流和图像压缩及其他事务。)
dcm4che:
Dcm4che is a collection of open source applications and utilities for the healthcare enterprise. These applications have been developed in the Java programming language for performance and portability, supporting deployment on JDK 1.4 and up. (dcm4che是医疗健康行业中一套开源应用程序和工具,采用Java语言开发,支持JDK1.4及以上版本。)
At the core of the dcm4che project is a robust implementation of the Dicom standard. The dcm4che-1.x DICOM toolkit is used in many production application across the world, while the current (2.x) version of the toolkit has been re-architected for high performance and flexibility. (dcm4che项目的核心是一个DICOM标准的健壮实现. Dcm4che-1.x工具箱已经被世界各地的很多产品使用,而目前的2.x版本是对dcm4che-1.x的重构,提供了应用的性能和灵活性。)
作为解释DICOM标准的工具包,当然最基本的功能便是读入和解析DICOM文件了。这时需要用到该工具包中的几个类,分别是:DicomObject, DicomElement。
以下是读入DICOM的例子代码:
DICOM files can be read from Java java.io.InputStream objects, and java.io.File objects. This is done through the org.dcm4che2.io.DicomInputStream class, which extends java.io.FilterInputStream. The DICOM file is typically read into a org.dcm4che2.data.DicomObject implementation.(dicom文件可以从Java的java.io.InputStream类和java.io.File类读取。这是通过dcm4che2的继承于java.io.FilterInputStream的DicomInputStream类完成的。DICOM文件将被读入为DicomObject类的实现)。
DicomObject dcmObj;
DicomInputStream dcmInputStream = null;
try {
dcmInputStream = new DicomInputStream(new File(“image.dcm”));
dcmObj = dcmInputStram.readDicomObject();
} catch (IOException e) {
e.printStackTrace();
return;
} finally{
try {
dcmInputSteam.close();
} catch (IOException ignore) {
}
}
做一个专门用来解析Dicom文件的Service名称为:DicomParseService.java.
package com.jssm.dicomparse.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
import org.dcm4che2.data.DicomElement;
import org.dcm4che2.data.DicomObject;
import org.dcm4che2.data.SpecificCharacterSet;
import org.dcm4che2.data.Tag;
import org.dcm4che2.imageioimpl.plugins.dcm.DicomImageReader;
import org.dcm4che2.io.DicomInputStream;
import org.dcm4che2.io.StopTagInputHandler;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import com.jssm.core.service.BaseService;
import com.jssm.dicomparse.service.DcmJsonWriter;
import com.jssm.dicomparse.service.IDicomParseService;
import com.jssm.dicomquery.model.Frame;
/** 装载Dicom文件和解析Dicom tag的Service.
* @version 1.0.0 */
@Service("dicomParseService")
@Scope("prototype")
public class DicomParseService extends BaseService implements IDicomParseService {
private static final String[] NATIVEFORMATSYNTAXUID = new String[] { "1.2.840.10008.1.2",
"1.2.840.10008.1.2.1", "1.2.840.10008.1.2.2" };
private DicomObject dicomObject;
private InputStream inputStream;
private DicomInputStream dicomInputStream;
/** @ loadDicomFile,读入Dicom文件,并将数据流写入DicomObject. */
public void loadDicomFile(String filePath) throws IOException {
try {
inputStream = new FileInputStream(new File(filePath));
dicomInputStream = new DicomInputStream(inputStream);
dicomObject = dicomInputStream.readDicomObject();
} catch (IOException e) {
// 如果读入文件发生异常时,则将IOException写入log中,而且在log中要注明文件的路径filePath.
log.error(e.toString());
throw e;
}
};
/** @ 关闭文件流. */
public void closeDicomFile() {
try {
if (inputStream != null) {
inputStream.close();
}
if (dicomInputStream != null) {
dicomInputStream.close();
}
} catch (IOException e) {
// 关闭文件失败则写log
log.error("dicomParseService close stream failed.");
}
}
/** @ getDicomJson, 返回json格式的DICOM信息. */
public String getDicomJson(String filePath) {
StringWriter jsonWriter = new StringWriter();
try {
File f = new File(filePath);
DicomInputStream is = new DicomInputStream(f);
JsonGenerator gen = Json.createGenerator(jsonWriter);
DcmJsonWriter jsw = new DcmJsonWriter(gen);
is.setHandler(jsw);
is.readDicomObject();
is.close();
jsw.generatorOver();
} catch (IOException e) {
log.error("in getDicomJson method, read dicom file occured IOException.");
return "";
}
return jsonWriter.toString();
}
/** @获取String类型的tag值. */
public String getString(int tag) {
byte[] byteTagValue = dicomObject.getBytes(tag);
// 如果获取的byte值为空,则返回null;如果不为空,则根据不同的字符编码集进行对应处理.
if (null == byteTagValue) {
return null;
} else {
SpecificCharacterSet characterSet = dicomObject.getSpecificCharacterSet();
String tagValue = null;
String unicodeTagValue = null;
try {
if (null == characterSet) {
// 如果DCM的TAG值编码集为null,则默认先用GBK解码,再将其转化为UTF-8.
unicodeTagValue = new String(byteTagValue, "GBK");
} else {
unicodeTagValue = characterSet.decode(byteTagValue);
}
byte[] utfBytes = unicodeTagValue.getBytes("UTF-8");
tagValue = new String(utfBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
this.log.info(e);
}
return tagValue;
}
};
/** @获取String[]类型的tag值. */
public String[] getStrings(int tag) {
return dicomObject.getStrings(tag);
};
/** @获取Date类型的tag值. */
public Date getDate(int tag) {
return dicomObject.getDate(tag);
};
/** @获取Integer类型的tag值. */
public Integer getInteger(int tag) {
DicomElement dcmElement = dicomObject.get(tag);
if (dcmElement == null) {
return null;
}
return Integer.valueOf(dicomObject.getInt(tag));
};
/** 获取帧信息
* @return List<Frame> */
public List<Frame> getFrames(String filePath) {
List<Frame> listFrames = new ArrayList<Frame>();
boolean isMultiFrame = false;
try {
int numberOfFrames = getNumberOfFrames();
if (numberOfFrames > 1) {
isMultiFrame = true;
}
long[] offsetLength = new long[2];
for (int i = 0; i < numberOfFrames; i++) {
offsetLength = getFrameOffsetLength(filePath, i, isMultiFrame);
if (offsetLength[1] <= 0) {
log.error("DicomParseService getFrames dataLen <= 0!");
break;
}
Frame frameObj = new Frame();
frameObj.setStartPos(offsetLength[0]);
frameObj.setDataLen(offsetLength[1]);
frameObj.setFrameIndex(i);
listFrames.add(frameObj);
}
} catch (IOException e) {
log.error(e);
}
return listFrames;
}
/** 获取帧的起始位置及长度
* @param filePath
* @param frameIndex
* @return long[]
* @throws IOException */
private long[] getFrameOffsetLength(String filePath, int frameIndex, boolean isMultiFrame)
throws IOException {
long[] offsetLength = new long[2];
File fileObj = new File(filePath);
DicomInputStream dcmInputStreamObj = new DicomInputStream(fileObj);
dcmInputStreamObj.setHandler(new StopTagInputHandler(Tag.PixelData));
dcmInputStreamObj.readDicomObject();
String transferSyntaxUID = getString(Tag.TransferSyntaxUID).trim();
boolean isCompressFormat = originalIsCompress(transferSyntaxUID);
if (!isMultiFrame && !isCompressFormat) {
offsetLength[0] = dcmInputStreamObj.getStreamPosition();
offsetLength[1] = dcmInputStreamObj.valueLength();
} else {
offsetLength = fetchFrameOffsetAndLength(fileObj, frameIndex);
}
if (isCompressFormat) {
boolean isLastZeroByte = isLastByteZeroValue(fileObj, offsetLength[0], offsetLength[1]);
if (isLastZeroByte) {
offsetLength[1] = offsetLength[1] - 1;
}
}
dcmInputStreamObj.close();
return offsetLength;
}
/** 判断原始图是否是压缩的
* @param transferSyntaxUID
* @return */
private boolean originalIsCompress(String transferSyntaxUID) {
boolean flag = true;
for (String syntaxUID : NATIVEFORMATSYNTAXUID) {
if (syntaxUID.equals(transferSyntaxUID)) {
flag = false;
}
}
return flag;
}
/** 获取帧的个数
* @return int */
private int getNumberOfFrames() {
Integer nFrames = getInteger(Tag.NumberOfFrames);
// tag不存在时,默认为单帧的。
int numberOfFrames = 1;
if (null != nFrames) {
numberOfFrames = nFrames.intValue();
}
return numberOfFrames;
}
/** 如果最后一个字节是0,则长度减1
* @param inputStream
* @param startPos
* @param dataLen
* @return boolean */
private boolean isLastByteZeroValue(File fileObj, long startPos, long dataLen) {
byte[] byteVal = new byte[1];
boolean result = false;
try {
FileInputStream inStream = new FileInputStream(fileObj);
inStream.skip(startPos + dataLen - 1);
inStream.read(byteVal);
if (byteVal[0] == 0) {
result = true;
}
inStream.close();
} catch (Exception e) {
log.error(e);
log.error("DicomParseService isLastByteZeroValue failed!");
}
return result;
}
/** 获取数据长度
* @param fileObj
* @param frameIndex
* @return long[] */
private long[] fetchFrameOffsetAndLength(File fileObj, int frameIndex) {
long[] offsetLength = new long[2];
try {
ImageInputStream imageInputStream = ImageIO.createImageInputStream(fileObj);
Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("DICOM");
DicomImageReader reader = (DicomImageReader) iter.next();
reader.setInput(imageInputStream, false);
offsetLength = reader.getImageInputStreamOffsetLength(frameIndex);
imageInputStream.close();
} catch (IOException e) {
log.error(e);
log.error("DicomParseService getDataLen failed");
}
return offsetLength;
}
}