1. 从零开始:理解DICOM通信的“对话”基础
如果你刚接触医学影像系统开发,听到DICOM、SCU、SCP这些词可能有点发怵。别担心,我们可以把它想象成一次日常的快递寄送。你(SCU,服务使用者)想给朋友(SCP,服务提供者)寄一个包裹(DICOM影像)。整个流程需要一套标准的“暗号”和“动作”来确保包裹能准确送达,并且双方都知道包裹的状态。DICOM标准就是这套“暗号”和“动作”的说明书,而C-ECHO、C-STORE、C-FIND、C-MOVE就是其中最核心的四个“标准动作”。
在实际项目中,比如你要开发一个超声工作站,需要从医院的PACS(影像归档与通信系统)调图,你的工作站就是SCU,PACS就是SCP。反过来,如果一台CT机要把新扫描的图像发送到PACS存储,那么CT机就是SCU,PACS就是SCP。角色是随着通信方向变化的,理解这一点非常重要。我刚开始做对接的时候,就曾因为角色搞反,调试了半天连接都不成功。
要实现这些通信,光知道概念不够,必须动手。市面上有很多优秀的开源库,比如 fo-dicom(.NET)、pydicom(Python)、dcmtk(C++),它们帮我们封装了底层复杂的网络协议和数据编码,让我们能更专注于业务逻辑。这篇文章,我就以最常用的 fo-dicom 库(针对C#开发者)和 pydicom 库(针对Python开发者)为例,带大家一步步实现从最简单的连接测试(C-ECHO)到最复杂的影像调取(C-MOVE)的全过程。我会分享我踩过的坑和调试技巧,目标是让你看完就能写出可运行的代码。
2. 环境准备与第一个“握手”:C-ECHO实现
万事开头难,我们先从最简单的C-ECHO开始。你可以把它理解为一次网络“握手”或“心跳检测”,目的就是确认:“嗨,对面的兄弟,你在吗?我们能正常通话吗?”
2.1 搭建你的开发沙箱
在写代码之前,我们需要一个练习环境。总不能直接拿生产环境的PACS服务器测试吧?这里我强烈推荐两个工具:
- DCMTK工具包:它包含了一系列命令行工具,其中的
storescp和echoscu是我们模拟SCP和SCU的利器。 - Orthanc:一个开源的、轻量级的DICOM服务器,自带Web管理界面,非常适合作为测试用的SCP(PACS模拟器)。
我个人的习惯是,在开发初期用Orthanc快速搭建环境,因为它配置简单;在深入调试协议细节时,则用DCMTK的命令行工具,因为它更透明、更底层。以Orthanc为例,你只需要从官网下载,启动后,它默认就在本地104端口监听,AE Title(应用实体标题,你可以理解为设备在DICOM网络中的唯一名称)就是ORTHANC。
2.2 手把手实现C-ECHO SCU
现在,我们来用代码发起一次握手。记住,作为SCU,我们的任务是主动发起请求。
C# (fo-dicom) 版本:
using Dicom.Network;
class Program
{
static void Main(string[] args)
{
// 1. 创建一个DICOM客户端(SCU)
var client = new DicomClient();
// 2. 创建C-ECHO请求,参数是SCU的AE Title和SCP的AE Title
var cEchoRequest = new DicomCEchoRequest("MY_WORKSTATION", "ORTHANC");
// 3. 设置请求的回调,用于处理SCP的响应
cEchoRequest.OnResponseReceived = (request, response) =>
{
Console.WriteLine($"收到C-ECHO响应!");
Console.WriteLine($"状态: {response.Status}");
// Status 为 Success 表示握手成功
if (response.Status == DicomStatus.Success)
{
Console.WriteLine("✅ 连接测试成功!与PACS通信正常。");
}
else
{
Console.WriteLine($"❌ 连接失败。错误信息: {response.ErrorComment}");
}
};
// 4. 添加请求到客户端,并指定SCP的地址和端口
client.AddRequest(cEchoRequest);
// 5. 发起异步连接并发送请求
try
{
client.Send("127.0.0.1", 104, false, "MY_WORKSTATION", "ORTHANC");
Console.WriteLine("C-ECHO请求已发送。");
}
catch (Exception ex)
{
Console.WriteLine($"发送请求时发生异常: {ex.Message}");
}
}
}
这段代码的关键点在于DicomCEchoRequest的两个参数,第一个是调用者AE Title(你自己的标识),第二个是被调用者AE Title(目标PACS的标识)。这两个Title必须在通信双方预先配置的认可列表中,否则SCP会直接拒绝连接,返回Status可能就是RefusedNotAuthorized。这是我踩的第一个坑:本地测试时,SCU和SCP的AE Title随便写可能行,但对接真实系统时,必须向PACS管理员申请合法的AE Title。
Python (pydicom + pynetdicom) 版本:
from pynetdicom import AE, evt
from pynetdicom.sop_class import VerificationSOPClass
# 1. 创建应用实体(AE)
ae = AE(ae_title=b'MY_WORKSTATION')
# 2. 添加C-ECHO所支持的服务上下文(可以理解为协议版本)
ae.add_requested_context(VerificationSOPClass)
# 3. 创建关联(Association)并发送请求
assoc = ae.associate("127.0.0.1", 104, ae_title=b'ORTHANC')
if assoc.is_established:
print("关联已建立,正在发送C-ECHO请求...")
# 发送C-ECHO请求
status = assoc.send_c_echo()
if status:
print(f"✅ C-ECHO请求成功! 状态: {status}")
else:
print("❌ C-ECHO请求失败。")
# 释放关联
assoc.release()
else:
print("❌ 无法建立关联,连接被拒绝。")
Python版本使用了pynetdicom这个强大的库。这里有一个细节:add_requested_context。DICOM通信在建立连接(Association)时,需要协商双方支持哪些服
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_29218509/article/details/158242056



