java dcm4che findscu实现workList通讯——客户端SCU
全網實現workList服務的,要么是基于C++的DCMTK、要么是基于C#的fo-dicom。想用dcm4che實現 找了好幾個月都沒有一個例子。無奈只能通過DCMTK和fo-dicom 實現方式并查看dcm4che源碼自己實現了。經過不懈的努力總算是實現了并實際跟設備測試成功!
首先得先了解 DICOM worklist工作原理?
一、關于Worklist
在RIS與PACS的系統集成中。Wordlist的連接bai為其主要工作之一。Wordlist成像設備工作列表,它是DICOM協議中眾多服務類別中的一個.它的功能是實現設備操作臺與登記臺之間的通訊,完成成像設備和信息系統的集成.稱為BASIC WORKLIST MANAGEMENT SERVICE(簡稱Worklist)。
二、DICOM標準中與Worklist相關的一些基本概念
配置影像檢查設備(Modality)的Worklist首先要閱讀該設備的“DICOM 一致性聲明(DICOM Conformance Statement)”中關于Worklist的部分,了解設備對Worklist的支持程度。而熟悉以下基本概念則有助于閱讀DICOM Conformance Statement:
1、VR(Value Representation):描述了數據元素的種類(字符串、數字、日期等)以及這些值的格式。在DICOM標準第五部分Data Structures and Encoding的第25頁中列出了所有的VR。
2、Data Set(數據集):一個數據集表示了一個DICOM對象,它進一步由Data Element(數據元素)組成。而數據元素包括了tag(唯一的)、值的長度以及值。數據元素中可能包含VR。
3、 數據元素類型:一個數據元素是否在一個數據集中出現,取決于該數據元素的類型。
4、 AE Title:AE Title(Application Entity Title)是配置影像檢查設備DICOM服務(Worklist、Storage、Print等)必不可少的參數之一。對于某一臺影像檢查設備,其各個DICOM服務可以對應不同的AE Title,當然這些DICOM服務也可以對應同一個AE Title。AE Title是一個字符串,但是這個字符串在我們要配置的RIS/PACS系統的網絡中必須是唯一的。因此,AE Title是這個網絡中某一個(或幾個)DICOM服務的唯一標識。
三、DICOM的Worklist實現的功能
fz2841585:從RIS或者其他系統下載病人信息,以免重復登記。
0753zhongwei:Worklist只是一個傳輸協議,DICOM的Worklist其實就是C-FIND服務,有點類似于Query/Retrieve,SCU在C-FIND命令集后面加上一些查詢字段,SCP把查詢結果放在C-FIND-RSP后面返回去。
tks1000:在CT或MR等工作站上,如果沒有Worklist功能,新檢查一個病人的時候,要輸入病人全部的基本信息,這樣比較麻煩,而且容易出錯。有了WorkList功能后,可以直接從服務器上讀取病人的基本信息,不用輸入,而且不易出錯。實質上還是C-FIND,不過需要MPPS等的支持。
chaoran898:DICOM的MWL是一種接口協議,至于怎樣查數據,那是coding實現的事情,MWL只負責把找到的數據按DICOM標準傳出去。
xiaoyilong19:我做了多臺設備的Worklist,深有體會:如果設備廠家不同的話,Worklist服務端程序就要調試一番,才能讓返回的數據在對方設備工作站上顯示出來,否則就是出現各種情況。亂碼還比較簡單處理,就是怕對方什么應答都沒有。實際上就是,請求,返回請求,和cs服務架構一樣。
四、Worklist在Pacs中的作用與的工作原理
五、 基本設計概念和處理流程
xuyuansheng:正常的流程是,病人在HIS上注冊,經hl7消息傳至RIS,RIS上便有了病人的登記信息。做檢查時,成像設備通過DICOM Worklist來從RIS上取得需做檢查的病人列表,選擇后做檢查。檢查完成后,圖像便可以傳到PACS中進行存儲。在這個過程中,病人信息僅在HIS端輸入一遍,但它流經RIS,Modality以及PACS。可以節省時間,減少錯誤,規范流程,互聯互通,形成數據共享。理想的情況下,讓醫生專注于檢查及診斷,而縮短的時間,也會提高病人的滿意度。
六、總結
看到這里后大概知道 workList 其實就是一個客戶端(SCU)發起C-Find請求 服務端(SCP)將這些結果按照DICOM協議返回對應的字段組合即可
接下來就該查看DCM4CHE源碼了
我們現在知道了workList 其實就是C-Find請求 我們就在dcm4che源碼里找關于c-find的一切內容了!
原來dcm4che源碼中dcm4che-tool 有個dcm4che-tool-findscu
很是驚喜 總算找到個入口了
甚至還有命令示例,NICE!
例子:
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
甚至dcm4chee 文件里有執行findscu 命令腳本!是不是感覺離成功近了一大步~~
先試試腳本命令
(本地局域網已經部署好了 dcm4chee-web 可以直接把它當作worklist scp)
確實可以建立了通訊并且可以查詢 那接下來就從 FindSCU.java源碼下功夫了
充分閱讀源碼后 將源碼進行改造
直擊源碼里的main 方法
public static void main(String[] args) {try {CommandLine cl = parseComandLine(args);//解析命令FindSCU main = new FindSCU();CLIUtils.configureConnect(main.remote, main.rq, cl); 設置連接ip和端口 CLIUtils.configureBind(main.conn, main.ae, cl);CLIUtils.configure(main.conn, cl);main.remote.setTlsProtocols(main.conn.getTlsProtocols());// 設置Tls協議main.remote.setTlsCipherSuites(main.conn.getTlsCipherSuites());configureServiceClass(main, cl);configureKeys(main, cl);configureOutput(main, cl);// 設置檢索級別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();// 查詢 這里是重點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);}主要看懂源碼的這兩個地方 大概實現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 拿到信息后,要求對方根據請求級別 (Patient/Study/Series/Image) 發送影像給己方。// 默認Patientmain.addLevel(rb.getString("level"));}}/*** 設置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為空,默認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);// 設置編碼,防止亂碼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)); // 患者唯一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)); // 懷孕狀態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)); // 檢查IDSystem.out.println("StudyInstanceUID : " + data.getString(Tag.StudyInstanceUID)); // Study Instance UID 檢查實例號,用于標識檢查的唯一IDSystem.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();}/*** 配置遠程連接** @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");}// 設置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");}// 設置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); // 設置連接ip和端口 (遠程)main.remote.setTlsProtocols(main.conn.getTlsProtocols()); // 設置Tls協議main.remote.setTlsCipherSuites(main.conn.getTlsCipherSuites());configureServiceClass(main); // 設置Information ModelconfigureRetrieve(main); // 設置檢索級別configureCancel(main); // 配置 --cancelmain.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();}}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);} }再上個成功查詢的圖
總結
以上是生活随笔為你收集整理的java dcm4che findscu实现workList通讯——客户端SCU的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python pack unpack_g
- 下一篇: linux服务器防病毒,Linux系统中