Python与DICOM服务器(Orthanc)进行通信

每日鸡汤:I once loved , I lost and finally I turned around and walk along. I got my final result I need.

因为需要从DICOM服务器请求病人的影相数据,所以才有了这篇BLOG。

1. Orthanc配置

打开之前文章中的 Configuration.json ,修改DicomAet、DicomPort 以及DicomModalities。在DicomModalities这里面授权允许访问的IP和AETitle。

{
    "Name": "TEST",
    "HttpPort": 8042,
    "DicomAet": "test",
    "DicomPort": 104,
    "DicomModalities":{
      "AET": "PYNETDICOM",
      "Host": "127.0.0.1",
      "Port": 104,
      "AllowEcho": true, // 是否允许 Echo
      "AllowFind": true, // 是否允许 Find
      "AllowGet": true, // 是否允许 Get
      "AllowMove": true, // 是否允许 Move
      "AllowStore": true, // 是否允许 Store
      "AllowStorageCommitment": true, // 是否允许 Storage Commitment
      "AllowTranscoding": true, // 是否允许转码
      "UseDicomTls": false, // 是否使用 DICOM TLS
      "LocalAet": "", // 本地 AET(可选)
      "Timeout": 30 // 超时时间(秒)
     }
    
}

在浏览器输入 http://localhost:8042/ 链接进入ORTHANC然后手动上传病人的dcm数据(可以在开源的网站下载)

http://localhost:8042/

这样就可以在Orthanc查询到数据啦!

 

2. 从DICOM查询病人数据

DICOM 查询/检索服务为服务用户提供了一种查询由 QR SCP 管理的 SOP 实例的机制。QR (Find) SOP 类允许 SCU 接收与请求的查询匹配的属性列表。这是通过 DIMSE C-FIND 服务完成的。详见 pynetdicom 开发文档。首先安装pynetdicom 

pip install pynetdicom 

然后与一个对等的 DICOM 应用实体相关联,并请求 SCP 搜索具有匹配 ID为556342B的患者ID的 SOP 实例,使用患者根查询/检索信息模型-在“ PATIENT”级别查找。

from pydicom.dataset import Dataset
from pynetdicom import AE, debug_logger
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelFind

# 启用调试日志
debug_logger()

# 创建一个 Application Entity (AE) 实例
ae = AE(ae_title='PYNETDICOM')

# 添加所需的查询上下文
ae.add_requested_context(PatientRootQueryRetrieveInformationModelFind)

# 创建查询数据集(Identifier)
ds = Dataset()
ds.PatientID = '556342B'  # 设置查询的患者 ID
ds.QueryRetrieveLevel = 'PATIENT'  # 设置查询的级别为患者级别
#ds.StudyInstanceUID = ''
#ds.SeriesInstanceUID = ''

# 与目标 DICOM 服务器建立关联(Association)
assoc = ae.associate("127.0.0.1", 104)  # 连接到 IP 地址为 127.0.0.1,端口为 104 的 DICOM 服务器

if assoc.is_established:
    # 发送 C-FIND 请求
    responses = assoc.send_c_find(ds, PatientRootQueryRetrieveInformationModelFind, ae_title='PYNETDICOM')
    for (status, identifier) in responses:
        if status and identifier:
            #print(dataset.PatientID)
            #print(dataset.PatientName)
            #print(dataset.StudyInstanceUID)
            #print(dataset.SeriesInstanceUID)
            print('C-FIND query status: 0x{0:04X}'.format(status.Status))
        else:
            print('Connection timed out, was aborted or received invalid response')

    # 释放关联
    assoc.release()
else:
    print('Association rejected, aborted or never connected')

代码详细解读

  1. 导入所需的库和模块,包括 Dataset 类、AE 类、debug_logger 函数以及 SOP 类(PatientRootQueryRetrieveInformationModelFind)。

  2. 启用调试日志,以便在控制台中查看详细的通信信息。

  3. 创建一个 AE 实例(应用实体)来表示客户端,并为其设置应用实体标题(AE Title)为 'PYNETDICOM'

  4. 使用 add_requested_context 方法将查询上下文(SOP 类)添加到 AE 实例中,指定了 PatientRootQueryRetrieveInformationModelFind,表示我们要执行的是患者查询操作。

  5. 创建查询数据集(Identifier),使用 Dataset 类来设置查询条件。在这里,我们设置了患者 ID 和查询级别。

  6. 使用 ae.associate 方法与目标 DICOM 服务器建立关联(Association),传递目标服务器的 IP 地址和端口号。

  7. 如果关联建立成功(is_establishedTrue),则发送 C-FIND 请求。接收到的响应包含查询结果。

  8. 循环处理查询响应,打印出每个响应的查询状态(status.Status)和标识符数据集(identifier)。

  9. 最后,释放关联(Association)以断开与服务器的连接。

注意事项:

如果你进行的是“ PATIENT”级别查找。你最终成功的响应,但是不能访问这些响应中的其他病人信息,比如我想要从数据里提取患者姓名:

responses = assoc.send_c_find(ds, PatientRootQueryRetrieveInformationModelFind)
        for (status, dataset) in responses:
            if status and dataset:
                print(dataset.PatientID)
                print(dataset.PatientName)
            else:
                print("Empty dataset received")
        assoc.release()

将会报错:

AttributeError: 'Dataset' object has no attribute 'PatientName'

那是因为在进行声明时需要给ds添加更多的属性,这样你才能获取到更多的结果:

ds = Dataset()
     ds.QueryRetrieveLevel = QueryRetrieveLevel
     ds.PatientName = ''
     ds.PatientID = PatientID
     ds.StudyInstanceUID = ''
     ds.SeriesInstanceUID = ''

