前言
关于dcm4che的介绍就不多做阐述了,很多博客都有很详细的介绍。
这里记一次使用dcm4che工具包中的findscu做查询。
前提是已经在安装好dcm4che服务,具体安装步骤也不多做阐述了。
dcm4che-tool-findscu
github上有开源项目
地址:https://github.com/dcm4che/dcm4che/tree/master/dcm4che-tool/dcm4che-tool-findscu
步骤
1.新建一个spring boot项目
2.引入findscu所需依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.dcm4che</groupId>
<artifactId>dcm4che-core</artifactId>
<version>5.22.5</version>
</dependency>
<dependency>
<groupId>org.dcm4che</groupId>
<artifactId>dcm4che-net</artifactId>
<version>5.22.5</version>
</dependency>
<dependency>
<groupId>org.dcm4che.tool</groupId>
<artifactId>dcm4che-tool-common</artifactId>
<version>5.22.5</version>
</dependency>
3.将开源的java代码:FindSCU.java和配置文件:messages.properties拷贝到spring boot项目中,结构如下:
4.在FindSCU类中,有一个main方法,是通过命令查询的,github上给出了命令的格式和例子。
格式:
例子:
github上对各个命令都有解释,这里简单解释一下这个例子
-c 代表远程连接
DCMQRSCP是指dcm4che服务的AETitle
localhost是指dcm4che服务的ip地址
11112是指dcm4che服务的端口
-m PatientName=Doe^John 代表查询患者姓名是Doe^John
-m StudyDate=20110510 代表查询患者检查时间是20110510
-m ModalitiesInStudy=CT 代表查询患者的模态是CT
命令也支持xml文件查询和结果导出xml,本文不记录这些操作
5.代码(用配置keys的方法代替命令查询)
读取messages.properties文件配置
private static ResourceBundle rb = ResourceBundle.getBundle("static.messages");
配置远程连接AE title、address、port
private static void configureConnect(Connection conn, AAssociateRQ rq) throws ParseException {
// 获取title属性值
String title = rb.getString("title");
if (StringUtils.isBlank(title)) {
throw new ParseException("title cannot be missing");
}
// 设置AE title
rq.setCalledAET(title);
// 读取host和port属性值
String host = rb.getString("host");
String port = rb.getString("port");
if (StringUtils.isBlank(host) || StringUtils.isBlank(port)) {
throw new ParseException("host or port cannot be missing");
}
// 设置host和por
conn.setHostname(host);
conn.setPort(Integer.parseInt(port));
}
设置Information Model
private static void configureServiceClass(FindSCU main) throws ParseException {
main.setInformationModel(informationModelOf(), IVR_LE_FIRST, queryOptionsOf());
}
private static InformationModel informationModelOf() throws ParseException {
try {
String model = rb.getString("model");
// 如果model为空,默认StudyRoot
return StringUtils.isNotBlank(model) ? InformationModel.valueOf(model) : InformationModel.StudyRoot;
} catch (IllegalArgumentException e) {
throw new ParseException(MessageFormat.format(rb.getString("invalid-model-name"), rb.getString("model")));
}
}
private static String[] IVR_LE_FIRST = new String[] { "1.2.840.10008.1.2", "1.2.840.10008.1.2.1",
"1.2.840.10008.1.2.2" };
private static EnumSet<QueryOption> queryOptionsOf() {
return EnumSet.noneOf(QueryOption.class);
}
设置检索级别
private static void configureRetrieve(FindSCU main) {
if (StringUtils.isNotBlank(rb.getString("level"))) {
// Retrieve是指SCU通过Query 拿到信息后,要求对方根据请求级别 (Patient/Study/Series/Image) 发送影像给己方。
// 默认Patient
main.addLevel(rb.getString("level"));
}
}
配置 --cancel
private static void configureCancel(FindSCU main) {
if (StringUtils.isNotBlank(rb.getString("cancel"))) {
main.setCancelAfter(Integer.parseInt(rb.getString("cancel")));
}
}
设置优先级
private static int priorityOf() {
String high = rb.getString("prior-high");
String low = rb.getString("prior-low");
return StringUtils.isNotBlank(high) ? 1 : (StringUtils.isNotBlank(low) ? 2 : 0);
}
打开链接
private void open()
throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException {
as = ae.connect(conn, remote, rq);
}
配置查询的keys
private void configureKeys(Attributes keys) {
this.keys.addAll(keys);
}
查询
private void query() throws IOException, InterruptedException {
query(keys);
}
private void query(Attributes keys) throws IOException, InterruptedException {
DimseRSPHandler rspHandler = new DimseRSPHandler(as.nextMessageID()) {
int cancelAfter = FindSCU.this.cancelAfter;
int numMatches;
@Override
public void onDimseRSP(Association as, Attributes cmd, Attributes data) {
super.onDimseRSP(as, cmd, data);
int status = cmd.getInt(Tag.Status, -1);
if (Status.isPending(status)) {
FindSCU.this.printResult(data);
++numMatches;
if (cancelAfter != 0 && numMatches >= cancelAfter)
try {
cancel(as);
cancelAfter = 0;
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
query(keys, rspHandler);
}
打印查询的结果
需要其他信息,继续data.getString(Tag.XX)前提是查询的时候配置了查询展示的Tag
private void printResult(Attributes data) {
String SpecificCharacterSet = data.getString(Tag.SpecificCharacterSet);
// 设置编码,防止乱码
if (StringUtils.isBlank(SpecificCharacterSet)) {
data.setString(Tag.SpecificCharacterSet, VR.CS, "GB18030");
data.setString(Tag.SpecificCharacterSet, VR.PN, "GB18030");
}
// 打印查询结果
System.out.println("---------- patient -----------");
System.out.println("PatientID : " + data.getString(Tag.PatientID)); // 患者唯一ID
System.out.println("PatientName : " + data.getString(Tag.PatientName)); // 患者姓名
System.out.println("PatientBirthDate : " + data.getDate(Tag.PatientBirthDate)); // 出生日期
System.out.println("PatientSex : " + data.getString(Tag.PatientSex)); // 患者性别
System.out.println("PatientWeight : " + data.getString(Tag.PatientWeight)); // 患者体重
System.out.println("PregnancyStatus : " + data.getString(Tag.PregnancyStatus)); // 怀孕状态
System.out.println("InstitutionName : " + data.getString(Tag.InstitutionName)); // 医院名称
System.out.println();
System.out.println("----------- study ------------");
System.out.println("AccessionNumber : " + data.getString(Tag.AccessionNumber)); // 检查号:RIS的生成序号,用于标识做检查的次序
System.out.println("StudyID : " + data.getString(Tag.StudyID)); // 检查ID
System.out.println("StudyInstanceUID : " + data.getString(Tag.StudyInstanceUID)); // Study Instance UID 检查实例号,用于标识检查的唯一ID
System.out.println("StudyDate : " + data.getDate(Tag.StudyDate)); // 检查日期时间
System.out.println("Modality : " + data.getString(Tag.Modality)); // 检查类型
System.out.println("ModalitiesInStudy : " + data.getString(Tag.ModalitiesInStudy)); // 检查类型
System.out.println("PatientAge : " + data.getString(Tag.PatientAge)); // 做检查时刻的患者年龄
System.out.println("StudyDescription : " + data.getString(Tag.StudyDescription)); // 检查描述信息
System.out.println("BodyPartExamined : " + data.getString(Tag.BodyPartExamined)); // 检查部位
System.out.println("ProtocolName : " + data.getString(Tag.ProtocolName)); // 协议名称
System.out.println();
}
配置开放查询方法
public static void matchingKeys(Attributes attrs) {
try {
FindSCU main = new FindSCU();
configureConnect(main.remote, main.rq); // 设置连接ip和端口 (远程)
main.remote.setTlsProtocols(main.conn.getTlsProtocols()); // 设置Tls协议
main.remote.setTlsCipherSuites(main.conn.getTlsCipherSuites());
configureServiceClass(main); // 设置Information Model
configureRetrieve(main); // 设置检索级别
configureCancel(main); // 配置 --cancel
main.setPriority(priorityOf()); // 设置优先级
ExecutorService executorService = Executors.newSingleThreadExecutor(); // 单线程化线程池
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); // 定时任务
main.device.setExecutor(executorService);
main.device.setScheduledExecutor(scheduledExecutorService);
try {
main.open(); // 打开链接
main.configureKeys(attrs);
main.query(); // 查询
} finally {
main.close();
executorService.shutdown();
scheduledExecutorService.shutdown();
}
} catch (ParseException | InterruptedException | IncompatibleConnectionException | GeneralSecurityException
| IOException e) {
e.printStackTrace();
}
}
6.messages.properties配置
#配置连接
title=DCM4CHEE
host=192.168.100.55
port=11112
#specifies Information Model. Supported names: PatientRoot, StudyRoot, PatientStudyOnly,
#MWL, UPSPull, UPSWatch, UPSQuery, HangingProtocol or ColorPalette. If no Information Model is specified,
#StudyRoot will be used.
model=
invalid-model-name={0} is not a supported Information Model name
#specifies retrieve level. Use STUDY for PatientRoot, StudyRoot, PatientStudyOnly by default.
level=
#cancel the query request after the receive of the specified number of matches.
cancel=
xml=write received matches as XML Infoset specified in DICOM Part 19
xsl=apply specified XSLT stylesheet to XML representation of received matches; implies -X
#优先级 默认0
prior-high=
prior-low=
7.测试
@Test
void findscuTest() {
Attributes attrs = new Attributes(1);
// 查询条件 相当于命令 findscu -c DCM4CHEE@192.168.100.55:11112 -m ModalitiesInStudy=MR
attrs.setString(Tag.ModalitiesInStudy, VR.CS, "MR");
// 查询展示的信息
attrs.setString(Tag.PatientID, VR.LO);
attrs.setString(Tag.PatientName, VR.PN);
attrs.setString(Tag.PatientBirthDate, VR.DA);
attrs.setString(Tag.PatientSex, VR.CS);
attrs.setString(Tag.PatientWeight, VR.DS);
attrs.setString(Tag.PregnancyStatus, VR.US);
attrs.setString(Tag.InstitutionName, VR.LO);
attrs.setString(Tag.AccessionNumber, VR.SH);
attrs.setString(Tag.StudyID, VR.SH);
attrs.setString(Tag.StudyInstanceUID, VR.UI);
attrs.setString(Tag.StudyDate, VR.DA);
attrs.setString(Tag.Modality, VR.CS);
attrs.setString(Tag.PatientAge, VR.AS);
attrs.setString(Tag.StudyDescription, VR.LO);
attrs.setString(Tag.BodyPartExamined, VR.CS);
attrs.setString(Tag.ProtocolName, VR.LO);
// 查询
FindSCU.matchingKeys(attrs);
}
查询结果
查询成功
抽空补充findscp模拟pacs服务器接收findscu发送的c-find请求