日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java dcm4che findscu实现workList通讯——客户端SCU

發(fā)布時(shí)間:2023/12/9 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java dcm4che findscu实现workList通讯——客户端SCU 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

全網(wǎng)實(shí)現(xiàn)workList服務(wù)的,要么是基于C++的DCMTK、要么是基于C#的fo-dicom。想用dcm4che實(shí)現(xiàn) 找了好幾個(gè)月都沒有一個(gè)例子。無奈只能通過DCMTK和fo-dicom 實(shí)現(xiàn)方式并查看dcm4che源碼自己實(shí)現(xiàn)了。經(jīng)過不懈的努力總算是實(shí)現(xiàn)了并實(shí)際跟設(shè)備測(cè)試成功!

首先得先了解 DICOM worklist工作原理?

一、關(guān)于Worklist

在RIS與PACS的系統(tǒng)集成中。Wordlist的連接bai為其主要工作之一。Wordlist成像設(shè)備工作列表,它是DICOM協(xié)議中眾多服務(wù)類別中的一個(gè).它的功能是實(shí)現(xiàn)設(shè)備操作臺(tái)與登記臺(tái)之間的通訊,完成成像設(shè)備和信息系統(tǒng)的集成.稱為BASIC WORKLIST MANAGEMENT SERVICE(簡(jiǎn)稱Worklist)。

二、DICOM標(biāo)準(zhǔn)中與Worklist相關(guān)的一些基本概念

配置影像檢查設(shè)備(Modality)的Worklist首先要閱讀該設(shè)備的“DICOM 一致性聲明(DICOM Conformance Statement)”中關(guān)于Worklist的部分,了解設(shè)備對(duì)Worklist的支持程度。而熟悉以下基本概念則有助于閱讀DICOM Conformance Statement:

1、VR(Value Representation):描述了數(shù)據(jù)元素的種類(字符串、數(shù)字、日期等)以及這些值的格式。在DICOM標(biāo)準(zhǔn)第五部分Data Structures and Encoding的第25頁中列出了所有的VR。

2、Data Set(數(shù)據(jù)集):一個(gè)數(shù)據(jù)集表示了一個(gè)DICOM對(duì)象,它進(jìn)一步由Data Element(數(shù)據(jù)元素)組成。而數(shù)據(jù)元素包括了tag(唯一的)、值的長(zhǎng)度以及值。數(shù)據(jù)元素中可能包含VR。

3、 數(shù)據(jù)元素類型:一個(gè)數(shù)據(jù)元素是否在一個(gè)數(shù)據(jù)集中出現(xiàn),取決于該數(shù)據(jù)元素的類型。

4、 AE Title:AE Title(Application Entity Title)是配置影像檢查設(shè)備DICOM服務(wù)(Worklist、Storage、Print等)必不可少的參數(shù)之一。對(duì)于某一臺(tái)影像檢查設(shè)備,其各個(gè)DICOM服務(wù)可以對(duì)應(yīng)不同的AE Title,當(dāng)然這些DICOM服務(wù)也可以對(duì)應(yīng)同一個(gè)AE Title。AE Title是一個(gè)字符串,但是這個(gè)字符串在我們要配置的RIS/PACS系統(tǒng)的網(wǎng)絡(luò)中必須是唯一的。因此,AE Title是這個(gè)網(wǎng)絡(luò)中某一個(gè)(或幾個(gè))DICOM服務(wù)的唯一標(biāo)識(shí)。

三、DICOM的Worklist實(shí)現(xiàn)的功能

fz2841585:從RIS或者其他系統(tǒng)下載病人信息,以免重復(fù)登記。

0753zhongwei:Worklist只是一個(gè)傳輸協(xié)議,DICOM的Worklist其實(shí)就是C-FIND服務(wù),有點(diǎn)類似于Query/Retrieve,SCU在C-FIND命令集后面加上一些查詢字段,SCP把查詢結(jié)果放在C-FIND-RSP后面返回去。

tks1000:在CT或MR等工作站上,如果沒有Worklist功能,新檢查一個(gè)病人的時(shí)候,要輸入病人全部的基本信息,這樣比較麻煩,而且容易出錯(cuò)。有了WorkList功能后,可以直接從服務(wù)器上讀取病人的基本信息,不用輸入,而且不易出錯(cuò)。實(shí)質(zhì)上還是C-FIND,不過需要MPPS等的支持。

chaoran898:DICOM的MWL是一種接口協(xié)議,至于怎樣查數(shù)據(jù),那是coding實(shí)現(xiàn)的事情,MWL只負(fù)責(zé)把找到的數(shù)據(jù)按DICOM標(biāo)準(zhǔn)傳出去。

