基于dcm4chee搭建的PACS系统讲解(三)服务端使用Rest API获取study等数据

12 篇文章 0 订阅
2 篇文章 0 订阅


前期预研的PACS系统,近期要在项目中上线了。因为PACS系统采用无权限认证,业务系统若直接访问PACS系统获取数据,有认证风险,所以决定将PACS系统中部分数据同步至服务端。

DICOMWeb Support模块

dcm4chee搭建的PACS系统中,包含DICOMWeb Support模块,即web形式访问DICOM对象,包含查询Study、StudySeries及instance等数据API,具体可以查看官方提供的swagger地址

主要数据结构ER

PACS主要数据结构包括:Patient(患者) / Study(病例) / Series(序列) / SOP Instances(图像信息),ER图可以参考下图

  • Patient(1) - Study(n)
  • Study(1) - Series(n)
  • Series(1) - SOP Instances(n)
    在这里插入图片描述

查询信息

基本信息

在这里插入图片描述
以上API是查询各个数据结构对应的基本信息(主要为DICOMWeb页面展示数据),返回数据为json数组,数据包括:

  • 查询病例:病例信息 + 按病例维度的相关统计
  • 查询序列:病例信息 + 序列信息
  • 查询图像:病例信息 + 序列信息 + 图像信息
  • 查询患者:患者信息 + 按患者维度的相关统计

metadata信息

图片中红框中的API是获取study、series及instance对象在dicom文件中的所有属性,非常的全面。返回数据为json数组。
在这里插入图片描述

统计信息

PACS系统提供了获取patient、study、instance、modality及institution维度的统计信息,返回json形如{"count":10}
在这里插入图片描述

实践

查询API及参数

本文主要涉及查询如下API:

API名称url
study列表http://<host>/dcm4chee-arc/aets/DCM4CHEE/rs/studies?includefield=all
study单条记录http://<host>/dcm4chee-arc/aets/DCM4CHEE/rs/studies?StudyInstanceUID={studyIUID}&includefield=all
series列表http://<host>/dcm4chee-arc/aets/DCM4CHEE/rs/studies/{studyIUID}/series?includefield=all&orderby=SeriesNumber
单条instance记录http://<host>/dcm4chee-arc/aets/DCM4CHEE/rs/studies/{studyIUID}/series/{seriesIUID}/instances?offset=0&limit=1&includefield=all
单条instance metadata记录http://<host>/dcm4chee-arc/aets/DCM4CHEE/rs/studies/{studyIUID}/series/{seriesIUID}/instances/{sopIUID}/metadata

NOTE:includefield属性表示查询PACS支持暴露的所有字段名。

解析API返回的json数组

查询病例数据,返回json数组如下:

[{"00080005":{"vr":"CS","Value":["ISO_IR 100"]},"00080020":{"vr":"DA","Value":["20151124"]},"00080030":{"vr":"TM","Value":["165546.548881"]},"00080050":{"vr":"SH"},"00080054":{"vr":"AE","Value":["DCM4CHEE"]},"00080056":{"vr":"CS","Value":["ONLINE"]},"00080061":{"vr":"CS","Value":["CT"]},"00080090":{"vr":"PN"},"00080201":{"vr":"SH"},"00081190":{"vr":"UR","Value":["http://172.16.100.216:8080/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.2.826.0.1.3680043.2.1125.1.45651447217485882639453512019955538"]},"00100010":{"vr":"PN","Value":[{"Alphabetic":"PANCREAS_0034"}]},"00100020":{"vr":"LO","Value":["PANCREAS_0034"]},"00100030":{"vr":"DA"},"00100040":{"vr":"CS"},"0020000D":{"vr":"UI","Value":["1.2.826.0.1.3680043.2.1125.1.45651447217485882639453512019955538"]},"00200010":{"vr":"SH","Value":["PANCREAS_0034"]},"00201206":{"vr":"IS","Value":["1"]},"00201208":{"vr":"IS","Value":["205"]}}]

刚开始定义VO与json层次对应,每个属性对应dicom的tag(json中的key值),结果解析失败,不得不将json数据解析为Map形式。

  1. 定义VRObjectNode接收json数组的key和value
  2. 使用fasterxml的ObjectMapper解析
  3. 显示指定tag值,解析到对应属性中

定义VRObjectNode

package com.lizzy.vo.admin.dicom;

import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnySetter;

public class VRObjectNode {
	
	private Map<String, Object> properties = new HashMap<>();

    @JsonAnySetter
    public void set(String key, Object value) {
        properties.put(key, value);
    }

    // Getter and Setter for properties
    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }
}

ObjectMapper解析

import com.fasterxml.jackson.databind.ObjectMapper;

public List<VRObjectNode> parse(String url) {

	ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
	if (HttpStatus.OK != responseEntity.getStatusCode()) {
		log.info("[parse] 访问PACS系统失败(url:{}),失败cdoe:{}", url, responseEntity.getStatusCodeValue());
		return new ArrayList<VRObjectNode>();
	}
	
	String dataStr = responseEntity.getBody();
	if (!StringUtils.hasLength(dataStr)) {
		log.info("[parse] PACS系统中无病例数据(url:{})!", url);
		return new ArrayList<VRObjectNode>();
	}	
	try {
		ObjectMapper objectMapper = new ObjectMapper();
		List<VRObjectNode> studies = 
				objectMapper.readValue(dataStr, objectMapper.getTypeFactory().constructCollectionType(List.class, VRObjectNode.class));
		return studies;
	} catch (JsonMappingException e) {
		
		e.printStackTrace();
	} catch (JsonProcessingException e) {
		
		e.printStackTrace();
	}
}