如果想获取到病人的影相数据,必须根据在 C-FIND 响应中收到的标识符(如StudyInstanceUID和SeriesInstanceUID)执行 C-MOVE (或 C-GET)。

执行上述代码将获得如下信息,跟你的dcm数据一致,这只是一个示例:

556342B
Rubo DEMO
1.3.12.2.1107.5.4.3.123456789012345.19950922.121803.6
1.3.12.2.1107.5.4.3.123456789012345.19950922.121803.8

3. 从DICOM获取病人的影像数据

与一个对等的 DICOM 应用实体关联,并请求检索患者 ID 为556342B的患者的所有 CT 数据集,该数据集属于具有StudyInstanceUID=1.3.12.2.1107.5.4.3.123456789012345.19950922.121803.6和SeriesInstanceUID =1.3.12.2.1107.5.4.3.123456789012345.19950922.121803.8的XRay图片。

from pydicom.dataset import Dataset
from pydicom.uid import XRayAngiographicImageStorage
from pynetdicom import AE, evt, build_role, debug_logger
from pynetdicom.sop_class import (
    PatientRootQueryRetrieveInformationModelGet,
    CTImageStorage
)

debug_logger()

# Implement the handler for evt.EVT_C_STORE
def handle_store(event):
    """Handle a C-STORE request event."""
    ds = event.dataset
    ds.file_meta = event.file_meta

    # Save the dataset using the SOP Instance UID as the filename
    ds.save_as(ds.SOPInstanceUID, write_like_original=False)

    # Return a 'Success' status
    return 0x0000

handlers = [(evt.EVT_C_STORE, handle_store)]

# Initialise the Application Entity
ae = AE()

# Add the requested presentation contexts (QR SCU)
ae.add_requested_context(PatientRootQueryRetrieveInformationModelGet)
# Add the requested presentation context (Storage SCP)
ae.add_requested_context(XRayAngiographicImageStorage) #根据影像类型进行更换如CTImageStorage

# Create an SCP/SCU Role Selection Negotiation item for CT Image Storage
role = build_role(XRayAngiographicImageStorage, scp_role=True) #与上面一致

# Create our Identifier (query) dataset
# We need to supply a Unique Key Attribute for each level above the
#   Query/Retrieve level
ds = Dataset()
ds.QueryRetrieveLevel = 'SERIES'
# Unique key for PATIENT level
ds.PatientID = '556342B'
# Unique key for STUDY level #这里的ID是刚刚find查询出来的
ds.StudyInstanceUID = '1.3.12.2.1107.5.4.3.123456789012345.19950922.121803.6'
# Unique key for SERIES level
ds.SeriesInstanceUID = '1.3.12.2.1107.5.4.3.123456789012345.19950922.121803.8'

# Associate with peer AE at IP 127.0.0.1 and port 11112
assoc = ae.associate("127.0.0.1", 104, ext_neg=[role], evt_handlers=handlers)

if assoc.is_established:
    # Use the C-GET service to send the identifier
    responses = assoc.send_c_get(ds, PatientRootQueryRetrieveInformationModelGet)
    for (status, identifier) in responses:
        if status:
            print('C-GET query status: 0x{0:04x}'.format(status.Status))
        else:
            print('Connection timed out, was aborted or received invalid response')

    # Release the association
    assoc.release()
else:
    print('Association rejected, aborted or never connected')

代码详细解读:

  1. 导入所需的库和模块,包括 Dataset 类、XRayAngiographicImageStorageAE 类、debug_logger 函数以及所需的 SOP 类(PatientRootQueryRetrieveInformationModelGetCTImageStorage)。

  2. 启用调试日志,以便在控制台中查看详细的通信信息。

  3. 实现了 evt.EVT_C_STORE 事件的处理函数 handle_store,该函数在收到 C-STORE 请求时负责将数据集保存到本地文件。

  4. 创建一个 AE 实例(应用实体),表示客户端。

  5. 使用 add_requested_context 方法将查询和存储上下文添加到 AE 实例中,分别是 PatientRootQueryRetrieveInformationModelGet 和特定图像类型的上下文。

  6. 创建一个用于指定图像类型的 SCP/SCU 角色选择协商项。

  7. 创建查询数据集(Identifier),用于指定查询条件,如患者 ID、检查实例 UID 和系列实例 UID。

  8. 使用 ae.associate 方法与目标 DICOM 服务器建立关联(Association),传递服务器的 IP 地址和端口号,同时指定了角色选择协商项。

  9. 如果关联建立成功,使用 assoc.send_c_get 方法发送查询标识符,并处理收到的查询响应。

  10. 打印每个查询响应的查询状态(status.Status)。

  11. 最后,释放关联以断开与服务器的连接。

注意事项:

一定要注意请求影像的类型,不然会请求不到数据:

# Add the requested presentation contexts (QR SCU)
ae.add_requested_context(PatientRootQueryRetrieveInformationModelGet)
# Add the requested presentation context (Storage SCP)
ae.add_requested_context(XRayAngiographicImageStorage) #根据影像类型进行更换如CTImageStorage

此时Orthanc服务器输出如下:

E0812 20:09:25.063563 OrthancException.cpp:61] Error in the network protocol: C-GET SCP: storeSCU: No presentation context for: (XA) 1.2.840.10008.5.1.4.1.1.12.1
E0812 20:09:25.066359 GetScp.cpp:272] IGetRequestHandler Failed: Error in the network protocol

 最后病人相应的图片数据将会保存到本地,可以利用dcmread函数进行解析:

ds=dicom.dcmread(path)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值