fo-dicom是如何实现DICOM 的网络通信功能

一、前言

前面的文章,我们介绍了fo-dicom是一个怎样的开源库等一些内容:

  • fo-dicom,第一个基于.NET Standard 2.0 开发的DICOM开源库
  • fo-dicom开源库是如何满足 DICOM标准的基本要求
  • fo-dicom开发之DICOM数据解析:常见数据类型及处理方法详解

今天我们分享下 fo-dicom是如何实现DICOM 的网络通信功能。

二、DICOM3.0标准的通用通信模型

下图显示了DICOM3.0标准的通用通信模型,该模型跨越了 网络(在线)和媒体存储交换(离线)通信。应用程序可利用以下任一传输机制:
在这里插入图片描述

  • DICOM 消息服务和上层服务,它们独立于特定的物理网络通信支持和协议(如 TCP/IP)。
  • DICOM Web 服务 API 和 HTTP 服务,允许使用通用超文本和相关协议来传输 DICOM 服务
  • 基本 DICOM 文件服务,它提供独立于特定媒体存储格式和文件结构的存储介质访问
  • DICOM 实时通信,提供基于 SMPTE 和 RTP 的 DICOM 元数据的实时传输。

DICOM的通用通信模型旨在为医疗图像和相关数据的传输和存储提供灵活和多样化的解决方案。DICOM的通用通信模型不仅仅是简单地传输和存储医疗图像和相关数据,而是提供了一种多层次、多种方式的灵活解决方案,以满足不同场景下的需求和要求。这种多样化的传输和存储机制使得DICOM成为医疗行业中不可或缺的通信标准,为医疗图像和相关数据的交换和共享提供了可靠和高效的技术支持。

DICOM的通用通信模型的重要性和价值在于其能够满足医疗行业不同方面的需求,为医疗图像和相关数据的传输和存储提供了全面而可靠的解决方案,从而推动了医疗信息技术的发展和应用。

三、基于.NET 的网络通信功能

fo-dicom 使用了Socket和TcpClient等底层网络通信类来与DICOM服务器进行连接和通信,从而实现 DICOM 的网络通信功能。下面是 fo-dicom 网络通信的实现基本原理:

  1. 传输协议选择fo-dicom 支持多种传输协议,如 TCP/IP、UDP 和 WebSocket。用户可以根据需要选择适合的传输协议。

  2. 连接建立:对于服务器端应用程序,使用 DicomServer 类监听指定的端口号,等待客户端连接请求。一旦有客户端连接请求到达,服务器将建立一个与客户端的网络连接。

  3. 数据传输:在 DICOM 通信中,数据通过 DIMSE(DICOM Message Service Element)进行传输。DIMSE 是基于消息的通信模式,包括 C-STORE(存储服务)、C-FIND(查询服务)、C-MOVE(移动服务)等。

  4. 数据编码fo-dicom 使用 DICOM 标准定义的数据格式和编码规则对数据进行编码。DICOM 数据集使用一系列的标签(Tag)来组织和描述不同的信息,例如患者姓名、图像序列等。fo-dicom 将数据集编码为字节流以进行传输。

  5. 数据解码:在接收方,fo-dicom 将接收到的字节流解码为 DICOM 数据集,以便进行后续的处理和分析。

  6. 数据处理:根据具体的应用需求,可以对 DICOM 数据集进行查询、存储、检索等操作。fo-dicom 提供了一组 API 来处理 DICOM 数据集,以便用户能够方便地访问和操作数据。

  7. 响应发送:在服务器端应用程序中,一旦完成对 DICOM 请求的处理,将向客户端发送一个响应。响应中包含请求的执行结果、状态信息等。

  8. 连接断开:通信完成后,可以关闭服务器端的监听或断开客户端与服务器的连接。

fo-dicom 通过使用 .NET 平台的网络通信库来实现底层的网络传输,并且遵循 DICOM 标准的数据格式和编码规则。它提供了一组简洁而强大的 API,使得用户可以方便地进行 DICOM 数据的传输和处理。

四、案例说明

以下是一个使用 fo-dicom 进行C-STORE命令的网络通信的简单案例:案例来源于官网的示例,https://github.com/fo-dicom/fo-dicom-samples

1. SCP处理C-STORE请求

