pydicom / pynetdicom

A Python implementation of the DICOM networking protocol
https://pydicom.github.io/pynetdicom
MIT License
500 stars 176 forks source link

Pydicom CMOVE Fails with only pending status #926

Closed blissdismissed closed 5 months ago

blissdismissed commented 5 months ago

Hi I am trying to do a very basic transfer of a series of dicom images using pydicom. Performing a CMOVE the first part is successful, the association is established with the PACS server, and I see the correct number of images in the queue for the second part of a CMOVE operation, the CStore.

However when it starts actually retrieving the images it fails without an error, moves on to the next image and then at the end throws a: -MOVE query status: 0xb000

Here is my main.py with bogus IP and UIDs


from pynetdicom import AE, evt, StoragePresentationContexts, debug_logger
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelMove

from pydicom.uid import ExplicitVRLittleEndian
from pynetdicom.sop_class import CTImageStorage

debug_logger()

def handle_store(event):
    """Handle EVT_C_STORE events."""
    ds = event.dataset
    ds.file_meta = event.file_meta
    ds.save_as(ds.SOPInstanceUID, write_like_original=False)

    return 0x0000

handlers = [(evt.EVT_C_STORE, handle_store)]

# Initialise the Application Entity
ae = AE()

# Add a requested presentation context
ae.add_requested_context(PatientRootQueryRetrieveInformationModelMove)
ae.add_supported_context(CTImageStorage,ExplicitVRLittleEndian)

# Add the Storage SCP's supported presentation contexts
ae.supported_contexts = StoragePresentationContexts

# Start our Storage SCP in non-blocking mode
ae.ae_title = 'MyComputer_AETitle'
scp = ae.start_server(("127.0.0.1", 4410), block=False, evt_handlers=handlers)

# Create out identifier (query) dataset
ds = Dataset()
ds.QueryRetrieveLevel = 'SERIES'
# Unique key for PATIENT level
ds.PatientID = '38319111'
# Unique key for STUDY level
ds.StudyInstanceUID = '1.2.220.113619.2.452.3.481098267.137.1712573337.111'
# Unique key for SERIES level
ds.SeriesInstanceUID = '1.2.220.113619.2.452.3.481098267.137.1712573337.111.3'

# Associate with peer AE at IP 127.0.0.1 and port 11112
assoc = ae.associate("172.88.111.15", 2010, ae_title="PACS_AETitle")

if assoc.is_established:
    # Use the C-MOVE service to send the identifier
    responses = assoc.send_c_move(ds, 'MYComputer_AETitle', PatientRootQueryRetrieveInformationModelMove)

    for (status, identifier) in responses:
        if status:
            print('C-MOVE 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')

# Stop our Storage SCP
scp.shutdown()```

This is what the log shows:

`D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Move SCP Response: 75 - 0xFF00 (Pending)
I: Sub-Operations Remaining: 2, Completed: 0, Failed: 71, Warning: 0
C-MOVE query status: 0xff00
D: ========================== INCOMING DIMSE MESSAGE ==========================
D: Message Type                  : C-MOVE RSP
D: Presentation Context ID       : 1
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Patient Root Query/Retrieve Information Model - MOVE
D: Remaining Sub-operations      : 1
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 72
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: Status                        : 0xFF00
D: ============================ END DIMSE MESSAGE =============================
D:
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Move SCP Response: 76 - 0xFF00 (Pending)
I: Sub-Operations Remaining: 1, Completed: 0, Failed: 72, Warning: 0
C-MOVE query status: 0xff00
D: ========================== INCOMING DIMSE MESSAGE ==========================
D: Message Type                  : C-MOVE RSP
D: Presentation Context ID       : 1
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Patient Root Query/Retrieve Information Model - MOVE
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 73
D: Warning Sub-operations        : 0
D: Identifier                    : Present
D: Status                        : 0xB000
D: ============================ END DIMSE MESSAGE =============================
D:
I: Move SCP Result: 0xB000 (Warning)
I: Sub-Operations Remaining: 0, Completed: 0, Failed: 73, Warning: 0
D: pydicom.read_dataset() TransferSyntax="Little Endian Explicit"
I:
I: # Response Identifier
I: (0008, 0058) Failed SOP Instance UID List        UI: Array of 73 elements
I:
C-MOVE query status: 0xb000
I: Releasing Association```

Any ideas how to deeper troubleshoot to figure out why the CStore is failing? I put a breakpoint in my handle_store(event) event handler but doesn't even get triggered, so I'm a bit at a loss. The AE Title, IP, and port are registered with my PACS system. Actually the CMOVE works when I send it to an Orthanc instance on the computer with the same port configuration.

Is this a threading issue? I'm running the code from the computer trying to receive the images and the creation of the storage SCP in the python script is in the same script making the CMOVE request.

Also are the files just going in the same directory at my main.py file? I don't see anywhere to enter a path in the documentation.

Thanks for any help you can offer!

scaramallion commented 5 months ago

If handle_store isn't being called then I really recommend you check what has been configured with PACS. Both the Move SCU AET/IP/Port and the destination Store SCP AET/IP/Port need to be registered.

blissdismissed commented 5 months ago

Thank you @scaramallion I had the configured saved in PACS. What seemed to do the trick was how the Store SCP was being configured. By changing the localhost IP from: scp = ae.start_server(("127.0.0.1", 4410), block=False, evt_handlers=handlers) to scp = ae.start_server(("0.0.0.0", 4410), block=False, evt_handlers=handlers)

The transfer of the files was accepted and stored in the same directory in which the script was run