显示指定tag并解析

如下代码中,定义一个StudyConvertVo接收study属性。

public void setStudyAttr() {
	// get nodeList 
	// ...
	for (VRObjectNode node : nodeList) {	
		StudyConvertVo vo = new StudyConvertVo();
		vo.setAccessionNo(parseVRValue("00080050", node));
		vo.setDcmStudyId(parseVRValue("00200010", node));
		vo.setStudyIUID(parseVRValue("0020000D", node));
		vo.setStudyDate(parseVRValue("00080020", node));
		vo.setStudyTime(parseVRValue("00080030", node));
		vo.setStudyDescr(parseVRValue("00081030", node));
		vo.setPatientId(parseVRValue("00100020", node));
		vo.setPatientBirthdate(parseVRValue("00100030", node));
		vo.setPatientName(parseVRValue("00100010", node));
		vo.setPatientSex(parseVRValue("00100040", node));
		vo.setSeriesNum(parseVRValue("00201206", node));
		vo.setImageNum(parseVRValue("00201208", node));
		// save 
	}
}

private String parseVRValue(String tag, VRObjectNode node) {
		
	HashMap<String, Object> objectMap = (HashMap<String, Object>) node.getProperties().get(tag);
	if (null == objectMap || !objectMap.containsKey("Value")) {
		return null;
	}
	
	String parseValue = null;
	String vr = (String) objectMap.get("vr");
	// 患者姓名需要特殊处理
	if ("PN".equals(vr)) {
		
		// 此处强转HashMap<String, Object>会报错,提示类型为List<String> 
		List<String> values =  (List<String>) objectMap.get("Value");
		for (Object value : values) {
			
			HashMap<String, Object> nameMap = (HashMap<String, Object>) value;
			if (nameMap != null && nameMap.containsKey("Alphabetic")) {
				parseValue = String.valueOf(nameMap.get("Alphabetic"));
				break;
			}
		}
	} else {
		
		List<String> values = (List<String>) objectMap.get("Value");
		if (CollectionUtils.isNotEmpty(values)) {
			parseValue = String.valueOf(values.get(0));
		}
	}
	log.debug("vr:{}, value:{}", vr, parseValue);
	
	return parseValue;
}

后记

解析dicom数据,可以依赖dcm4chee提供的各个工具包,但若将这些工具包加入到项目中十分的厚重,所以本文采用显示解析dicom tag方式,算是取巧了。。。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用dcm4che库获取dcm4chee服务的Dicom文件的Java代码示例: ```java import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Tag; import org.dcm4che3.io.DicomInputStream; import org.dcm4che3.net.ApplicationEntity; import org.dcm4che3.net.Connection; import org.dcm4che3.net.Device; import org.dcm4che3.net.QueryOption; import org.dcm4che3.net.service.DicomServiceException; import org.dcm4che3.net.service.QueryRetrieveSCP; import org.dcm4che3.net.service.QueryRetrieveService; import org.dcm4che3.util.SafeClose; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.List; public class DicomRetrieveExample { public static void main(String[] args) throws Exception { // 连接dcm4chee服务 Device device = new Device("retrieve"); Connection conn = new Connection(); conn.setHostname("dcm4chee-server"); conn.setPort(11112); device.setConnection(conn); ApplicationEntity ae = new ApplicationEntity("retrieve"); ae.addConnection(conn); device.addApplicationEntity(ae); device.setAssociationInitiator(true); device.setPackPDV(true); device.setTcpNoDelay(true); device.setConnectTimeout(5000); device.setRequestTimeout(5000); device.setReceivePDULength(16384); device.setSendPDULength(16384); device.setRspTimeout(5000); device.setAssociationAcceptor(true); device.setAsyncOpsWindow(0, 1); device.setMaxAsyncOpsInvoked(1); device.setMaxAsyncOpsPerformed(1); device.setStorageSCP(new QueryRetrieveSCP("*") { @Override protected void store(Socket socket, InputStream data, int pcid, String tsuid, Attributes attrs, Attributes rsp) throws IOException, DicomServiceException { // 不存储 } }); device.startListening(); // 检索Dicom文件 QueryRetrieveService qrService = ae.getDevice().getQrService(); qrService.setQueryOptions(QueryOption.RELATIONAL); qrService.setRetrieveTimeout(10000); qrService.setCMoveDestination(conn.getCalledAET()); List<String> studies = qrService.queryStudies("*"); for (String study : studies) { List<String> series = qrService.querySeries(study, "*"); for (String serie : series) { List<String> instances = qrService.queryInstances(serie, "*"); for (String instance : instances) { // 获取Dicom文件 DicomInputStream dis = null; FileOutputStream fos = null; try { dis = new DicomInputStream(qrService.retrieveInstance(instance)); Attributes attrs = dis.readDataset(-1, -1); // 保存Dicom文件 File outputFile = new File("path/to/output/file.dcm"); fos = new FileOutputStream(outputFile); fos.write(dis.readBytes()); } finally { SafeClose.close(fos); SafeClose.close(dis); } } } } // 断开连接 device.stopListening(); device.unbindConnections(); } } ``` 这个示例演示了如何使用dcm4che库从dcm4chee服务检索Dicom文件。首先,使用dcm4che库连接dcm4chee服务。然后,使用dcm4chee服务的QueryRetrieveService查询Dicom文件,获取Dicom文件并保存它们。最后,断开连接。 请将`dcm4chee-server`替换为dcm4chee服务的主机名或IP地址,并将`path/to/output/file.dcm`替换为要保存的文件路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值