xiaoyilong19:我做了多臺(tái)設(shè)備的Worklist,深有體會(huì):如果設(shè)備廠家不同的話,Worklist服務(wù)端程序就要調(diào)試一番,才能讓返回的數(shù)據(jù)在對(duì)方設(shè)備工作站上顯示出來,否則就是出現(xiàn)各種情況。亂碼還比較簡(jiǎn)單處理,就是怕對(duì)方什么應(yīng)答都沒有。實(shí)際上就是,請(qǐng)求,返回請(qǐng)求,和cs服務(wù)架構(gòu)一樣。

四、Worklist在Pacs中的作用與的工作原理

五、 基本設(shè)計(jì)概念和處理流程

xuyuansheng:正常的流程是,病人在HIS上注冊(cè),經(jīng)hl7消息傳至RIS,RIS上便有了病人的登記信息。做檢查時(shí),成像設(shè)備通過DICOM Worklist來從RIS上取得需做檢查的病人列表,選擇后做檢查。檢查完成后,圖像便可以傳到PACS中進(jìn)行存儲(chǔ)。在這個(gè)過程中,病人信息僅在HIS端輸入一遍,但它流經(jīng)RIS,Modality以及PACS。可以節(jié)省時(shí)間,減少錯(cuò)誤,規(guī)范流程,互聯(lián)互通,形成數(shù)據(jù)共享。理想的情況下,讓醫(yī)生專注于檢查及診斷,而縮短的時(shí)間,也會(huì)提高病人的滿意度。

六、總結(jié)
看到這里后大概知道 workList 其實(shí)就是一個(gè)客戶端(SCU)發(fā)起C-Find請(qǐng)求 服務(wù)端(SCP)將這些結(jié)果按照DICOM協(xié)議返回對(duì)應(yīng)的字段組合即可

接下來就該查看DCM4CHE源碼了

我們現(xiàn)在知道了workList 其實(shí)就是C-Find請(qǐng)求 我們就在dcm4che源碼里找關(guān)于c-find的一切內(nèi)容了!

原來dcm4che源碼中dcm4che-tool 有個(gè)dcm4che-tool-findscu



很是驚喜 總算找到個(gè)入口了

甚至還有命令示例,NICE!
例子:

findscu -c DCMQRSCP@localhost:11112 -m PatientName=Doe^John -m StudyDate=20110510- -m ModalitiesInStudy=CT

github上對(duì)各個(gè)命令都有解釋,這里簡(jiǎn)單解釋一下這個(gè)例子

-c 代表遠(yuǎn)程連接

DCMQRSCP是指dcm4che服務(wù)的AETitle

localhost是指dcm4che服務(wù)的ip地址

11112是指dcm4che服務(wù)的端口

-m PatientName=Doe^John 代表查詢患者姓名是Doe^John

-m StudyDate=20110510 代表查詢患者檢查時(shí)間是20110510

-m ModalitiesInStudy=CT 代表查詢患者的模態(tài)是CT

命令也支持xml文件查詢和結(jié)果導(dǎo)出xml

甚至dcm4chee 文件里有執(zhí)行findscu 命令腳本!是不是感覺離成功近了一大步~~
先試試腳本命令
(本地局域網(wǎng)已經(jīng)部署好了 dcm4chee-web 可以直接把它當(dāng)作worklist scp)

確實(shí)可以建立了通訊并且可以查詢 那接下來就從 FindSCU.java源碼下功夫了

充分閱讀源碼后 將源碼進(jìn)行改造

直擊源碼里的main 方法

