pynetdicom C_GET 失败,没有“CT 图像存储”的表示上下文已被 SCP 角色的对等方接受

问题描述

我第一次尝试使用 pynetdicom。我已经在我的 PC 上安装了它和 ConQuest DICOM 服务器。我能够使 pynetdicom echo 示例正常工作,但是当我尝试 pynetdicom Storage SCU 示例 (https://pydicom.github.io/pynetdicom/stable/examples/storage.html) 时,它失败了:

No presentation context for 'CT Image Storage' has been accepted by the peer for the SCP role

问题是,就我所见,该示例已经协商了 CT 图像存储上下文。这是日志:

pydev debugger: starting (pid: 39132)
I: Requesting Association
D: Request Parameters:
D: ======================= OUTGOING A-ASSOCIATE-RQ PDU ========================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.5.3
D: Our Implementation Version Name:   PYNETDICOM_153
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Our Max PDU Receive Size:    16382
D: Presentation Contexts:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Patient Root Query/Retrieve information Model - GET
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntaxes:
D:       =Implicit VR Little Endian
D:       =Explicit VR Little Endian
D:       =Deflated Explicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        3 (Proposed)
D:     Abstract Syntax: =CT Image Storage
D:     Proposed SCP/SCU Role: SCP
D:     Proposed Transfer Syntaxes:
D:       =Implicit VR Little Endian
D:       =Explicit VR Little Endian
D:       =Deflated Explicit VR Little Endian
D:       =Explicit VR Big Endian
D: Requested Extended Negotiation: None
D: Requested Common Extended Negotiation: None
D: Requested Asynchronous Operations Window Negotiation: None
D: Requested User Identity Negotiation: None
D: ========================== END A-ASSOCIATE-RQ PDU ==========================
D: Accept Parameters:
D: ======================= INCOMING A-ASSOCIATE-AC PDU ========================
D: Their Implementation Class UID:    1.2.826.0.1.3680043.2.135.1066.101
D: Their Implementation Version Name: 1.5.0/WIN32
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Their Max PDU Receive Size:  32768
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =Patient Root Query/Retrieve information Model - GET
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        3 (Accepted)
D:     Abstract Syntax: =CT Image Storage
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ========================== END A-ASSOCIATE-AC PDU ==========================
I: Association Accepted
I: Sending Get Request: MsgiD 1
I: 
I: # Request Identifier
I: (0008,0052) CS [PATIENT]                                # 1 QueryRetrieveLevel
I: (0010,0010) PN [Wrench,Fred]                           # 1 PatientName
I: 
D: ========================== OUTGOING DimsE MESSAGE ==========================
D: Message Type                  : C-GET RQ
D: Message ID                    : 1
D: Affected SOP Class UID        : Patient Root Query/Retrieve information Model - GET
D: Identifier                    : Present
D: Priority                      : Low
D: ============================ END DimsE MESSAGE =============================
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Received Store Request
D: ========================== INCOMING DimsE MESSAGE ==========================
D: Message Type                  : C-STORE RQ
D: Presentation Context ID       : 3
D: Message ID                    : 59647
D: Affected SOP Class UID        : CT Image Storage
D: Affected SOP Instance UID     : 1.3.6.1.4.1.33997.3232235894.19152.1608672548.1.1.1
D: Move Originator               : PYNETDICOM
D: Data Set                      : Present
D: Priority                      : Medium
D: ============================ END DimsE MESSAGE =============================
E: No presentation context for 'CT Image Storage' has been accepted by the peer for the SCP role
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
D: ========================== INCOMING DimsE MESSAGE ==========================
D: Message Type                  : C-GET RSP
D: Presentation Context ID       : 1
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Patient Root Query/Retrieve information Model - GET
D: Remaining Sub-operations      : 0
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 5
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: Status                        : 0xFE00
D: ============================ END DimsE MESSAGE =============================
D: 
I: Get SCP Result: 0xFE00 (Cancel)
I: Sub-Operations Remaining: 0,Completed: 0,Failed: 5,Warning: 0
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
C-GET query status: 0xfe00
I: Releasing Association

因此日志显示 CT 图像存储上下文已被接受,但 SCP/SCU 角色:认,无论这意味着什么。当我通过 pynetdicom 处理进行调试时,我看到有一个 CT 上下文,但仅适用于 SCP 角色。 ConQuest 显示它已发送 DICOM 文件,然后断开连接。

当然还有代码(仅对 pynetdicom 示例进行了微小更改:

from pydicom.dataset import Dataset

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(CtimageStorage)

# Create an SCP/SCU Role Selection Negotiation item for CT Image Storage
role = build_role(CtimageStorage,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 = 'PATIENT'
ds.PatientName = 'Wrench,Fred'

# Associate with peer AE at IP 127.0.0.1 and port 11112
assoc = ae.associate('127.0.0.1',5679,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')

我一直在谷歌上搜索,直到我的 googler 感到疼痛为止。

解决方法

在 DICOM C-GET 中,关联请求者必须发送 SCP/SCU role selection 项,为每个 SOP 类别(SCU、SCP 或 SCU/SCP)提议特定角色。关联接受者然后用它自己的角色选择项进行响应,这些选项表明它接受哪个提议的角色。

问题似乎是,当 Conquest 作为关联接受者时,它不会发送任何预期的响应,因此 pynetdicom 假定您正在获得默认角色,因为这是 DICOM 标准所要求的,当没有收到回复

如果关联接受者没有返回 SCP/SCU 角色选择项,则关联请求者的角色应为 SCU,关联接受者的角色应为 SCP

这在调试日志中可见,其中说明了接受的 SCP/SCU 角色(它说的是 SCP 而不是默认值):

D:   Context ID:        3 (Accepted)
D:     Abstract Syntax: =CT Image Storage
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian

这也是为什么您会收到一个异常,指出 CT Image Storage 没有接受任何 SCP 角色。

这看起来与 this issue 是相同的问题,并且可以通过 forcing pynetdicom to treat the context 以类似的方式解决以支持 SCP 角色:

if assoc.is_established:
    for cx in assoc.accepted_contexts:
        cx._as_scp = True

    # 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')

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...