医学DICOM文件与PACS系统进行数据传输

最近在做一个医疗项目,其中设计到医学影像数据Dicom文件的传输,其中的一个功能是需要Dicom文件和PACS系统进行互通,就是能将本地的DICOM文件传输到PACS系统,并且本地系统能查询到PACS系统数据并下载。针对将文件上传到PACS系统。之前我们项目中都是使用的C++来处理的。C#的项目都是通过调用C++封装的接口来实现与PACS的数据传输。我这里做了实现了两种方案,都是纯C#实现,并且都已走通。
方式一:通过C#的开源库fo-dicom来实现的数据传输,直接通过NuGet安装相关的依赖包,比如我这里是NET6,搜索并下载fo-dicom.NetCore,如果是.net framework版本的,应该是搜索fo-dicom安装即可。也可以去github上下载源码笔记编译。这里直接上代码,代码中都有注释。

方式一:基于fo-dicom的开源库来实现
        /// <summary>
        /// 发送Dicom数据到PACS系统,C#实现方式
        /// </summary>
        /// <param name="dicomFilePath">Dicom文件路径</param>
        /// <param name="nodeName">Dicom节点名称,我这里是根据节点来找到对应的PACS服务器信息</param>
        /// <returns></returns>
        public async Task SendDicomFileToPacs(string dicomFilePath, string nodeName)
        {
            DICOMConnectParameterWrapper connectPara = new DICOMConnectParameterWrapper();
            DICOMConnectKeyWrapper connectKey = new DICOMConnectKeyWrapper();
            bool isSecure = false;
            if (GetConnectionParameter(nodeName, ref connectPara, ref connectKey, ref isSecure) == false)
            {
                await Task.CompletedTask; //如果找不到配置的PACS服务器信息,直接返回
            }
            //connectPara.ServiceIP=PACS服务器IP,connectPara.ServicePort=PACS服务器端口,connectPara.ClientAETitle=客户端AETITLE,connectPara.ServiceAETitle=PACS服务器AEtitle
            var client = new Dicom.Network.Client.DicomClient(connectPara.ServiceIP, Convert.ToInt32(connectPara.ServicePort), false, connectPara.ClientAETitle, connectPara.ServiceAETitle);
            var file = DicomFile.Open(@dicomFilePath);

            client.AssociationAccepted += (sender, args) =>
            {
                Console.WriteLine("Association accepted");
            };
            client.AssociationRejected += (sender, args) =>
            {
                Console.WriteLine("Association rejected");
            };
            client.RequestTimedOut += (sender, args) =>
            {
                Console.WriteLine("DIMSE timeout");
            };
            client.StateChanged += (sender, args) =>
            {
                Console.WriteLine("DIMSE progress: {0} / {1}", args.NewState.ToString(), args.OldState.ToString());
            };
            await client.AddRequestAsync(new Dicom.Network.DicomCStoreRequest(@dicomFilePath)
            {
                OnResponseReceived = (req, res) =>
                {
                    Console.WriteLine($"C-STORE response: {res.Status}");
                }
            });
            try
            {
                await client.SendAsync();
            }
            catch(Exception ex)
            {
                Console.WriteLine("An error occurred while sending the DICOM file: {0}", ex.Message);
            }
            
        }

在方式一中,这里封装的是针对简单的传输。是在PACS系统不需要认证等前提下,就是说数据传输不需要加密认证等。 当然,实际使用时,PACS系统有些会有安全和认证机制,我们需要根据实际情况处理。我这里的方法GetConnectionParameter是封装的根据PACS节点名来找到对应的PACS的配置,比如PACS的服务器IP,Port,是否加密,证书,等等。如果有加密或认证什么的,还需要进一步处理,我这里会涉及到多个PACS服务器系统,我是在系统中做了配置,通过nodeName来找到对一个的PACS系统的信息。

GetConnectionParameter获取PACS服务器信息
        private bool GetConnectionParameter(string nodeName, ref DICOMConnectParameterWrapper connectPara, ref DICOMConnectKeyWrapper connectKey, ref bool isSecure)
        {
            if (!DicomPeerDictionary.ContainsKey(nodeName))
            {
                return false;
            }
            DicomNodeBase node = DicomPeerDictionary[nodeName];

            connectPara.ClientAETitle = ConfigInfo.DicomNode_ClientAETitle; //客户端AETitle
            connectPara.ClientIP = CommonHelper.GetAddressIP(); //客户端IP地址
            connectPara.ClientPort = ConfigInfo.DicomNode_ClientPort; //客户端端口
            connectPara.ServiceAETitle = node.AETitle; //PACS服务器AETitle
            connectPara.ServiceIP = node.IP; //PACS服务器IP
            connectPara.ServicePort = node.Port; //PACS服务器端口

            connectKey.PrivateKey = node.PrivateKeyPath; //秘钥
            connectKey.CACertificateFile = node.CACertificatePath; //CA证书文件
            connectKey.CertificateFile = node.CertificatePath; //证书

            isSecure = node.IsEncryptionChecked; //是否加密

            return true;
        }

使用的时候就简单了,直接调用SendDicomFileToPacs方法,传入Dicom文件的全路径,以及PACS配置的NodeName即可。另外,我这里用的fo-dicom的库是fo-dicom.netcore的4.0.8版本的,每个版本的可能会有些差别。这里还有涉及到从PACS查询数据,下载数据的功能,这个在fo-dicom库中可以使用类Dicom.Network.DicomCFindRequest来进行操作,其中封装了好几个查询的方法:CreateStudyQuery,CreateSeriesQuery,CreatePatientQuery,CreateImageQuery,CreateWorklistQuery,然后询请求的响应通过类中的OnResponseReceived事件处理,可以获取到查询结果的数据集。我们可以将查询结果的数据集直接转换成一个DICOM文件保存到本地,这样就可以实现下载功能。

方式二:传输数据到PACS的实现方式
这种方式我是针对Orthanc的PACS系统的实现方式,,Orthanc本身有提供API接口,只需要调用其接口实现数据传输即可。使用的时候传入Dicom文件路径和PACS服务器URL即可。这里代码如下。

发送数据到Orthanc的PACS系统
        /// <summary>
        /// 发送数据到Orthanc的PACS系统,C#实现方式,直接调用Orthanc的API来实现
        /// </summary>
        /// <param name="dicomFilePath">Dicom文件路径</param>
        /// <param name="orthancUrl">PACS系统的URL,类似 http://localhost:8042</param>
        public void UploadDicomToOrthanc(string dicomFilePath, string orthancUrl)
        {
            using (var client = new HttpClient())
            {
                using (var content = new MultipartFormDataContent())
                {
                    // 添加文件内容
                    var fileStream = File.OpenRead(dicomFilePath);
                    var streamContent = new StreamContent(fileStream);
                    content.Add(streamContent, "file", Path.GetFileName(dicomFilePath));

                    // 发送POST请求到Orthanc服务器
                    var response =  client.PostAsync($"{orthancUrl}/instances", content).Result;

                    if (response.IsSuccessStatusCode)
                    {
                        Console.WriteLine("DICOM文件上传成功。");
                    }
                    else
                    {
                        Console.WriteLine("上传失败,HTTP状态码:" + response.StatusCode);
                    }
                }
            }

        }
posted @ 2024-09-14 11:41  奔四的大龄码农  阅读(238)  评论(0编辑  收藏  举报