本文主要描述,dicom通信的scu,scp的c-echo、c-store、c-find、c-move的使用。
DicomService
IDicomServiceProvider
IDicomCStoreProvider
IDicomCEchoProvider
IDicomCFindProvider
IDicomCMoveProvider
IDicomTransformRule
(1)c-echo
客户端代码:
DicomClient client = new DicomClient();
client.AssociationAccepted += Client_AssociationAccepted;
client.AssociationRejected += Client_AssociationRejected;
client.AssociationReleased += Client_AssociationReleased;
client.NegotiateAsyncOps();
client.AddRequest(new DicomCEchoRequest()); //client.Send
client.SendAsync(ae_dest.ip,
ae_dest.port,
false,
ae_src.name,//SCU
ae_dest.name//ANY-SCP
);
private void Client_AssociationReleased(object sender, EventArgs e)
{
//string log = $"Client_AssociationReleased --> {e}";
//AppendLog(log);
} private void Client_AssociationRejected(object sender, AssociationRejectedEventArgs e)
{
string log = $"Client_AssociationRejected --> {e}";
AppendLog("echo ng");
} private void Client_AssociationAccepted(object sender, AssociationAcceptedEventArgs e)
{
string log = $"Client_AssociationAccepted --> {e}";
AppendLog("echo ok");
}
(2)c-store
客户端代码:
private void SendOne(Switch_Dicom_Image entity)
{
string fileReal = Path.Combine(AppSettings.dicom_path_root, entity.FilePath); var destServer = dao.GetOneDestSwitchAETitle(entity.SrcAETitle); string aet_current = AppSettings.scp_aet; string[] files = new string[] { fileReal }; int expected = files.Length;
var actual = ; var client = new DicomClient();
client.NegotiateAsyncOps(expected, ); foreach (string file in files)
{
try
{
Log($"正在发送文件“{file}”"); DicomCStoreRequest req = new DicomCStoreRequest(file);
req.OnResponseReceived = (req2, res) =>
{
try
{
Interlocked.Increment(ref actual); string log = $"OnResponseReceived --> 【{actual}】 {res.Status} {req2.SOPInstanceUID.UID}";
Log(log); if (res.Status == DicomStatus.Success)
{
using (var dbContext = new StudyProEntities())
{
var record = dbContext.Switch_Dicom_Image.Where(one => one.ImageGUID == entity.ImageGUID).FirstOrDefault();
record.SendStatus = ;
record.SendCount = record.SendCount + ;
record.SendTime = DateTime.Now;
int n = dbContext.SaveChanges();
if (n > )
{
//将接受目录下的文件给删除
File.Delete(file);
} }//end using }
else
{
using (var dbContext = new StudyProEntities())
{
var record = dbContext.Switch_Dicom_Image.Where(one => one.ImageGUID == entity.ImageGUID).FirstOrDefault();
record.SendStatus = ;
record.SendCount = record.SendCount + ;
record.SendError = $"{res.Status}";
int n = dbContext.SaveChanges();
if (n > )
{
//失败不能删除文件
} }//end using
}
}
catch (Exception ex)
{
LogHelper.Instance.Fatal(ex.ToString());
} }; client.AddRequest(req); //client.SendAsync(
client.Send(
destServer.IPAddress,
destServer.Port,
false,
aet_current,//SCU
destServer.AETitle,//ANY-SCP
timeout
);
}
catch (Exception ex)
{
LogHelper.Instance.Fatal(ex.ToString());
} }//end foreach
}
服务端代码:
mActionLog?.Invoke("接收到待处理的 DicomCStoreRequest..."); bool b = false; string pPatientID = "";
b = request.Dataset.TryGetValue<string>(DicomTag.PatientID, , out pPatientID);
if (!b)
{
throw new Exception("未能识别 PatientID");
}
mActionLog?.Invoke($"pPatientID={pPatientID}"); string pStudyInstanceUID = "";
b = request.Dataset.TryGetValue<string>(DicomTag.StudyInstanceUID, , out pStudyInstanceUID);
if (!b)
{
throw new Exception("未能识别 StudyInstanceUID");
}
mActionLog?.Invoke($"pStudyInstanceUID={pStudyInstanceUID}"); string pSeriesInstanceUID = "";
b = request.Dataset.TryGetValue<string>(DicomTag.SeriesInstanceUID, , out pSeriesInstanceUID);
if (!b)
{
throw new Exception("未能识别 SeriesInstanceUID");
}
mActionLog?.Invoke($"pSeriesInstanceUID={pSeriesInstanceUID}"); string pSOPInstanceUID = "";
b = request.Dataset.TryGetValue<string>(DicomTag.SOPInstanceUID, , out pSOPInstanceUID);
if (!b)
{
throw new Exception("未能识别 pSOPInstanceUID");
} mActionLog?.Invoke($"pSOPInstanceUID={pSOPInstanceUID}"); string file = ""; string pathLocalCache = App.gPathLocalCache;//Path.Combine(Application.StartupPath, "Cache"); string pathRelative = "";
//pathRelative = $"{pStudyInstanceUID}/{pSeriesInstanceUID}/{pSOPInstanceUID}.dcm";
pathRelative = $"{pStudyInstanceUID}/{pSOPInstanceUID}.dcm"; file = Path.Combine(pathLocalCache, pathRelative); var dir = Path.GetDirectoryName(file);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
} if (File.Exists(file))
{
File.Delete(file);
} request.File.Save(file);
(3)c-find
客户端代码:
public List<DicomDataset> GetData(AEInfo ae, DicomCFindRequest dicomCFindRequest)
{
ManualResetEvent mre = new ManualResetEvent(false);
List<DicomDataset> list = new List<DicomDataset>(); dicomCFindRequest.OnResponseReceived =
(DicomCFindRequest request, DicomCFindResponse response) =>
{
Debug.WriteLine($"Status={response.Status}"); if (response.Status == DicomStatus.Success
|| response.Status == DicomStatus.ProcessingFailure)
{
mre.Set();
return;
} //输出值信息
response.ToString(true); if (response.HasDataset)
{ list.Add(response.Dataset);
}
}; //发起C-FIND-RQ,用A-ASSOCIATE服务建立DICOM实体双方之间的连接
var client = new DicomClient();
//client.NegotiateAsyncOps(); client.AddRequest(dicomCFindRequest); client.Send(host: ae.ip,//127.0.0.1
port: ae.port,
useTls: false,
callingAe: local_aet,//SCU-AE
calledAe: ae.name//SCP-AE
); bool b = mre.WaitOne( * );
if (!b)
{
MessageBox.Show("查询超时,请重试!");
return null;
} return list;
服务端代码:
DicomStatus status = DicomStatus.Success; List<DicomCFindResponse> list = new List<DicomCFindResponse>(); try
{
if (UserCustomCFindRequestHandle != null)
{
IList<DicomDataset> data = UserCustomCFindRequestHandle(request);
if (data != null)
{
LogHelper.Instance.Debug($"OnCFindRequest 结果的记录数为 {data.Count}"); foreach (var one in data)
{
DicomCFindResponse rsp = new DicomCFindResponse(request, DicomStatus.Pending);
rsp.Dataset = one;
list.Add(rsp);
}
}
else
{
status = DicomStatus.QueryRetrieveOutOfResources;
}
}
}
catch (Exception ex)
{
LogHelper.Instance.Error(ex.ToString());
list.Clear();
status = DicomStatus.ProcessingFailure;
} //DicomStatus.QueryRetrieveOutOfResources list.Add(new DicomCFindResponse(request, status));
(4)c-move
客户端代码:
var requestCMove = new DicomCMoveRequest(ae_dest.name, studyInstanceUid, seriesInstanceUid, sopInstanceUid); var id = requestCMove.MessageID; requestCMove.OnResponseReceived = (DicomCMoveRequest request, DicomCMoveResponse response) =>
{
string log = $"OnResponseReceived --> {response.Status} | Completed={ response.Completed }, Remaining={ response.Remaining }, Failures={ response.Failures }, Warnings={ response.Warnings }";
this.AppendLog(log); //sopInstanceUID
if (response.Status == DicomStatus.Pending)
{
if(response.Dataset!=null)
{
string key = response.Dataset.GetString(DicomTag.SOPInstanceUID);
if (!string.IsNullOrEmpty(key))
{
mActionRun?.Invoke(key);
}
}
} }; var client = new DicomClient();
client.AddRequest(requestCMove); //client.Send
client.SendAsync(ae_dest.ip,
ae_dest.port,
false,
ae_src.name,//SCU-AE
ae_dest.name//SCP-AE
);
服务端代码:
string aet_current = this.Association.CalledAE;
string aet_remote = this.Association.CallingAE; Sys_AETitle ae = UserCustomApplicationEntityTitleHandle(aet_remote); //AE Title 长度不能太长,这个是最长的长度,比如:“xxx_client_tool_”,最长16个字符。 LogHelper.Instance.Debug($"根据{aet_remote}查找到的ae --> {JsonConvert.SerializeObject(ae)}"); DicomStatus status = DicomStatus.Success;
IList<DicomCMoveResponse> listResponse = new List<DicomCMoveResponse>(); IList<CMoveReturnInfo> listFind; //DicomClient client = new DicomClient(); if (UserCustomCMoveRequestHandle != null)
{
listFind = UserCustomCMoveRequestHandle(request); if (listFind != null
&& listFind.Count > )
{
int len = listFind.Count; LogHelper.Instance.Debug($"cmove-cstore给客户端文件数为 {len}"); int nRemaining = len;
int nFailures = ;
int nWarnings = ;
int nCompleted = ; if (true)
{
DicomCMoveResponse responseCMove = new DicomCMoveResponse(request, DicomStatus.Pending);
responseCMove.Remaining = nRemaining;
responseCMove.Completed = nCompleted;
responseCMove.Warnings = nWarnings;
responseCMove.Failures = nFailures; base.SendResponseAsync(responseCMove);
//SendResponse(responseCMove);
}//end if foreach (var one in listFind)
{
try
{
string path = AppSettings.dicom_path_root; string file = Path.Combine(path, one.DomainID, one.StudyDateTime.Value.ToString("yyyyMMdd"), one.SysStudyGUID, one.SOPInstanceUID + ".dcm"); if (!File.Exists(file))
{
lock (_objLock)
{
nFailures++;
} throw new Exception($"文件不存在 {file}");
} DicomCStoreRequest dicomCStoreRequest = new DicomCStoreRequest(file);
//读取了dcm文件后,dicomCStoreRequest.Dataset的值将从file读取填充
//dicomCStoreRequest.Dataset.Add(DicomTag.XXX, XXX); dicomCStoreRequest.OnResponseReceived = (rq, rs) =>
{
LogHelper.Instance.Debug($"dicomCStoreRequest --> {rs.Status}"); if (rs.Status == DicomStatus.Success)
{
lock (_objLock)
{
nCompleted++; nRemaining = len - nFailures - nWarnings - nCompleted;
} //--------------------------------------------------------------------
if (true)
{
DicomCMoveResponse response = new DicomCMoveResponse(request, DicomStatus.Pending);
response.Remaining = nRemaining;
response.Completed = nCompleted;
response.Warnings = nWarnings;
response.Failures = nFailures; //将一些信息返回给客户端,作为客户端确认相关操作使用
response.Dataset = new DicomDataset();
response.Dataset.Add(DicomTag.SOPInstanceUID, one.SOPInstanceUID);
response.Dataset.Add(DicomTag.StudyInstanceUID, one.StudyInstanceUID);
response.Dataset.Add(DicomTag.SeriesInstanceUID, one.SeriesInstanceUID);
response.Dataset.Add(DicomTagVNA.CMoveServerFilePath, file); base.SendResponseAsync(response);
//SendResponse(rsponse);
}//end if
//--------------------------------------------------------------------
}
else
{
LogHelper.Instance.Debug($"cmove-cstore给客户端返回失败({rs.Status})");
} }; try
{
LogHelper.Instance.Debug($"发送文件 --> {file}"); DicomClient client = new DicomClient();
client.AddRequest(dicomCStoreRequest); client.Send(
ae.IPAddress,
ae.Port,
false,
aet_current,
aet_remote
);
}
catch (Exception ex)
{
LogHelper.Instance.Debug("cmove发送给客户端失败 --> " + ex.ToString());
throw ex;
} }
catch (Exception ex)
{
Debug.WriteLine(ex.ToString()); DicomCMoveResponse rs = new DicomCMoveResponse(request, DicomStatus.StorageStorageOutOfResources);
listResponse.Add(rs); return listResponse; }
finally
{ } }//end foreach listResponse.Add(new DicomCMoveResponse(request, DicomStatus.Success));
return listResponse; }
} listResponse.Add(new DicomCMoveResponse(request, DicomStatus.NoSuchObjectInstance));
return listResponse;