本文主要描述,dicom通信的scu,scp的c-echo、c-store、c-find、c-move的使用。
DicomService
IDicomServiceProvider
IDicomCStoreProvider
IDicomCEchoProvider
IDicomCFindProvider
IDicomCMoveProvider
IDicomTransformRule
(1)c-echo
客户端代码:
1 DicomClient client = new DicomClient(); 2 client.AssociationAccepted += Client_AssociationAccepted; 3 client.AssociationRejected += Client_AssociationRejected; 4 client.AssociationReleased += Client_AssociationReleased; 5 client.NegotiateAsyncOps(); 6 client.AddRequest(new DicomCEchoRequest()); 7 8 9 //client.Send 10 client.SendAsync(ae_dest.ip, 11 ae_dest.port, 12 false, 13 ae_src.name,//SCU 14 ae_dest.name//ANY-SCP 15 );
1 private void Client_AssociationReleased(object sender, EventArgs e) 2 { 3 //string log = $"Client_AssociationReleased --> {e}"; 4 //AppendLog(log); 5 } 6 7 private void Client_AssociationRejected(object sender, AssociationRejectedEventArgs e) 8 { 9 string log = $"Client_AssociationRejected --> {e}"; 10 AppendLog("echo ng"); 11 } 12 13 private void Client_AssociationAccepted(object sender, AssociationAcceptedEventArgs e) 14 { 15 string log = $"Client_AssociationAccepted --> {e}"; 16 AppendLog("echo ok"); 17 }
(2)c-store
客户端代码:
1 private void SendOne(Switch_Dicom_Image entity) 2 { 3 string fileReal = Path.Combine(AppSettings.dicom_path_root, entity.FilePath); 4 5 var destServer = dao.GetOneDestSwitchAETitle(entity.SrcAETitle); 6 7 string aet_current = AppSettings.scp_aet; 8 9 string[] files = new string[] { fileReal }; 10 11 int expected = files.Length; 12 var actual = 0; 13 14 var client = new DicomClient(); 15 client.NegotiateAsyncOps(expected, 1); 16 17 foreach (string file in files) 18 { 19 try 20 { 21 Log($"正在发送文件“{file}”"); 22 23 DicomCStoreRequest req = new DicomCStoreRequest(file); 24 req.OnResponseReceived = (req2, res) => 25 { 26 try 27 { 28 Interlocked.Increment(ref actual); 29 30 string log = $"OnResponseReceived --> 【{actual}】 {res.Status} {req2.SOPInstanceUID.UID}"; 31 Log(log); 32 33 if (res.Status == DicomStatus.Success) 34 { 35 using (var dbContext = new StudyProEntities()) 36 { 37 var record = dbContext.Switch_Dicom_Image.Where(one => one.ImageGUID == entity.ImageGUID).FirstOrDefault(); 38 record.SendStatus = 1; 39 record.SendCount = record.SendCount + 1; 40 record.SendTime = DateTime.Now; 41 int n = dbContext.SaveChanges(); 42 if (n > 0) 43 { 44 //将接受目录下的文件给删除 45 File.Delete(file); 46 } 47 48 }//end using 49 50 } 51 else 52 { 53 using (var dbContext = new StudyProEntities()) 54 { 55 var record = dbContext.Switch_Dicom_Image.Where(one => one.ImageGUID == entity.ImageGUID).FirstOrDefault(); 56 record.SendStatus = 2; 57 record.SendCount = record.SendCount + 1; 58 record.SendError = $"{res.Status}"; 59 int n = dbContext.SaveChanges(); 60 if (n > 0) 61 { 62 //失败不能删除文件 63 } 64 65 }//end using 66 } 67 } 68 catch (Exception ex) 69 { 70 LogHelper.Instance.Fatal(ex.ToString()); 71 } 72 73 }; 74 75 client.AddRequest(req); 76 77 //client.SendAsync( 78 client.Send( 79 destServer.IPAddress, 80 destServer.Port, 81 false, 82 aet_current,//SCU 83 destServer.AETitle,//ANY-SCP 84 timeout 85 ); 86 } 87 catch (Exception ex) 88 { 89 LogHelper.Instance.Fatal(ex.ToString()); 90 } 91 92 }//end foreach 93 }
服务端代码:
1 mActionLog?.Invoke("接收到待处理的 DicomCStoreRequest..."); 2 3 bool b = false; 4 5 string pPatientID = ""; 6 b = request.Dataset.TryGetValue<string>(DicomTag.PatientID, 0, out pPatientID); 7 if (!b) 8 { 9 throw new Exception("未能识别 PatientID"); 10 } 11 mActionLog?.Invoke($"pPatientID={pPatientID}"); 12 13 string pStudyInstanceUID = ""; 14 b = request.Dataset.TryGetValue<string>(DicomTag.StudyInstanceUID, 0, out pStudyInstanceUID); 15 if (!b) 16 { 17 throw new Exception("未能识别 StudyInstanceUID"); 18 } 19 mActionLog?.Invoke($"pStudyInstanceUID={pStudyInstanceUID}"); 20 21 string pSeriesInstanceUID = ""; 22 b = request.Dataset.TryGetValue<string>(DicomTag.SeriesInstanceUID, 0, out pSeriesInstanceUID); 23 if (!b) 24 { 25 throw new Exception("未能识别 SeriesInstanceUID"); 26 } 27 mActionLog?.Invoke($"pSeriesInstanceUID={pSeriesInstanceUID}"); 28 29 string pSOPInstanceUID = ""; 30 b = request.Dataset.TryGetValue<string>(DicomTag.SOPInstanceUID, 0, out pSOPInstanceUID); 31 if (!b) 32 { 33 throw new Exception("未能识别 pSOPInstanceUID"); 34 } 35 36 mActionLog?.Invoke($"pSOPInstanceUID={pSOPInstanceUID}"); 37 38 39 string file = ""; 40 41 string pathLocalCache = App.gPathLocalCache;//Path.Combine(Application.StartupPath, "Cache"); 42 43 string pathRelative = ""; 44 //pathRelative = $"{pStudyInstanceUID}/{pSeriesInstanceUID}/{pSOPInstanceUID}.dcm"; 45 pathRelative = $"{pStudyInstanceUID}/{pSOPInstanceUID}.dcm"; 46 47 file = Path.Combine(pathLocalCache, pathRelative); 48 49 var dir = Path.GetDirectoryName(file); 50 if (!Directory.Exists(dir)) 51 { 52 Directory.CreateDirectory(dir); 53 } 54 55 if (File.Exists(file)) 56 { 57 File.Delete(file); 58 } 59 60 request.File.Save(file);
(3)c-find
客户端代码:
1 public ListGetData(AEInfo ae, DicomCFindRequest dicomCFindRequest) 2 { 3 ManualResetEvent mre = new ManualResetEvent(false); 4 List list = new List (); 5 6 dicomCFindRequest.OnResponseReceived = 7 (DicomCFindRequest request, DicomCFindResponse response) => 8 { 9 Debug.WriteLine($"Status={response.Status}"); 10 11 if (response.Status == DicomStatus.Success 12 || response.Status == DicomStatus.ProcessingFailure) 13 { 14 mre.Set(); 15 return; 16 } 17 18 //输出值信息 19 response.ToString(true); 20 21 if (response.HasDataset) 22 { 23 24 list.Add(response.Dataset); 25 } 26 }; 27 28 //发起C-FIND-RQ,用A-ASSOCIATE服务建立DICOM实体双方之间的连接 29 var client = new DicomClient(); 30 //client.NegotiateAsyncOps(); 31 32 client.AddRequest(dicomCFindRequest); 33 34 client.Send(host: ae.ip,//127.0.0.1 35 port: ae.port, 36 useTls: false, 37 callingAe: local_aet,//SCU-AE 38 calledAe: ae.name//SCP-AE 39 ); 40 41 bool b = mre.WaitOne(1000 * 10); 42 if (!b) 43 { 44 MessageBox.Show("查询超时,请重试!"); 45 return null; 46 } 47 48 return list;
服务端代码:
1 DicomStatus status = DicomStatus.Success; 2 3 Listlist = new List (); 4 5 try 6 { 7 if (UserCustomCFindRequestHandle != null) 8 { 9 IList data = UserCustomCFindRequestHandle(request); 10 if (data != null) 11 { 12 LogHelper.Instance.Debug($"OnCFindRequest 结果的记录数为 {data.Count}"); 13 14 foreach (var one in data) 15 { 16 DicomCFindResponse rsp = new DicomCFindResponse(request, DicomStatus.Pending); 17 rsp.Dataset = one; 18 list.Add(rsp); 19 } 20 } 21 else 22 { 23 status = DicomStatus.QueryRetrieveOutOfResources; 24 } 25 } 26 } 27 catch (Exception ex) 28 { 29 LogHelper.Instance.Error(ex.ToString()); 30 list.Clear(); 31 status = DicomStatus.ProcessingFailure; 32 } 33 34 //DicomStatus.QueryRetrieveOutOfResources 35 36 list.Add(new DicomCFindResponse(request, status));
(4)c-move
客户端代码:
1 var requestCMove = new DicomCMoveRequest(ae_dest.name, studyInstanceUid, seriesInstanceUid, sopInstanceUid); 2 3 var id = requestCMove.MessageID; 4 5 requestCMove.OnResponseReceived = (DicomCMoveRequest request, DicomCMoveResponse response) => 6 { 7 string log = $"OnResponseReceived --> {response.Status} | Completed={ response.Completed }, Remaining={ response.Remaining }, Failures={ response.Failures }, Warnings={ response.Warnings }"; 8 this.AppendLog(log); 9 10 //sopInstanceUID 11 if (response.Status == DicomStatus.Pending) 12 { 13 if(response.Dataset!=null) 14 { 15 string key = response.Dataset.GetString(DicomTag.SOPInstanceUID); 16 if (!string.IsNullOrEmpty(key)) 17 { 18 mActionRun?.Invoke(key); 19 } 20 } 21 } 22 23 }; 24 25 var client = new DicomClient(); 26 client.AddRequest(requestCMove); 27 28 //client.Send 29 client.SendAsync(ae_dest.ip, 30 ae_dest.port, 31 false, 32 ae_src.name,//SCU-AE 33 ae_dest.name//SCP-AE 34 );
服务端代码:
1 string aet_current = this.Association.CalledAE; 2 string aet_remote = this.Association.CallingAE; 3 4 Sys_AETitle ae = UserCustomApplicationEntityTitleHandle(aet_remote); 5 6 7 //AE Title 长度不能太长,这个是最长的长度,比如:“xxx_client_tool_”,最长16个字符。 8 9 LogHelper.Instance.Debug($"根据{aet_remote}查找到的ae --> {JsonConvert.SerializeObject(ae)}"); 10 11 12 DicomStatus status = DicomStatus.Success; 13 IListlistResponse = new List (); 14 15 16 IList listFind; 17 18 //DicomClient client = new DicomClient(); 19 20 if (UserCustomCMoveRequestHandle != null) 21 { 22 listFind = UserCustomCMoveRequestHandle(request); 23 24 if (listFind != null 25 && listFind.Count > 0) 26 { 27 int len = listFind.Count; 28 29 LogHelper.Instance.Debug($"cmove-cstore给客户端文件数为 {len}"); 30 31 int nRemaining = len; 32 int nFailures = 0; 33 int nWarnings = 0; 34 int nCompleted = 0; 35 36 if (true) 37 { 38 DicomCMoveResponse responseCMove = new DicomCMoveResponse(request, DicomStatus.Pending); 39 responseCMove.Remaining = nRemaining; 40 responseCMove.Completed = nCompleted; 41 responseCMove.Warnings = nWarnings; 42 responseCMove.Failures = nFailures; 43 44 base.SendResponseAsync(responseCMove); 45 //SendResponse(responseCMove); 46 }//end if 47 48 49 foreach (var one in listFind) 50 { 51 try 52 { 53 string path = AppSettings.dicom_path_root; 54 55 string file = Path.Combine(path, one.DomainID, one.StudyDateTime.Value.ToString("yyyyMMdd"), one.SysStudyGUID, one.SOPInstanceUID + ".dcm"); 56 57 58 if (!File.Exists(file)) 59 { 60 lock (_objLock) 61 { 62 nFailures++; 63 } 64 65 throw new Exception($"文件不存在 {file}"); 66 } 67 68 DicomCStoreRequest dicomCStoreRequest = new DicomCStoreRequest(file); 69 //读取了dcm文件后,dicomCStoreRequest.Dataset的值将从file读取填充 70 //dicomCStoreRequest.Dataset.Add(DicomTag.XXX, XXX); 71 72 73 dicomCStoreRequest.OnResponseReceived = (rq, rs) => 74 { 75 LogHelper.Instance.Debug($"dicomCStoreRequest --> {rs.Status}"); 76 77 if (rs.Status == DicomStatus.Success) 78 { 79 lock (_objLock) 80 { 81 nCompleted++; 82 83 nRemaining = len - nFailures - nWarnings - nCompleted; 84 } 85 86 //-------------------------------------------------------------------- 87 if (true) 88 { 89 DicomCMoveResponse response = new DicomCMoveResponse(request, DicomStatus.Pending); 90 response.Remaining = nRemaining; 91 response.Completed = nCompleted; 92 response.Warnings = nWarnings; 93 response.Failures = nFailures; 94 95 96 //将一些信息返回给客户端,作为客户端确认相关操作使用 97 response.Dataset = new DicomDataset(); 98 response.Dataset.Add(DicomTag.SOPInstanceUID, one.SOPInstanceUID); 99 response.Dataset.Add(DicomTag.StudyInstanceUID, one.StudyInstanceUID); 100 response.Dataset.Add(DicomTag.SeriesInstanceUID, one.SeriesInstanceUID); 101 response.Dataset.Add(DicomTagVNA.CMoveServerFilePath, file); 102 103 base.SendResponseAsync(response); 104 //SendResponse(rsponse); 105 }//end if 106 //-------------------------------------------------------------------- 107 } 108 else 109 { 110 LogHelper.Instance.Debug($"cmove-cstore给客户端返回失败({rs.Status})"); 111 } 112 113 114 }; 115 116 117 try 118 { 119 LogHelper.Instance.Debug($"发送文件 --> {file}"); 120 121 DicomClient client = new DicomClient(); 122 client.AddRequest(dicomCStoreRequest); 123 124 client.Send( 125 ae.IPAddress, 126 ae.Port, 127 false, 128 aet_current, 129 aet_remote 130 ); 131 } 132 catch (Exception ex) 133 { 134 LogHelper.Instance.Debug("cmove发送给客户端失败 --> " + ex.ToString()); 135 throw ex; 136 } 137 138 } 139 catch (Exception ex) 140 { 141 Debug.WriteLine(ex.ToString()); 142 143 DicomCMoveResponse rs = new DicomCMoveResponse(request, DicomStatus.StorageStorageOutOfResources); 144 listResponse.Add(rs); 145 146 return listResponse; 147 148 } 149 finally 150 { 151 152 } 153 154 }//end foreach 155 156 listResponse.Add(new DicomCMoveResponse(request, DicomStatus.Success)); 157 return listResponse; 158 159 } 160 } 161 162 listResponse.Add(new DicomCMoveResponse(request, DicomStatus.NoSuchObjectInstance)); 163 return listResponse;