医用图像打印机怎么用DICOM入门系列——DICOM高级服务:存储提交与打印的实战解析

新闻资讯2026-04-21 03:14:45

在前面的文章中,我们系统学习了DICOM的基础(文件结构、网络传输)、Web接口(DICOMweb)、以及与AI的集成(从像素数据到智能诊断)。这些内容覆盖了医学影像的存储、共享、分析与智能化等核心场景。但在真实的医疗环境中,DICOM的功能远不止于此——它还提供了一系列高级服务,用于保障影像数据的可靠性(如存储提交验证)和拓展应用场景(如硬拷贝打印)。

其中,存储提交服务(Storage Commitment)打印服务(Print Management)是两类典型的高级服务:

  • 存储提交服务:解决“影像真的被安全存储了吗?”的问题,通过DICOM协议确认图像已被目标系统(如PACS)持久化保存,避免因网络故障或存储错误导致数据丢失;

  • 打印服务:支持将医学影像(如X光片、CT关键切片)直接输出到医用胶片打印机,生成符合临床诊断要求的硬拷贝(物理胶片),满足部分场景下医生对物理影像的需求。

本文将深入解析这两类高级服务的原理、应用场景,并通过工具实操与代码示例(如pydicom和Orthanc),带你体验如何在实际系统中调用它们。

1. 为什么需要存储提交服务?

在常规的DICOM存储流程(C-STORE)中,发送方(如CT设备)将图像发送给接收方(如PACS服务器),接收方返回“存储成功”的响应。但这个响应仅表示“我收到了数据”,并不保证数据已被持久化到磁盘(例如接收方可能只是暂存到内存,后续因崩溃或断电丢失)。

存储提交服务(Storage Commitment Service Class)​ 解决的就是这个信任问题:

发送方在完成C-STORE后,可以主动发起一个“存储提交请求”,要求接收方书面承诺:“我已经把这张图像安全地写到硬盘里了,即使系统崩溃也不会丢”。如果接收方无法承诺(例如存储失败),则会明确通知发送方。

典型应用场景:

  • 法规合规:某些国家/地区的医疗法规要求关键影像(如急诊CT)必须确保存储可靠性;

  • 数据备份:基层医院上传影像到云端PACS后,需要确认云端已真正保存,避免本地删除后云端丢失;

  • 系统可靠性验证:在重要检查(如手术规划MRI)中,确保影像不会因中间环节故障而消失。

2. 存储提交服务的核心流程

存储提交服务通过DICOM消息(N-ACTION和N-EVENT-REPORT)实现,分为两个阶段:

  1. 阶段1:C-STORE(常规存储)

    发送方(SCU)将DICOM图像发送给接收方(SCP),接收方返回“存储成功”的响应(但此时可能未持久化)。

  2. 阶段2:存储提交请求(N-ACTION)

    发送方向接收方发起“存储提交请求”,包含之前存储的图像的唯一标识符(SOP Instance UID列表),询问:“这些图像真的存好了吗?”。

    • 接收方检查这些图像是否已持久化(例如写入磁盘),若成功则返回“承诺”(确认存储);

    • 若失败(例如磁盘满、写入错误),则返回“拒绝”并说明原因。

  3. 阶段3(可选):事件报告(N-EVENT-REPORT)

    接收方后续可能主动通知发送方存储状态的变化(例如延迟持久化后补发确认)。

3. 工具实操:用Orthanc验证存储提交服务

Orthanc是一个支持存储提交服务的高级PACS服务器(默认配置可能需手动开启)。以下是验证步骤:

步骤1:确认Orthanc的存储提交配置