假设我们有一个服务器端应用程序,它监听在本地的端口号 11112上,等待客户端的连接请求。一旦接收到来自客户端的 C-STORE 请求,服务器将把接收到的 DICOM 图像数据保存到本地磁盘。

using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using FellowOakDicom.Network;
using Microsoft.Extensions.Logging;namespace Samples
{internal class Program{private const string _storagePath = @".\DICOM";private static void Main(string[] args){// start DICOM server on port from command line argument or 11112var port = args != null && args.Length > 0 && int.TryParse(args[0], out int tmp) ? tmp : 11112;Console.WriteLine($"Starting C-Store SCP server on port {port}");using (var server = DicomServerFactory.Create<CStoreSCP>(port)){// end processConsole.WriteLine("Press <return> to end...");Console.ReadLine();}}private class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider{private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]{DicomTransferSyntax.ExplicitVRLittleEndian,DicomTransferSyntax.ExplicitVRBigEndian,DicomTransferSyntax.ImplicitVRLittleEndian};private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]{// LosslessDicomTransferSyntax.JPEGLSLossless,DicomTransferSyntax.JPEG2000Lossless,DicomTransferSyntax.JPEGProcess14SV1,DicomTransferSyntax.JPEGProcess14,DicomTransferSyntax.RLELossless,// LossyDicomTransferSyntax.JPEGLSNearLossless,DicomTransferSyntax.JPEG2000Lossy,DicomTransferSyntax.JPEGProcess1,DicomTransferSyntax.JPEGProcess2_4,// UncompressedDicomTransferSyntax.ExplicitVRLittleEndian,DicomTransferSyntax.ExplicitVRBigEndian,DicomTransferSyntax.ImplicitVRLittleEndian};public CStoreSCP(INetworkStream stream, Encoding fallbackEncoding, ILogger log, DicomServiceDependencies dependencies): base(stream, fallbackEncoding, log, dependencies){}public Task OnReceiveAssociationRequestAsync(DicomAssociation association){if (association.CalledAE != "STORESCP"){return SendAssociationRejectAsync(DicomRejectResult.Permanent,DicomRejectSource.ServiceUser,DicomRejectReason.CalledAENotRecognized);}foreach (var pc in association.PresentationContexts){if (pc.AbstractSyntax == DicomUID.Verification){pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes);}else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None){pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);}}return SendAssociationAcceptAsync(association);}public Task OnReceiveAssociationReleaseRequestAsync(){return SendAssociationReleaseResponseAsync();}public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason){/* nothing to do here */}public void OnConnectionClosed(Exception exception){/* nothing to do here */}public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request){var studyUid = request.Dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID).Trim();var instUid = request.SOPInstanceUID.UID;var path = Path.GetFullPath(Program._storagePath);path = Path.Combine(path, studyUid);if (!Directory.Exists(path)){Directory.CreateDirectory(path);}path = Path.Combine(path, instUid) + ".dcm";await request.File.SaveAsync(path);return new DicomCStoreResponse(request, DicomStatus.Success);}public Task OnCStoreRequestExceptionAsync(string tempFileName, Exception e){// let library handle logging and error responsereturn Task.CompletedTask;}public Task<DicomCEchoResponse> OnCEchoRequestAsync(DicomCEchoRequest request){return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success));}}}
}

在上面的代码中,首先从命令行参数中获取端口号,然后创建一个 CStoreSCP 对象作为 DICOM 服务器,并将其绑定到指定的端口。在 CStoreSCP 类中,实现了 IDicomServiceProvider、IDicomCStoreProvider 和 IDicomCEchoProvider 接口,分别处理 DICOM 关联请求、C-Store 请求和 C-Echo 请求。其中,
OnReceiveAssociationRequestAsync() 方法会检查 Called AE 是否为 STORESCP,如果不是则拒绝关联请求。OnCStoreRequestAsync() 方法则会将接收到的 DICOM 数据保存到本地文件系统中。其他的方法实现通常为空实现,因为并不需要对其进行特殊处理。

2. SCU发送C-STORE请求

对于客户端应用程序,我们可以使用 DicomClient 类来发送 C-STORE 请求到服务器。以下是一个简单的客户端示例:

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using FellowOakDicom.Network;
using FellowOakDicom.Network.Client;namespace Samples
{internal static class Program{private static string _storeServerHost = "127.0.0.1";private static int _storeServerPort = 11112;private const string _storeServerAET = "STORESCP";private const string _aet = "FODICOMSCU";static async Task Main(string[] args){var storeMore = "";_storeServerHost = GetServerHost();_storeServerPort = GetServerPort();Console.WriteLine("***************************************************");Console.WriteLine("Server AE Title: " + _storeServerAET);Console.WriteLine("Server Host Address: " + _storeServerHost);Console.WriteLine("Server Port: " + _storeServerPort);Console.WriteLine("Client AE Title: " + _aet);Console.WriteLine("***************************************************");var client = DicomClientFactory.Create(_storeServerHost, _storeServerPort, false, _aet, _storeServerAET);client.NegotiateAsyncOps();do{try{Console.WriteLine();Console.WriteLine("Enter the path for a DICOM file:");Console.Write(">>>");string dicomFile = Console.ReadLine();while (!File.Exists(dicomFile)){Console.WriteLine("Invalid file path, enter the path for a DICOM file or press Enter to Exit:");dicomFile = Console.ReadLine();if (string.IsNullOrWhiteSpace(dicomFile)){return;}}var request = new DicomCStoreRequest(dicomFile);request.OnResponseReceived += (req, response) => Console.WriteLine("C-Store Response Received, Status: " + response.Status);await client.AddRequestAsync(request);await client.SendAsync();}catch (Exception exception){Console.WriteLine();Console.WriteLine("----------------------------------------------------");Console.WriteLine("Error storing file. Exception Details:");Console.WriteLine(exception.ToString());Console.WriteLine("----------------------------------------------------");Console.WriteLine();}Console.WriteLine("To store another file, enter \"y\"; Othersie, press enter to exit: ");Console.Write(">>>");storeMore = Console.ReadLine().Trim();} while (storeMore.Length > 0 && storeMore.ToLower()[0] == 'y');}private static string GetServerHost(){var hostAddress = "";var localIP = GetLocalIPAddress();do{Console.WriteLine("Your local IP is: " + localIP);Console.WriteLine("Enter \"1\" to use your local IP Address: " + localIP);Console.WriteLine("Enter \"2\" to use defult: " + _storeServerHost);Console.WriteLine("Enter \"3\" to enter custom");Console.Write(">>>");string input = Console.ReadLine().Trim().ToLower();if (input.Length > 0){if (input[0] == '1'){hostAddress = localIP;}else if (input[0] == '2'){hostAddress = _storeServerHost;}else if (input[0] == '3'){Console.WriteLine("Enter Server Host Address:");Console.Write(">>>");hostAddress = Console.ReadLine();}}} while (hostAddress.Length == 0);return hostAddress;}private static int GetServerPort(){Console.WriteLine("Enter Server port, or \"Enter\" for default \"" + _storeServerPort + "\":");Console.Write(">>>");var input = Console.ReadLine().Trim();return string.IsNullOrEmpty(input) ? _storeServerPort : int.Parse(input);}public static string GetLocalIPAddress(){var host = Dns.GetHostEntry(Dns.GetHostName());foreach (var ip in host.AddressList){if (ip.AddressFamily == AddressFamily.InterNetwork){return ip.ToString();}}return "";}}
}

首先定义了一些变量,包括存储服务器的主机地址、端口号,以及客户端和服务器的 AE(Application Entity)标题。然后,在 Main 方法中创建了一个 DicomClient 对象,并通过调用 NegotiateAsyncOps 方法进行异步操作的协商。接下来,进入一个循环,用户可以输入要发送的 DICOM 文件的路径。程序会检查路径是否有效,如果无效则提示用户重新输入,直到输入为空或用户选择退出。然后,创建一个 DicomCStoreRequest 对象,传入要发送的 DICOM 文件路径作为参数。并通过订阅 OnResponseReceived 事件来处理响应。最后,调用 AddRequestAsync 方法将请求添加到客户端的请求队列中,并调用 SendAsync 方法发送请求。

其中GetServerHost 方法用于获取服务器主机地址,它会提示用户选择使用本地 IP 地址、默认地址还是自定义地址。GetServerPort 方法用于获取服务器端口号,用户可以输入自定义端口号,或者直接回车使用默认端口号。GetLocalIPAddress 方法用于获取本地 IP 地址。

3. 小结

这个案例展示了一个简单的基于 fo-dicom 的 DICOM 网络通信示例,即SCU和SCP对CStore的通信的简单处理,服务器接收到客户端发送的 C-STORE 请求并保存图像到本地磁盘。

总结

关于C-STORE的处理,如果不是特别清楚,后续,我们在一起学习关于通信协议相关的内容,重点分析,C-ECHO,C-Store,C-Find,C-Move等等,加深理解和使用场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1544053.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Transformers基础入门篇2】基础组件之Pipeline

文章目录 一、什么是Pipeline二、查看PipeLine支持的任务类型三、Pipeline的创建和使用3.1 根据任务类型&#xff0c;直接创建Pipeline&#xff0c;默认是英文模型3.2 指定任务类型&#xff0c;再指定模型&#xff0c;创建基于指定模型的Pipeline3.3 预先加载模型&#xff0c;再…

pycharm恢复两边侧边栏常驻显示

问题&#xff1a; pycharm两边的侧边栏菜单默认不显示&#xff08;打开project还得用alt1快捷键&#xff09;&#xff0c;非常不方便&#xff0c;如下图&#xff1a; pycharm版本&#xff1a;2022.3 professional 勾选&#xff1a;setttngs -> Appearance -> tool Wind…

云原生虚拟化kubevirt安装

kubevirt 介绍 Kubevirt 是 Redhat 开源的一套以容器方式运行虚拟机的项目&#xff0c;通过 kubernetes 云原生方式来管理虚拟机生命周期。它通过使用自定义资源&#xff08;CRD&#xff09;和其它 Kubernetes 功能来无缝扩展现有的集群&#xff0c;以提供一组可用于管理虚拟机…

JavaScript的注释与常见输出方式

注释 源码中注释是不被引擎所解释的&#xff0c;它的作用是对代码进行解释。Javascript 提供两种注释的写法:一种是单行注释&#xff0c;用//起头;另一种是多行注释&#xff0c;放在/*和*/之间。 单行注释&#xff1a; //这是单行注释 多行注释&#xff1a; /*这是 多行 注…

远程升级,你成功了吗?

最近又遇到了远程升级失败的情况&#xff0c;而且是不明原因的多次接连失败。。。 事情是这样的&#xff1a;最近有客户反馈在乡村里频繁出现掉线的情况。通过换货、换SIM卡对比排查测试&#xff0c;发现只有去年5月22号采购的那批模块在客户环境附近会出现掉线的情况&#xf…

服务器操作系统【sar 命令】

sar 安装、语法参数说明以及示例 文章目录 功能概述一、功能介绍1.安装配置2. 配置3. 启动二、sar 语法及参数说明三、示例及释义1.汇报 io 传输速率信息2.内存分页信息3.块设备状态信息4.hugepages 利用率统计信息5.列长度和负载平均值6.内存利用率统计信息7.swap 交换空间利用…

Redis数据持久化总结笔记

Redis 是内存数据库&#xff0c;如果不将内存中的数据库状态保存到磁盘&#xff0c;那么一旦服务器进程退出&#xff0c;服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能&#xff01; Redis 提供了 2 个不同形式的持久化方式 RDB&#xff08;Redis DataBase&#…

VS2019配置Open3Dv0.18.0版本库

文章目录 一、引言二、配置过程三、举个例子参考资料一、引言 现在如果直接使用vs2019对Open3D(v0.15.2)进行编译,会比较麻烦,一是需要科学上网,另一个就是容易出现错误,这里就仍然按照之前的思路来配置新版本的Open3D(VS2015(及以上版本)配置Open3Dv0.15.2版本库)。 二…

科研小白入门工具

三、科研绘图 1.流程图绘制工具&#xff1a;powerpoint、亿图图示、visio、draw.io 2.绘制标准&#xff1a;布局合理、色彩鲜明、字体大小、矢量输出 矢量图绘制推荐流程&#xff1a;亿图图示绘制--visio--word--pdf无损放大 3.文章插图&#xff1a;excel、origin、matlab、…

【JUC并发编程系列】深入理解Java并发机制:Volatile从底层原理解析到高级应用技巧(六、Volatile关键字、JMM、重排序、双重检验锁)

文章目录 【JUC并发编程系列】深入理解Java并发机制&#xff1a;Volatile从底层原理解析到高级应用技巧(六、Volatile关键字、JMM、重排序、双重检验锁)1. Volatile的特性2. Volatile的用法3. CPU多核硬件架构剖析4. JMM内存模型4.1 主要特性4.2 JMM 的工作原理4.3 实现机制 5.…

电商跨境电商商城系统/网上商城接口/电商数据接口详情

电商API接口背景&#xff1a;电商运营中&#xff0c;数据分析这项工作越来越重要&#xff0c;许多品牌方也越来越热衷去做电商数据分析。不过&#xff0c;全面的数据该如何获取呢&#xff0c;此时&#xff0c;电商数据接口的重要性便凸显出来了。 电商API数据接口主要有以下特…

ASP.NET Core8.0学习笔记(十九)——EF Core DbSet

一、DbSet概述 1.DbSet提供了通过DbContext对表进行查询操作的路径。DbSet对应的属性名称将默认映射为实体T的表名。 2.使用DbSet<T>进行查询的方法&#xff1a; (1)直接在DbContext中创建对应的DbSet<T>属性 (2)使用DbSet DbContext.Set<T>方法操作数据表。…

对c语言中的指针进行深入全面的解析

1.普通的指针: 实际上指针就是存放地址的变量&#xff0c;eg: int a10; int *p&a; 拆分一下int *中的*说明p是一个指针&#xff0c;int是它所指向的类型&#xff1b; 2.字符串指针和字符串数组 char*str1"abcd"; 先看这一个&#xff0c;这个就是一个字符串…

[vulnhub] Hackademic.RTB1

第一次打靶机&#xff0c;思路看的红队笔记 https://www.vulnhub.com/entry/hackademic-rtb1,17/ 环境&#xff1a;kali Linux - 192.168.75.131&#xff0c;靶机 - 192.168.75.132 主机发现和端口扫描 扫描整个网络有哪台机子在线&#xff0c;不进行端口扫描 nmap -sP 192.16…

关于API概念:连接数字世界的桥梁

在数字化时代&#xff0c;信息和数据的流动是构建现代应用程序的基础。API&#xff08;应用程序编程接口&#xff09;作为连接不同软件和服务的桥梁&#xff0c;正逐渐成为现代技术架构中不可或缺的一部分。本文将探讨API的概念、重要性以及它如何塑造我们的数字生活。 什么是A…

解决Echarts:宽度100%,渲染的宽度却是100px

为什么我们宽度设置了100%&#xff0c;结果变为了100px&#xff1f; 源码这里没有获取到clientWidth&#xff0c;会将设置的width:100%转换称100px 解决办法&#xff1a; <div ref"numberPieRef"></div>let numberPieRef ref(null); let myChart nu…

基于二自由度汽车模型的汽车质心侧偏角估计

一、质心侧偏角介绍 在车辆坐标系中&#xff0c;质心侧偏角通常定义为质心速度方向与车辆前进方向的夹角。如下图所示&#xff0c;u为车辆前进方向&#xff0c;v为质心速度方向&#xff0c;u和v之间的夹角便是质心侧偏角。 质心侧偏角的作用有如下三点&#xff1a; 1、稳定性…

深度学习之表示学习 - 贪心逐层无监督预训练篇

引言 在人工智能的浩瀚星空中&#xff0c;深度学习以其强大的数据处理与模式识别能力&#xff0c;成为了一颗璀璨的明星。而表示学习&#xff0c;作为深度学习的核心基石之一&#xff0c;正引领着这一领域不断突破边界。表示学习旨在将原始数据转换为更加抽象、更有意义的特征…

【51实物与仿真】基于51单片机设计的波形/函数发生器(正弦波、锯齿波、三角波、矩形波,设定频率步进值,改变振幅,LCD显示)——文末完整资料链接

基于51单片机设计的波形函数发生器 演示视频: 功能简介: 1.本设计基于STC89C51/52(与AT89S51/52、AT89C51/52通用,可任选)单片机。 2.LCD1602液晶显示波形种类和频率值(10-100HZ)。 3.按键设置波形种类和设定频率步进值。 4.电位器器改变振幅(0V-3.5V稳定)。 5…

苹果AI手机遇阻,国产手机找到超车机遇

行至九月&#xff0c;2024年&#xff0c;这个所谓AI手机的元年&#xff0c;已经走过近三个季度了。 市场最为期待的AI手机机型也基本都发布了。9月20日&#xff0c;首款搭载Apple Intelligence功能的苹果新品iPhone16正式发售。或许是为了进一步扩大销售&#xff0c;今年天猫A…