DICOM:C-GET服务

Posted zssure

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DICOM:C-GET服务相关的知识,希望对你有一定的参考价值。

背景:

之前博文对比过多次C-MOVE与C-GET服务的区别,两者最大的区别在于C-GET是基于单个TCP连接的点对点的两方服务,而C-MOVE是基于两个TCP连接的三方服务(详情参见:《DICOM:C-GET与C-MOVE对比剖析》,以及DICOM:C-GET与C-MOVE对比剖析(续))。加之前一篇专栏博文DICOM:DICOM3.0网络通信协议之“开源库实现剖析”也已详细对比了dcm4che和fo-dicom开源库的底层实现,因此本篇博文直接给出基于fo-dicom开源库的C-GET服务实现的主要代码,着重介绍C-GET服务端与C-MOVE服务端发起C-STORE 子操作的区别。

C-GET-SCU:

在fo-dicom开源库中DICOM的各种Client端已经抽象出了DicomClientBase类,针对各种DIMSE-C服务(诸如C-STORE、C-GET、C-MOVE、C-ECHO、C-FIND)唯一不同的就是绑定各自对应的委托即可。C-GET-SCU客户端的核心代码如下:

        #region Protected Overrides
        protected override void OnConnected()
        
            DcmAssociate associate = new DcmAssociate();

            byte pcid = associate.AddPresentationContext(_getSopClass);

            associate.AddTransferSyntax(pcid, DicomTransferSyntax.ExplicitVRLittleEndian);
            associate.AddTransferSyntax(pcid, DicomTransferSyntax.ImplicitVRLittleEndian);
            byte pcid2 = associate.AddPresentationContext(DicomUID.CTImageStorage);

            associate.AddTransferSyntax(pcid2, DicomTransferSyntax.ExplicitVRLittleEndian);
            associate.AddTransferSyntax(pcid2, DicomTransferSyntax.ImplicitVRLittleEndian);

            associate.CalledAE = CalledAE;
            associate.CallingAE = CallingAE;
            associate.MaximumPduLength = MaxPduSize;
            //zssure:2015/07/06
            //Add UserIdentity Information
            //http://medical.nema.org/medical/dicom/current/output/html/part07.html#sect_D.3.3.7
            if (userIdentity == null)
                SendAssociateRequest(associate);
            else
                SendAssociateRequest(associate, userIdentity);
            //zssure:end,2015/07/06
        

        private void PerformQueryOrRelease()
        
            if (_getQueries.Count > 0)
            
                byte pcid = Associate.FindAbstractSyntax(GetSopClassUID);
                if (Associate.GetPresentationContextResult(pcid) == DcmPresContextResult.Accept)
                
                    current = _getQueries.Dequeue();
                    SendCGetRequest(pcid,1,Priority,current.ToDataset());
                
                else
                
                    SendReleaseRequest();
                
            
            else
            
                SendReleaseRequest();
            
        
        protected override void OnReceiveCStoreRequest(byte presentationID, ushort messageID, DicomUID affectedInstance,
            DcmPriority priority, string moveAE, ushort moveMessageID, DcmDataset dataset, string fileName)
        
            try
            
                if (OnCStoreRequest != null)
                    OnCStoreRequest(presentationID, messageID, affectedInstance, priority, moveAE, moveMessageID, dataset, fileName);
                SendCStoreResponse(presentationID, messageID, affectedInstance, DcmStatus.Success);

            
            catch (System.Exception ex)
            
                SendCStoreResponse(presentationID, messageID, affectedInstance, DcmStatus.ProcessingFailure);

            
            Console.WriteLine("c-get c-store RQ!");
        
        protected override void OnReceiveAssociateAccept(DcmAssociate association)
        
            PerformQueryOrRelease();
        
        protected override void OnReceiveCGetResponse(byte presentationID, ushort messageID, DcmDataset dataset,
            DcmStatus status, ushort remain, ushort complete, ushort warning, ushort failure)
        
            if (OnCGetResponse != null)
            
                OnCGetResponse(current, dataset, status, remain, complete, warning, failure);
            
            if (remain == 0 && status != DcmStatus.Pending)
            
                PerformQueryOrRelease();
            
        

