I am trying to use pynetdicom for the first time. I have it and the ConQuest DICOM server installed on my PC. I am able to get the pynetdicom echo example working, but when I try the pynetdicom Storage SCU example (https://pydicom.github.io/pynetdicom/stable/examples/storage.html) it fails with:

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

Problem is, the example has negotiated the CT Image Storage context as far as I can see. Here's the logging:

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

So the log shows that the CT Image Storage Context was accepted, but with SCP/SCU Role: Default, whatever that means. When I debug through the pynetdicom processing I see that there is a CT context, but only for the SCP role. ConQuest shows it's sent the DICOM file and then got disconnected.

And of course the code (only minor changes from the pynetdicom example:

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

I've googled and googled until my googler was sore.

1

There are 1 best solutions below

0
On

In DICOM C-GET the association requestor must send SCP/SCU role selection items proposing particular roles for each SOP Class (SCU, SCP or SCU/SCP). The association acceptor then responds with its own role selection items which indicate which of the proposed roles it accepts.

The problem seems to be that the when Conquest acts as an association acceptor it doesn't send any of the expected responses, so pynetdicom assumes that you're getting the default roles, because that's what's required by the DICOM Standard when no response is received

If the SCP/SCU Role Selection item is not returned by the Association-acceptor then the role of the Association-requester shall be SCU and the role of the Association-acceptor shall be SCP

This is visible in the debug log where it states the Accepted SCP/SCU Role (it'd say SCP instead of 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

And is also why you're getting an exception stating that no SCP role has been accepted for CT Image Storage.

This looks like the same problem as this issue and can be worked around in a similar manner by forcing pynetdicom to treat the context as supporting the SCP role:

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