文章目录
- DICOMWeb Support模块
- 主要数据结构ER
- 查询信息
- 基本信息
- metadata信息
- 统计信息
- 实践
- 查询API及参数
- 解析API返回的json数组
- 定义VRObjectNode
- ObjectMapper解析
- 显示指定tag并解析
- 后记
前期预研的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形式。
- 定义VRObjectNode接收json数组的key和value
- 使用fasterxml的ObjectMapper解析
- 显示指定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<>();@JsonAnySetterpublic void set(String key, Object value) {properties.put(key, value);}// Getter and Setter for propertiespublic 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方式,算是取巧了。。。