【注意】:这里需要注意的有几点:
1)CGETClient端需要响应服务端发起的C-STORE-RQ,因此需要重写OnReceiveCStoreRequest函数;
2)之前在博文 DICOM:参考dcm4che2扩展fo-dicom(mDCM)中的UserIdentity字段已经介绍过扩展Association添加UserIdentity字段

C-GET-SCP:

C-GET服务端区别于C-MOVE服务端在于,DicomService服务类自身需要实现OnReceiveCStoreResponse函数,而之前C-MOVE服务端是在发送C-STORE-RQ时直接绑定OnReceiveCStoreResponse事件到CStoreClient。核心代码如下:

        private ConcurrentDictionary<ushort, CGetParameters> cgetProcessDic = new ConcurrentDictionary<ushort, CGetParameters>();
        protected override void OnReceiveCStoreResponse(byte presentationID, ushort messageIdRespondedTo, DicomUID affectedInstance, DcmStatus status)
        
            CGetParameters cgetPara = null;
            if (status == DcmStatus.Success)
            
                try
                
                    cgetProcessDic.TryGetValue(messageIdRespondedTo, out cgetPara);
                    cgetPara.CGetStatus.Complete++;
                    cgetPara.CGetStatus.Remain--;
                    SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Pending, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
                    if (cgetPara.CGetStatus.Remain > 0)
                    
                    ///self do something
                    
                        else
                        
                            cgetPara.CGetStatus.Fail++;
                            cgetPara.CGetStatus.Remain--;
                            SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Pending, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
                        
                    
                    else if (cgetPara.CGetStatus.Remain == 0)
                    
                        if (cgetProcessDic.TryRemove(messageIdRespondedTo, out cgetPara))
                            SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Success, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
                        else
                        
                            Log.Info("ReceiveCStoreResponse for CGet failed when remove from ConcurrentDictionary<ushort, CGetParameters>");
                            try
                            
                                cgetProcessDic.TryRemove(messageIdRespondedTo, out cgetPara);
                            
                            catch (System.Exception ex2)
                            
                                Log.Info("ReceiveCStoreResponse for CGet failed when remove from ConcurrentDictionary<ushort, CGetParameters> again,0,1", ex2.Message, ex2.StackTrace);
                            
                        

                    
                
                catch (System.Exception ex)
                
                    Log.Info("ReceiveCStoreResponse for CGet failed! 0,1", ex.Message, ex.StackTrace);
                    SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.InvalidArgumentValue, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
                
            
            else
            
                cgetPara.CGetStatus.Fail++;
                cgetPara.CGetStatus.Remain--;
                SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Pending, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
            
        

【注意】:上述代码需要注意是:
通过线程安全集合类ConcurrentDictionary在C-GET与C-STORE两种服务间同步状态,因为在OnReceiveCGetRequest函数中服务端是可以明确定位客户端请求的数据的,但是在接收到客户端C-STORE-RSP时,通过简单的DICOM Message是无法得知之前在OnReceiveCGetRequest中定位的数据的,因此需要在服务类中添加一个线程安全集合类来共享状态。如是可见,上述代码中大量的操作是在维护ConcurrentDictionary的状态,用于协调C-STORE与C-MOVE在同一个TCP连接中消息的传递。

备注:

这里纠正之前博文DICOM:C-GET与C-MOVE对比剖析中对于C-GET服务的C-STORE和C-MOVE消息流的流程错误,如下图所示:




作者:zssure@163.com
时间:2015-12-16

以上是关于DICOM:C-GET服务的主要内容,如果未能解决你的问题,请参考以下文章

DICOM:docker实现DICOM服务虚拟化

DICOM:docker实现DICOM服务虚拟化

DICOM:开源DICOM服务框架DCM4CHE 安装

DICOM医学图像处理:WEB PACS初谈四,PHP DICOM Class

DICOM医学图像处理:Orthanc Plugin SDK实现WADO服务

Slicer加载DICOM性能优化初探索