医学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上下载源码笔记编译。这里直接上代码,代码中都有注释。
/// <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系统的信息。
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即可。这里代码如下。
/// <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);
}
}
}
}