近日得到反馈dicom解析的图片不全,结果发现都是多帧的dicom,原来的只能解析单帧,继续原来的解析方式还要先转多帧dicom到多个单帧dicom,再继续解析,很是麻烦。网上几乎没有可用的示例,笔者特在此提供示例
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.UID;
import org.dcm4che3.data.VR;
import org.dcm4che3.emf.MultiframeExtractor;
import org.dcm4che3.image.ICCProfile;
import org.dcm4che3.imageio.plugins.dcm.DicomImageReadParam;
import org.dcm4che3.io.DicomInputStream;
import org.dcm4che3.io.DicomOutputStream;
import org.dcm4che3.util.SafeClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
* @author mysteriousman
public class DicomParserUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(DicomParserUtil.class);
// 目前只支持zip压缩文件
private static final String compressExtension = "zip";
private static final String dicomExtension = "dcm";
private static final MultiframeExtractor extractor = new MultiframeExtractor();
private static final ICCProfile.Option iccProfile = ICCProfile.Option.none;
private static final ImageReader imageReader = ImageIO.getImageReadersByFormatName("DICOM").next();
private static final ImageWriter imageWriter;
private static final ImageWriteParam imageWriteParam;
static {
Iterator<ImageWriter> imageWriters =
ImageIO.getImageWritersByFormatName("JPEG");
if (!imageWriters.hasNext()) {
throw new RuntimeException();
Iterable<ImageWriter> iterable = () -> imageWriters;
imageWriter = StreamSupport.stream(iterable.spliterator(), false)
.filter(matchClassName("com.sun.imageio.plugins.*"))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
imageWriteParam = imageWriter.getDefaultWriteParam();
* 解析上传的文件
* @param multipartFiles 上传文件
public static Map<String, List<DicomAttribute>> parse(List<MultipartFile> multipartFiles) {
for (MultipartFile multipartFile : multipartFiles) {
String extension = FilenameUtils.getExtension(multipartFile.getOriginalFilename());
if (!compressExtension.equalsIgnoreCase(extension)) continue;
InputStream inputStream = null;
try {
inputStream = multipartFile.getInputStream();
return parse(inputStream);
} catch (Exception ignored) {
} finally {
try {
Objects.requireNonNull(inputStream).close();
} catch (Exception ignored) {
return Collections.emptyMap();
* 解析输入流
* @param inputStream 输入流
* @return DICOM文件信息
public static Map<String, List<DicomAttribute>> parse(InputStream inputStream) throws IOException {
Map<String, List<DicomAttribute>> result = new HashMap<>();
parse(inputStream, result);
return result;
* 解析输入流
* @param inputStream 输入流
* @param result DICOM文件信息容器
* @return DICOM文件信息
public static Map<String, List<DicomAttribute>> parse(InputStream inputStream, Map<String, List<DicomAttribute>> result) throws IOException {
ByteArrayInputStream originalInputStream = new ByteArrayInputStream(IOUtils.toByteArray(inputStream));
List<ZipEntry> zipEntries = unwrapZipEntries(originalInputStream);
for (ZipEntry zipEntry : zipEntries) {
if (!zipEntry.isDirectory()) {
String extension = FilenameUtils.getExtension(zipEntry.getName());
InputStream zipEntryInputStream = getInputStream(originalInputStream, zipEntry.getName());
// zip压缩文件
if (compressExtension.equalsIgnoreCase(extension)) {
parse(zipEntryInputStream, result);
// 可能是dicom文件
if (dicomExtension.equalsIgnoreCase(extension) || StringUtils.EMPTY.equals(extension)) {
try {
// dicom文件流
DicomInputStream dicomInputStream = new DicomInputStream(zipEntryInputStream);
Attributes attributes = dicomInputStream.readDataset();
// 当前dicom帧数
int frames = attributes.getInt(Tag.NumberOfFrames, 1);
Attributes fmi;
for (int frame = 1; frame <= frames; frame++) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Attributes sf;
if (frames > 1) {
fmi = dicomInputStream.getFileMetaInformation();
sf = extractor.extract(attributes, frame - 1);
} else {
fmi = null;
sf = attributes;
convert(getInputStream(originalInputStream, zipEntry.getName()), byteArrayOutputStream, fmi, sf, frame);
String imageUrl = "";
//String imageUrl = OssUtil.uploadFileSimply(OssUtil.OssBucket.JPG_BUCKET, byteArrayOutputStream);
List<DicomAttribute> dicomAttributes = new ArrayList<>();
for (int tag : sf.tags()) {
DicomAttribute dicomAttribute = new DicomAttribute();
dicomAttribute.name = tag;
dicomAttribute.value = sf.getString(tag);
dicomAttribute.vr = sf.getVR(tag);
dicomAttributes.add(dicomAttribute);
result.put(imageUrl, dicomAttributes);
LOGGER.info("DICOM解析完毕 图片地址 -> " + imageUrl);
//FileUtils.writeByteArrayToFile(new File("/home/mysteriousman/Downloads/abc" + frame + ".jpeg"), byteArrayOutputStream.toByteArray());
} catch (Exception e) {
LOGGER.warn("DICOM解析失败", e);
return result;
private static void convert(InputStream src, OutputStream target, Attributes fmi, Attributes sf, int frame) throws IOException {
writeImage(target, iccProfile.adjust(readImage(src, frame)));
DicomOutputStream dicomOutputStream = new DicomOutputStream(target, UID.ExplicitVRLittleEndian);
try {
dicomOutputStream.writeDataset(fmi != null
? sf.createFileMetaInformation(
fmi.getString(Tag.TransferSyntaxUID))
: null, sf);
} finally {
SafeClose.close(dicomOutputStream);
private static void writeImage(OutputStream target, BufferedImage bi) throws IOException {
imageWriter.setOutput(ImageIO.createImageOutputStream(target));
imageWriter.write(null, new IIOImage(bi, null, null), imageWriteParam);
private static BufferedImage readImage(InputStream inputStream, int frame) throws IOException {
try (DicomInputStream dicomInputStream = new DicomInputStream(inputStream)) {
imageReader.setInput(dicomInputStream);
return imageReader.read(frame - 1, readParam());
private static ImageReadParam readParam() {
DicomImageReadParam param = (DicomImageReadParam) imageReader.getDefaultReadParam();
param.setAutoWindowing(true);
param.setPreferWindow(true);
param.setOverlayActivationMask(0xffff);
param.setOverlayGrayscaleValue(0xffff);
param.setOverlayRGBValue(0xffffff);
return param;
private static List<ZipEntry> unwrapZipEntries(InputStream inputStream) {
List<ZipEntry> zipEntries = new ArrayList<>();
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry ze;
while ((ze = zipInputStream.getNextEntry()) != null) {
LOGGER.debug("Unzipping " + ze.getName());
zipEntries.add(ze);
} catch (Exception e) {
LOGGER.warn("Unwrap file failed", e);
return zipEntries;
private static InputStream getInputStream(InputStream inputStream, String entry) throws IOException {
inputStream.reset();
ZipInputStream zin = new ZipInputStream(inputStream);
for (ZipEntry e; (e = zin.getNextEntry()) != null; ) {
if (e.getName().equals(entry)) {
byte[] bytes = IOUtils.toByteArray(zin);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
inputStream.reset();
return byteArrayInputStream;
throw new EOFException("Cannot find " + entry);
private static Predicate<Object> matchClassName(String clazz) {
Predicate<String> predicate = clazz.endsWith("*")
? startsWith(clazz.substring(0, clazz.length() - 1))
: clazz::equals;
return w -> predicate.test(w.getClass().getName());
private static Predicate<String> startsWith(String prefix) {
return s -> s.startsWith(prefix);
public static class DicomAttribute {
public int name;
public Object value;
public VR vr;
使用前自行引入相应的依赖包,注意当前只支持zip压缩文件。可以处理基于spring的文件上传parse(List<multipartFiles> multipartFiles),也可直接使用parse(InputStream inputStream)处理zip输入流;方法最终返回图片地址(需要自己处理)及相应的dicom属性。
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
最后更新:2023年3月7日