dicom 影像通信(scu、scp)的c-echo、c-store、c-find、c-move
本文主要描述,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;
dicom 影像通信(scu、scp)的c-echo、c-store、c-find、c-move的更多相关文章
- C#开发PACS医学影像处理系统(六):加载Dicom影像
对于一款软件的扩展性和维护性来说,上层业务逻辑和UI表现一定要自己开发才有控制权,否则项目上线之后容易被掣肘, 而底层图像处理,我们不需要重复造轮子,这里推荐使用fo-dicom,同样基于Dicom3 ...
- C#开发PACS医学影像处理系统(十五):Dicom影像交叉定位线算法
1.定位线概念:某个方位的影像在另一个方向的影像上的投影相交线,例如横断面(从头到脚的方向)在矢状面(从左手到右手)上的影像投影面交线. 举个例子:右边的是MR(核磁共振)的某一帧切片,这是从头开始扫 ...
- C#开发PACS医学影像处理系统(十四):处理Dicom影像窗宽窗位
概念解释(网络资料): 窗宽: 窗宽指CT图像所显示的CT 值范围.在此CT值范围内的组织结构按其密度高低从白到黑分为16 个灰阶以供观察对比.例如,窗宽选定为100 Hu ,则人眼可分辨的CT值为1 ...
- 大型三甲医院管理系统源码PACS超声科室源码DICOM影像工作站
详情点击查看 开发环境 :VS2008 + C# + SQL2000 功能简介 1.患者登记工作站 集中登记患者基本信息和检查信息,包括就诊方式.患者来源.检查类型.检查部位.申请科室.申请医生等.可 ...
- C#开发PACS医学影像处理系统(十一):Dicom影像挂片协议
通俗点说,挂片协议可以看作整个系统的一个相对复杂一点的配置文件,可以用JSON或XML格式来读取与保存, 另外,可以制作一个独立的exe配置程序来管理这些挂片协议. 假设配置了CT的挂片协议的右键菜单 ...
- C#开发PACS医学影像处理系统(十九):Dicom影像放大镜
在XAML代码设计器中,添加canvas画布与圆形几何对象,利用VisualBrush笔刷来复制画面内容到指定容器: <Canvas x:Name="CvsGlass" Wi ...
- dicom通讯的工作方式及dicom标准简介
本文主要讲述dicom标准及dicom通讯的工作方式.dicom全称医学数字图像与通讯 其实嘛就两个方面 那就是“存储”跟“通讯”. 文件数据组织方式 网络数据组织方式.文件数据组织方式就是解析静态 ...
- DICOM标准相关资料
由于需要阅读影像,对DICOM需要先熟悉起来.关于DICOM,找了一些资料,可以学习.如下: DICOM标准:http://dicom.nema.org/standard.html 中文 DICOM ...
- DICOM:DICOM3.0网络通信协议(续)
转载:http://blog.csdn.net/zssureqh/article/details/44278693 题记: 近一年来一直坚持周末写博客,整理工作和闲暇之余的点点滴滴.对于新知识点.新技 ...
随机推荐
- [BZOJ4553][HEOI2016/TJOI2016]序列
传送门 好像是DP再套个裸的CDQ? 树套树是不可能写树套树的,这辈子都不可能写树套树的 对于一个 \(i\) ,设它最小为 \(a_i\) ,原数为 \(b_i\) ,最大为 \(c_i\) \(f ...
- Spring Boot2 系列教程(二十一)整合 MyBatis
前面两篇文章和读者聊了 Spring Boot 中最简单的数据持久化方案 JdbcTemplate,JdbcTemplate 虽然简单,但是用的并不多,因为它没有 MyBatis 方便,在 Sprin ...
- JS面试题-<变量和类型>-JavaScript的数据类型
前言 整理以前的面试题,发现问js数据类型的频率挺高的,回忆当初自己的答案,就是简简单单的把几个类型名称罗列了出来,便没有了任何下文.其实这一个知识点下可以牵涉发散出很多的知识点,如果一个面试者只是罗 ...
- Spring-Mybatis-SpringMVC三大框架整合
我们直接切人正题,不多逼逼赖赖 第一步:依赖,一下的这些基本上是SSM整合的全部依赖 <!-- https://mvnrepository.com/artifact/org.springfram ...
- 使用websocketpp进行websocket通信
websocketpp介绍 websocketpp是一个只有头文件的支持websocket协议的C++开源库,支持websocket客户端和服务器功能,网络传输模块基于boost::asio 提供 s ...
- Eclipse对Java项目打Jar包
在本Java项目中,如下图一所示,Java项目含有外部依赖Jar包 -- fastjson-1.2.29.jar 包. 在经历了多次的失败后,最后我终于使用 Eclipse 对 "Java ...
- 《Java多线程面试题》系列-创建线程的三种方法及其区别
1. 创建线程的三种方法及其区别 1.1 继承Thread类 首先,定义Thread类的子类并重写run()方法: package com.zwwhnly.springbootaction.javab ...
- GitHub和Git
GitHub托管项目代码 首先一些基本概念: repository(仓库) 用来存放项目代码,每个项目代表一个仓库,开一个项目就意味着你有一个仓库. star(收藏) 收藏方便下次查找. fork(复 ...
- thinkphp5配合datatable插件分页后端处理程序
thinkphp5配合datatable插件分页后端处理程序第一版DataTable.php v.1.0 <?php use think\Db; /** * DataTable.php. */ ...
- nyoj 3 多边形重心问题
多边形重心问题 时间限制:3000 ms | 内存限制:65535 KB 难度:5 描述 在某个多边形上,取n个点,这n个点顺序给出,按照给出顺序将相邻的点用直线连接, (第一个和最后一个连接 ...