2

我第一次尝试使用 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')

我一直在谷歌搜索,直到我的谷歌用户感到疼痛。

4

1 回答 1

0

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

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

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

这在调试日志中可见,它声明了 Accepted SCP/SCU Role(它会说 SCP 而不是 Default):

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 图像存储没有接受任何 SCP 角色。

这看起来与此问题相同,可以通过强制 pynetdicom 将上下文视为支持 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')
于 2021-01-03T22:50:33.027 回答