登录Orthanc的Web管理界面(http://localhost:8042),进入 “Settings” → “DICOM” → “Storage Commitment”,确保以下选项启用:

  • Enable Storage Commitment:勾选(默认可能关闭);

  • Storage Commitment SCP Port:指定接收存储提交请求的端口(通常与主端口分开,如10404)。

重启Orthanc使配置生效。

步骤2:发送DICOM图像并触发存储提交请求

我们可以用Python的 pynetdicom​ 库模拟一个发送方(SCU),完成以下操作:

  1. 先通过C-STORE将DICOM文件发送到Orthanc;

  2. 再通过存储提交服务(N-ACTION)请求Orthanc确认该图像已持久化。

from pynetdicom import AE, StoragePresentationContexts, evt
from pynetdicom.sop_class import CTImageStorage, StorageCommitmentPushModel
import pydicom

# 1. 配置AE(应用实体)——发送方(SCU)
ae = AE()

# 添加支持的上下文:CT图像存储(用于C-STORE)和存储提交(用于N-ACTION)
ae.add_requested_context(CTImageStorage)  # C-STORE的SOP类
ae.add_requested_context(StorageCommitmentPushModel)  # 存储提交的SOP类

# 2. 读取本地DICOM文件
dcm_file = "example.dcm"  # 替换为你的DICOM文件路径
dataset = pydicom.dcmread(dcm_file)

# 3. 连接到Orthanc服务器(假设Orthanc主服务端口104,存储提交服务端口10404)
pacs_ip = '127.0.0.1'
pacs_port_main = 104      # 主存储服务端口(C-STORE)
pacs_port_commit = 10404  # 存储提交服务端口(N-ACTION)

# --- 阶段1:C-STORE(存储图像到Orthanc)---
assoc_store = ae.associate(pacs_ip, pacs_port_main)
if assoc_store.is_established:
    print("[阶段1] 正在通过C-STORE发送DICOM文件到Orthanc...")
    status_store = assoc_store.send_c_store(dataset)
    if status_store and status_store.Status in [0x0000, 0xB000]:  # 0x0000=成功,0xB000=警告(部分成功)
        print("[阶段1] C-STORE成功!图像已临时存储(待提交确认)")
        sop_instance_uid = dataset.SOPInstanceUID  # 获取该图像的唯一标识符
    else:
        print("[阶段1] C-STORE失败!状态码:", status_store.Status if status_store else "无响应")
        exit()
    assoc_store.release()
else:
    print("[阶段1] 无法连接到Orthanc主服务!请检查IP和端口(默认104)")
    exit()

# --- 阶段2:存储提交请求(N-ACTION)---
# 构造存储提交的请求数据(包含要验证的SOP Instance UID列表)
commit_dataset = pydicom.Dataset()
commit_dataset.TransactionUID = pydicom.uid.generate_uid()  # 本次提交的唯一事务ID
commit_dataset.ReferencedSOPSequence = [
    pydicom.Dataset({
        'ReferencedSOPClassUID': CTImageStorage,  # 引用的SOP类(与存储的图像类型一致)
        'ReferencedSOPInstanceUID': sop_instance_uid  # 引用的具体图像UID
    })
]

# 关联到Orthanc的存储提交服务端口
assoc_commit = ae.associate(pacs_ip, pacs_port_commit)
if assoc_commit.is_established:
    print("[阶段2] 正在向Orthanc发起存储提交请求...")
    # 发送N-ACTION请求(Action Type ID=1表示“提交请求”)
    status_commit = assoc_commit.send_n_action(
        1,  # Action Type ID: 1=提交请求
        StorageCommitmentPushModel,  # 服务类
        commit_dataset
    )
    if status_commit and status_commit.Status in [0x0000, 0xB000]:
        print("[阶段2] 存储提交成功!Orthanc确认图像已持久化")
        # 可进一步解析status_commit.dataset获取详细信息(如失败原因)
    else:
        print("[阶段2] 存储提交失败!状态码:", status_commit.Status if status_commit else "无响应")
        if status_commit and hasattr(status_commit, 'dataset'):
            print("失败详情:", status_commit.dataset)
    assoc_commit.release()
else:
    print("[阶段2] 无法连接到Orthanc存储提交服务!请检查端口(默认10404)")

代码说明

  • 阶段1:通过C-STORE将DICOM文件发送到Orthanc(端口104),获取该图像的SOPInstanceUID(唯一标识符);

  • 阶段2:通过存储提交服务(端口10404)向Orthanc发送N-ACTION请求,询问该SOPInstanceUID对应的图像是否已持久化;

  • 结果验证:若Orthanc返回状态码0x0000,表示“已承诺存储”;若返回0xA700(拒绝)或0xCxxx(错误),则表示存储可能失败。

📌 注意

  • Orthanc默认可能未启用存储提交服务(端口10404),需手动在配置文件(Orthanc.json)中开启:

    {
      "StorageCommitment": {
        "Enable": true,
        "Port": 10404
      }
    }
  • 若测试环境无真实存储风险(如本地测试),存储提交可能总是返回成功(因为Orthanc默认会快速持久化),但在生产环境中(如网络不稳定或存储故障),该服务能有效发现问题。

1. 为什么需要打印服务?

尽管现代医学影像主要通过显示器查看(如PACS工作站、Web查看器),但在某些场景下,医生仍需要物理胶片(如手术室中的X光片、基层医院的传统阅片习惯)。DICOM打印服务(Print Management Service Class)就是为医学影像输出到医用胶片打印机而设计的标准协议。

打印服务支持:

  • 将DICOM图像(如X光、CT切片)发送到胶片打印机,并按临床要求排版(如多张图像拼接在一张胶片上);

  • 控制打印参数(如胶片尺寸、分辨率、对比度、标注文字);

  • 验证打印机状态(如墨盒余量、纸张是否就绪)。

典型应用场景:

  • 急诊手术:快速打印关键CT切片作为术中参考;

  • 基层医院:通过胶片向患者展示检查结果(部分患者更信任物理影像);

  • 教学与科研:打印典型病例的影像用于教学演示。

2. 打印服务的核心组件与流程

DICOM打印服务涉及三个核心角色:

  • SCU(Service Class User):发起打印请求的设备(如医生工作站、PACS系统);

  • SCP(Service Class Provider):提供打印服务的设备(如医用胶片打印机,如Agfa、Konica Minolta的专业打印机);

  • Film Session(胶片会话)与 Film Box(胶片盒):打印任务的逻辑单元(一个Film Session可包含多张Film Box,每张Film Box对应一张胶片上的图像布局)。

典型打印流程

  1. SCU向SCP查询打印机状态(确认打印机在线、有纸、墨盒正常);

  2. SCU创建打印任务:定义Film Session(整体任务参数,如打印份数)和Film Box(单张胶片的图像与排版参数,如图像位置、窗宽窗位);

  3. SCU发送DICOM图像到指定的Film Box

  4. SCU触发打印指令,SCP将图像输出到胶片打印机。

3. 工具实操:用pydicom模拟打印请求(需真实打印机支持)

由于医用胶片打印机通常需要专用硬件(且配置复杂),本文通过模拟场景演示打印服务的核心逻辑(使用pydicom构造打印请求,但实际执行需连接真实打印机)。

步骤1:环境准备

确保你有一个支持DICOM打印服务的医用胶片打印机(如Agfa DryStar 5503、Konica Minolta Bizhub Medical),并已配置好DICOM网络参数(IP地址、端口通常为104或自定义)。

步骤2:代码模拟打印任务(关键逻辑)

以下代码展示了如何用pydicom构造一个打印请求(包含Film Session和Film Box),并发送到打印机的DICOM服务端口(假设打印机IP为192.168.1.100,端口为104):

from pynetdicom import AE, PrintManagementPresentationContexts
from pynetdicom.sop_class import BasicFilmSessionSOPClass, BasicFilmBoxSOPClass, BasicGrayscaleImageBoxSOPClass
import pydicom

# 1. 配置AE(应用实体)——打印请求发起方(SCU)
ae = AE()

# 添加支持的上下文:打印会话、打印盒、图像盒
ae.add_requested_context(BasicFilmSessionSOPClass)  # 胶片会话
ae.add_requested_context(BasicFilmBoxSOPClass)     # 胶片盒(单张胶片)
ae.add_requested_context(BasicGrayscaleImageBoxSOPClass)  # 灰度图像盒(单张图像)

# 2. 读取要打印的DICOM图像(例如一张X光片)
dcm_file = "xray_example.dcm"  # 替换为你的X光/DICOM文件路径
dataset = pydicom.dcmread(dcm_file)

# 3. 连接到打印机的DICOM服务端口(假设打印机IP和端口)
printer_ip = '192.168.1.100'  # 替换为你的打印机IP
printer_port = 104            # 通常为104(需根据打印机配置调整)

# --- 步骤1:创建Film Session(胶片会话,定义整体任务参数)---
film_session_ds = pydicom.Dataset()
film_session_ds.SOPClassUID = BasicFilmSessionSOPClass
film_session_ds.SOPInstanceUID = pydicom.uid.generate_uid()  # 本次会话的唯一ID
film_session_ds.ConfigurationStatus = 'CONFIRMED'  # 会话状态(已确认)
film_session_ds.PrintPriority = 'MEDIUM'           # 打印优先级(LOW/MEDIUM/HIGH)
film_session_ds.MediumType = 'BLUE-FILM'           # 胶片类型(如蓝片、白片)
film_session_ds.FilmDestination = 'MAGAZINE'       # 胶片输出位置(如暗盒、托盘)
film_session_ds.FilmSessionLabel = '急诊X光打印'    # 会话标签(可选)

# 关联到打印机并发送Film Session创建请求
assoc_print = ae.associate(printer_ip, printer_port)
if assoc_print.is_established:
    print("[步骤1] 正在向打印机创建胶片会话...")
    status_session = assoc_print.send_n_create(
        film_session_ds.SOPInstanceUID,
        BasicFilmSessionSOPClass,
        film_session_ds
    )
    if status_session and status_session.Status in [0x0000, 0xB000]:
        print("[步骤1] 胶片会话创建成功!Session UID:", film_session_ds.SOPInstanceUID)
        session_uid = film_session_ds.SOPInstanceUID
    else:
        print("[步骤1] 胶片会话创建失败!状态码:", status_session.Status if status_session else "无响应")
        assoc_print.release()
        exit()
    assoc_print.release()
else:
    print("[步骤1] 无法连接到打印机!请检查IP和端口")
    exit()

# --- 步骤2:创建Film Box(胶片盒,定义单张胶片的图像与排版)---
film_box_ds = pydicom.Dataset()
film_box_ds.SOPClassUID = BasicFilmBoxSOPClass
film_box_ds.SOPInstanceUID = pydicom.uid.generate_uid()  # 本次胶片盒的唯一ID
film_box_ds.ImageDisplayFormat = 'STANDARD\1,1'         # 图像排版格式(1张图像占满胶片)
film_box_ds.AnnotationDisplayFormatID = 'ANNOTATION'     # 是否显示标注
film_box_ds.FilmOrientation = 'PORTRAIT'                 # 胶片方向(竖向/横向)
film_box_ds.FilmSizeID = '8INX10IN'                      # 胶片尺寸(如8x10英寸)
film_box_ds.MagnificationType = 'NONE'                   # 放大类型(无放大)
film_box_ds.RequestedImageSize = '2048x2048'             # 请求的图像尺寸(像素)
film_box_ds.ConfigurationStatus = 'CONFIRMED'            # 胶片盒状态

# 关联到之前创建的Film Session(通过Session SOP Instance UID)
film_box_ds.ReferencedFilmSessionSOPInstanceUID = session_uid

# --- 步骤3:添加图像到Film Box(定义要打印的DICOM图像)---
image_box_ds = pydicom.Dataset()
image_box_ds.SOPClassUID = BasicGrayscaleImageBoxSOPClass
image_box_ds.SOPInstanceUID = pydicom.uid.generate_uid()  # 本次图像盒的唯一ID
image_box_ds.ImagePosition = 1                           # 图像在胶片上的位置(1表示第一张)
image_box_ds.PresentationLUTShape = 'IDENTITY'           # 灰度映射(默认线性)
image_box_ds.BasicGrayscaleImageSequence = [dataset]     # 包含要打印的DICOM图像数据集

# 将图像盒添加到胶片盒
film_box_ds.BasicImageBoxSequence = [image_box_ds]

# 发送Film Box创建请求到打印机
assoc_print = ae.associate(printer_ip, printer_port)
if assoc_print.is_established:
    print("[步骤2] 正在向打印机创建胶片盒(包含图像)...")
    status_box = assoc_print.send_n_create(
        film_box_ds.SOPInstanceUID,
        BasicFilmBoxSOPClass,
        film_box_ds
    )
    if status_box and status_box.Status in [0x0000, 0xB000]:
        print("[步骤2] 胶片盒创建成功!Box UID:", film_box_ds.SOPInstanceUID)
        box_uid = film_box_ds.SOPInstanceUID
    else:
        print("[步骤2] 胶片盒创建失败!状态码:", status_box.Status if status_box else "无响应")
        assoc_print.release()
        exit()
    assoc_print.release()
else:
    print("[步骤2] 无法连接到打印机!")
    exit()

# --- 步骤3:触发打印指令(实际发送打印命令)---
# (注:完整实现需调用N-ACTION或N-EVENT-REPORT触发打印,此处简化为提示)
print("[步骤3] 胶片盒已创建,理论上可触发打印!实际需调用打印机的'打印'动作(如N-ACTION Type ID=2)")
print("(模拟结束:真实环境需连接支持DICOM打印的医用胶片打印机,并配置正确的IP/端口)")

代码说明

  • Film Session:定义打印任务的“全局参数”(如胶片类型、打印份数、输出位置);

  • Film Box:定义单张胶片的“局部参数”(如图像排版、尺寸、包含的DICOM图像);

  • BasicGrayscaleImageBoxSOPClass:指定要打印的DICOM图像(需包含像素数据)。

📌 关键限制

  • 本代码仅演示了打印请求的构造逻辑(创建Film Session和Film Box),实际触发打印需额外调用N-ACTION(Action Type ID=2表示“打印”)

  • 真实环境中必须连接支持DICOM打印的医用胶片打印机(普通打印机无法解析DICOM打印协议),且需配置正确的IP、端口和认证信息;

  • 大多数开发者更常用“软拷贝”(显示器查看)而非打印,因此打印服务在现代PACS中使用频率较低,但在特定场景(如手术室、基层医院)仍有不可替代性。

DICOM的高级服务(存储提交与打印)虽然不如存储、网络传输那样常用,但在关键场景下保障了医学影像的可靠性与拓展性

服务类型

核心价值

典型应用场景

存储提交服务

确保DICOM图像被目标系统(如PACS)真正持久化存储,避免数据丢失

法规合规、重要检查(如急诊CT)、云端备份验证

打印服务

将医学影像输出到医用胶片,生成物理硬拷贝

手术室参考、基层医院传统阅片、教学演示

对于开发者而言,掌握这些高级服务意味着可以:

  • 构建更可靠的医疗影像系统(如基层医院上传影像后,通过存储提交确认云端PACS已保存,再删除本地副本);

  • 满足特殊场景需求(如为患者提供胶片打印服务,或集成医用打印机到PACS工作流);

  • 深入理解DICOM标准的全面能力(不仅是基础存储与传输,还包括质量保障与扩展应用)。