每日鸡汤: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数据(可以在开源的网站下载)
这样就可以在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')
代码详细解读
-
导入所需的库和模块,包括
Dataset
类、AE
类、debug_logger
函数以及 SOP 类(PatientRootQueryRetrieveInformationModelFind
)。 -
启用调试日志,以便在控制台中查看详细的通信信息。
-
创建一个
AE
实例(应用实体)来表示客户端,并为其设置应用实体标题(AE Title)为'PYNETDICOM'
。 -
使用
add_requested_context
方法将查询上下文(SOP 类)添加到 AE 实例中,指定了PatientRootQueryRetrieveInformationModelFind
,表示我们要执行的是患者查询操作。 -
创建查询数据集(Identifier),使用
Dataset
类来设置查询条件。在这里,我们设置了患者 ID 和查询级别。 -
使用
ae.associate
方法与目标 DICOM 服务器建立关联(Association),传递目标服务器的 IP 地址和端口号。 -
如果关联建立成功(
is_established
为True
),则发送 C-FIND 请求。接收到的响应包含查询结果。 -
循环处理查询响应,打印出每个响应的查询状态(
status.Status
)和标识符数据集(identifier
)。 -
最后,释放关联(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')
代码详细解读:
-
导入所需的库和模块,包括
Dataset
类、XRayAngiographicImageStorage
、AE
类、debug_logger
函数以及所需的 SOP 类(PatientRootQueryRetrieveInformationModelGet
和CTImageStorage
)。 -
启用调试日志,以便在控制台中查看详细的通信信息。
-
实现了
evt.EVT_C_STORE
事件的处理函数handle_store
,该函数在收到 C-STORE 请求时负责将数据集保存到本地文件。 -
创建一个
AE
实例(应用实体),表示客户端。 -
使用
add_requested_context
方法将查询和存储上下文添加到 AE 实例中,分别是PatientRootQueryRetrieveInformationModelGet
和特定图像类型的上下文。 -
创建一个用于指定图像类型的 SCP/SCU 角色选择协商项。
-
创建查询数据集(Identifier),用于指定查询条件,如患者 ID、检查实例 UID 和系列实例 UID。
-
使用
ae.associate
方法与目标 DICOM 服务器建立关联(Association),传递服务器的 IP 地址和端口号,同时指定了角色选择协商项。 -
如果关联建立成功,使用
assoc.send_c_get
方法发送查询标识符,并处理收到的查询响应。 -
打印每个查询响应的查询状态(
status.Status
)。 -
最后,释放关联以断开与服务器的连接。
注意事项:
一定要注意请求影像的类型,不然会请求不到数据:
# 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)