public static void main(String[] args) {try {CommandLine cl = parseComandLine(args);//解析命令FindSCU main = new FindSCU();CLIUtils.configureConnect(main.remote, main.rq, cl); 設(shè)置連接ip和端口 CLIUtils.configureBind(main.conn, main.ae, cl);CLIUtils.configure(main.conn, cl);main.remote.setTlsProtocols(main.conn.getTlsProtocols());// 設(shè)置Tls協(xié)議main.remote.setTlsCipherSuites(main.conn.getTlsCipherSuites());configureServiceClass(main, cl);configureKeys(main, cl);configureOutput(main, cl);// 設(shè)置檢索級(jí)別configureCancel(main, cl);// 配置 --cancelmain.setPriority(CLIUtils.priorityOf(cl));ExecutorService executorService =Executors.newSingleThreadExecutor();ScheduledExecutorService scheduledExecutorService =Executors.newSingleThreadScheduledExecutor();main.device.setExecutor(executorService);main.device.setScheduledExecutor(scheduledExecutorService);try {main.open();// 打開鏈接List<String> argList = cl.getArgList();if (argList.isEmpty())main.query();// 查詢 這里是重點(diǎn)elsefor (String arg : argList)main.query(new File(arg));} finally {main.close();executorService.shutdown();scheduledExecutorService.shutdown();}} catch (ParseException e) {System.err.println("findscu: " + e.getMessage());System.err.println(rb.getString("try"));System.exit(2);} catch (Exception e) {System.err.println("findscu: " + e.getMessage());e.printStackTrace();System.exit(2);}}

具體看query 方法

public void query( DimseRSPHandler rspHandler) throws IOException, InterruptedException {query(keys, rspHandler);}private void query(Attributes keys, DimseRSPHandler rspHandler) throws IOException, InterruptedException {as.cfind(model.cuid, priority, keys, null, rspHandler);}

主要看懂源碼的這兩個(gè)地方 大概實(shí)現(xiàn)findscu 就有思路了

需要的maven 包

<dependency><groupId>org.dcm4che</groupId><artifactId>dcm4che-core</artifactId><version>5.16.1</version></dependency><dependency><groupId>org.dcm4che</groupId><artifactId>dcm4che-net</artifactId><version>5.16.1</version></dependency><dependency><groupId>org.dcm4che.tool</groupId><artifactId>dcm4che-tool-common</artifactId><version>5.16.1</version></dependency><dependency><groupId>commons-cli</groupId><artifactId>commons-cli</artifactId><version>1.4</version></dependency><dependency><groupId>org.dcm4che</groupId><artifactId>dcm4che-imageio</artifactId><version>5.16.1</version></dependency><!-- lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>org.dcm4che</groupId><artifactId>dcm4che-imageio-opencv</artifactId><version>5.16.1</version><scope>runtime</scope></dependency></dependencies>

改造后的findscu .java

import com.javasm.entity.enums.InformationModel; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.dcm4che3.data.*; import org.dcm4che3.net.*; import org.dcm4che3.net.pdu.AAssociateRQ; import org.dcm4che3.net.pdu.ExtendedNegotiation; import org.dcm4che3.net.pdu.PresentationContext; import org.dcm4che3.util.SafeClose;import java.io.*; import java.security.GeneralSecurityException; import java.text.MessageFormat; import java.util.EnumSet; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService;public class FindSCU {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 final Device device = new Device("findscu");private final ApplicationEntity ae = new ApplicationEntity("FINDSCU");private final Connection conn = new Connection();private final Connection remote = new Connection();private final AAssociateRQ rq = new AAssociateRQ();private int priority;private int cancelAfter;private InformationModel model;private Attributes keys = new Attributes();private OutputStream out;private Association as;private FindSCU() {device.addConnection(conn);device.addApplicationEntity(ae);ae.addConnection(conn);}private void setPriority(int priority) {this.priority = priority;}private void setInformationModel(InformationModel model, String[] tss, EnumSet<QueryOption> queryOptions) {this.model = model;rq.addPresentationContext(new PresentationContext(1, model.cuid, tss));if (!queryOptions.isEmpty()) {model.adjustQueryOptions(queryOptions);rq.addExtendedNegotiation(new ExtendedNegotiation(model.cuid, QueryOption.toExtendedNegotiationInformation(queryOptions)));}if (model.level != null)addLevel(model.level);}private void addLevel(String s) {keys.setString(Tag.QueryRetrieveLevel, VR.CS, s);}private void setCancelAfter(int cancelAfter) {this.cancelAfter = cancelAfter;}private static EnumSet<QueryOption> queryOptionsOf() {return EnumSet.noneOf(QueryOption.class);}private static void configureCancel(FindSCU main) {if (StringUtils.isNotBlank(rb.getString("cancel"))) {main.setCancelAfter(Integer.parseInt(rb.getString("cancel")));}}private static void configureRetrieve(FindSCU main) {if (StringUtils.isNotBlank(rb.getString("level"))) {// Retrieve是指SCU通過Query 拿到信息后,要求對(duì)方根據(jù)請(qǐng)求級(jí)別 (Patient/Study/Series/Image) 發(fā)送影像給己方。// 默認(rèn)Patientmain.addLevel(rb.getString("level"));}}/*** 設(shè)置Information Model** @param main* @throws ParseException*/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為空,默認(rèn)StudyRootreturn 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 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);}private void close() throws IOException, InterruptedException {if (as != null && as.isReadyForDataTransfer()) {as.waitForOutstandingRSP();as.release();}SafeClose.close(out);out = null;}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;@Overridepublic void onDimseRSP(Association as, Attributes cmd, Attributes data) {super.onDimseRSP(as, cmd, data);int status = cmd.getInt(Tag.Status, -1);FindSCU.this.printResult(data);if (Status.isPending(status)) {++numMatches;if (cancelAfter != 0 && numMatches >= cancelAfter)try {cancel(as);cancelAfter = 0;} catch (IOException e) {e.printStackTrace();}}}};query(keys, rspHandler);}private void query(Attributes keys, DimseRSPHandler rspHandler) throws IOException, InterruptedException {as.cfind(model.cuid, priority, keys, null, rspHandler);}private void printResult(Attributes data) {String SpecificCharacterSet = data.getString(Tag.SpecificCharacterSet);// 設(shè)置編碼,防止亂碼if (StringUtils.isBlank(SpecificCharacterSet)) {data.setString(Tag.SpecificCharacterSet, VR.CS, "GB18030");data.setString(Tag.SpecificCharacterSet, VR.PN, "GB18030");}// 打印查詢結(jié)果System.out.println("---------- patient -----------");System.out.println("PatientID : " + data.getString(Tag.PatientID)); // 患者唯一IDSystem.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)); // 懷孕狀態(tài)System.out.println("InstitutionName : " + data.getString(Tag.InstitutionName)); // 醫(yī)院名稱System.out.println();System.out.println("----------- study ------------");System.out.println("AccessionNumber : " + data.getString(Tag.AccessionNumber)); // 檢查號(hào):RIS的生成序號(hào),用于標(biāo)識(shí)做檢查的次序System.out.println("StudyID : " + data.getString(Tag.StudyID)); // 檢查IDSystem.out.println("StudyInstanceUID : " + data.getString(Tag.StudyInstanceUID)); // Study Instance UID 檢查實(shí)例號(hào),用于標(biāo)識(shí)檢查的唯一IDSystem.out.println("StudyDate : " + data.getDate(Tag.StudyDate)); // 檢查日期時(shí)間System.out.println("Modality : " + data.getString(Tag.Modality)); // 檢查類型System.out.println("ModalitiesInStudy : " + data.getString(Tag.ModalitiesInStudy)); // 檢查類型System.out.println("PatientAge : " + data.getString(Tag.PatientAge)); // 做檢查時(shí)刻的患者年齡System.out.println("StudyDescription : " + data.getString(Tag.StudyDescription)); // 檢查描述信息System.out.println("BodyPartExamined : " + data.getString(Tag.BodyPartExamined)); // 檢查部位System.out.println("ProtocolName : " + data.getString(Tag.ProtocolName)); // 協(xié)議名稱System.out.println();}/*** 配置遠(yuǎn)程連接** @param conn Connection* @param rq AAssociateRQ*/private static void configureConnect(Connection conn, AAssociateRQ rq) throws ParseException {// 獲取title屬性值String title = "AEtitle";//修改成你的if (StringUtils.isBlank(title)) {throw new ParseException("title cannot be missing");}// 設(shè)置AE titlerq.setCalledAET(title);// 讀取host和port屬性值String host = "127.0.0.1";//修改成你的String port = "8080";//修改成你的if (StringUtils.isBlank(host) || StringUtils.isBlank(port)) {throw new ParseException("host or port cannot be missing");}// 設(shè)置host和porconn.setHostname(host);conn.setPort(Integer.parseInt(port));}public static void matchingKeys(Attributes attrs) {try {FindSCU main = new FindSCU();configureConnect(main.remote, main.rq); // 設(shè)置連接ip和端口 (遠(yuǎn)程)main.remote.setTlsProtocols(main.conn.getTlsProtocols()); // 設(shè)置Tls協(xié)議main.remote.setTlsCipherSuites(main.conn.getTlsCipherSuites());configureServiceClass(main); // 設(shè)置Information ModelconfigureRetrieve(main); // 設(shè)置檢索級(jí)別configureCancel(main); // 配置 --cancelmain.setPriority(priorityOf()); // 設(shè)置優(yōu)先級(jí)ExecutorService executorService = Executors.newSingleThreadExecutor(); // 單線程化線程池ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); // 定時(shí)任務(wù)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();}}public static void main(String[] args) {Attributes attrs = new Attributes();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);} }

再上個(gè)成功查詢的圖

總結(jié)

以上是生活随笔為你收集整理的java dcm4che findscu实现workList通讯——客户端SCU的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。