pydicom / pynetdicom

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

send_c_move do not trigger on_c_store if move_aet = calling_aet #171

Closed esthevaov closed 5 years ago

esthevaov commented 6 years ago

Hello Mr @scaramallion , I thought you might be able to help me. I am having this bug where I send the C-MOVE request, but the on_c_store function is not being called when the scp server answers my request.

Expected behaviour

I based this code on the movescu app, so it's expected that my AE would be able to get an association from the scp server an trigger the on_c_store function to save the file.

Actual behaviour

But, apparently, the move request is made and the association never changes to be able to receive C-STORE commands.

Steps to reproduce

So I am using this code as MOVE client, maybe the on_c_store function is not fully working, but it is never called...

################################ CODE ####################################

###!/usr/bin/env python

"""
    A getscu application.
"""

import argparse
import logging
import os
import socket
import sys
import time

from pydicom.dataset import Dataset, FileDataset
from pydicom.filewriter import write_file
from pydicom.uid import ExplicitVRLittleEndian, ImplicitVRLittleEndian, \
                        ExplicitVRBigEndian, UID

from pynetdicom3 import AE, StorageSOPClassList, QueryRetrieveSOPClassList
from pynetdicom3 import pynetdicom_uid_prefix
from pynetdicom3.pdu_primitives import SCP_SCU_RoleSelectionNegotiation

logger = logging.Logger('getscu')
stream_logger = logging.StreamHandler()
formatter = logging.Formatter('%(levelname).1s: %(message)s')
stream_logger.setFormatter(formatter)
logger.addHandler(stream_logger)
logger.setLevel(logging.ERROR)

logger.setLevel(logging.DEBUG)
pynetdicom_logger = logging.getLogger('pynetdicom3')
pynetdicom_logger.setLevel(logging.DEBUG)

logger.debug('$getscu.py v{0!s} {1!s} $'.format('0.1.0', '2016-02-15'))
logger.debug('')

MyAETitle = "MyAETitle"
ServerIP = '192.168.0.1'
ServerPort = 4096
ServerTitle = 'server-dicom'

ae = AE(ae_title=MyAETitle,
        port=0,
        scu_sop_class=QueryRetrieveSOPClassList,
        scp_sop_class=StorageSOPClassList)

### Set the extended negotiation SCP/SCU role selection to allow us to receive
###   C-STORE requests for the supported SOP classes
ext_neg = []
for context in ae.presentation_contexts_scu:
    tmp = SCP_SCU_RoleSelectionNegotiation()
    tmp.sop_class_uid = context.AbstractSyntax
    tmp.scu_role = False
    tmp.scp_role = True

    ext_neg.append(tmp)

### Request association with remote
assoc = ae.associate(ServerIP, ServerPort, ServerTitle, ext_neg=ext_neg)

### Create query dataset
d = Dataset()
d.StudyInstanceUID = '1.2.392.200036.9116.2.5.1.37.2418728817.1457303667.507231'
d.QueryRetrieveLevel = "STUDY"
query_model = 'S'

def on_c_store(dataset):
    """
    Function replacing ApplicationEntity.on_store(). Called when a dataset is
    received following a C-STORE. Write the received dataset to file

    Parameters
    ----------
    dataset - pydicom.Dataset
        The DICOM dataset sent via the C-STORE

    Returns
    -------
    status : pynetdicom.sop_class.Status or int
        A valid return status code, see PS3.4 Annex B.2.3 or the
        StorageServiceClass implementation for the available statuses
    """
    mode_prefix = 'UN'
    mode_prefixes = {'CT Image Storage' : 'CT',
                     'Enhanced CT Image Storage' : 'CTE',
                     'MR Image Storage' : 'MR',
                     'Enhanced MR Image Storage' : 'MRE',
                     'Positron Emission Tomography Image Storage' : 'PT',
                     'Enhanced PET Image Storage' : 'PTE',
                     'RT Image Storage' : 'RI',
                     'RT Dose Storage' : 'RD',
                     'RT Plan Storage' : 'RP',
                     'RT Structure Set Storage' : 'RS',
                     'Computed Radiography Image Storage' : 'CR',
                     'Ultrasound Image Storage' : 'US',
                     'Enhanced Ultrasound Image Storage' : 'USE',
                     'X-Ray Angiographic Image Storage' : 'XA',
                     'Enhanced XA Image Storage' : 'XAE',
                     'Nuclear Medicine Image Storage' : 'NM',
                     'Secondary Capture Image Storage' : 'SC'}

    try:
        mode_prefix = mode_prefixes[dataset.SOPClassUID.__str__()]
    except:
        pass

    filename = '{0!s}.{1!s}'.format(mode_prefix, dataset.SOPInstanceUID)
    logger.info('Storing DICOM file: {0!s}'.format(filename))

    if os.path.exists(filename):
        logger.warning('DICOM file already exists, overwriting')

    meta = Dataset()
    meta.MediaStorageSOPClassUID = dataset.SOPClassUID
    meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID
    meta.ImplementationClassUID = pynetdicom_uid_prefix

    ds = FileDataset(filename, {}, file_meta=meta, preamble=b"\0" * 128)
    ds.update(dataset)
    ds.is_little_endian = True
    ds.is_implicit_VR = True
    ds.save_as(filename)

    return 0x0000 # Success

ae.on_c_store = on_c_store

### Send query
if assoc.is_established:
    response = assoc.send_c_move(d, MyAETitle, query_model=query_model)
    print(response)
    for (status, dataset) in response:
        logger.warning(dataset)
    time.sleep(1)
    if response is not None:
        for value in response:
            pass
    assoc.release()

### done
ae.quit()

############################ END OF CODE ###############################

My environment

Windows-7-6.1.7601-SP1

Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)]

pydicom 1.0.2

pynetdicom3 0.9.1

Debug code


D: $getscu.py v0.1.0 2016-02-15 $
D:
I: Requesting Association
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.0.9.1
D: Our Implementation Version Name:   PYNETDICOM3_091
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    MyAETitle
D: Called Application Name:     server-dicom
D: Our Max PDU Receive Size:    16382
D: Presentation Contexts:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Patient Root Query/Retrieve Information Model - FIND
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        3 (Proposed)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - FIND
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        5 (Proposed)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - F
IND
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        7 (Proposed)
D:     Abstract Syntax: =Modality Worklist Information Model - FIND
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        9 (Proposed)
D:     Abstract Syntax: =Patient Root Query/Retrieve Information Model - MOVE
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        11 (Proposed)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - MOVE
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        13 (Proposed)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - M
OVE
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        15 (Proposed)
D:     Abstract Syntax: =Patient Root Query/Retrieve Information Model - GET
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        17 (Proposed)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - GET
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        19 (Proposed)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - G
ET
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D: Requested Extended Negotiation: None
D: Requested Common Extended Negotiation: None
D: Requested User Identity Negotiation: None
D: ======================= END A-ASSOCIATE-RQ ======================
D: Constructing Associate RQ PDU
D: PDU Type: Associate Accept, PDU Length: 779 + 6 bytes PDU header
D:     Only dumping 512 bytes.
D:     02  00  00  00  03  0b  00  01  00  00  73  65  72  76  65  72
D:     2d  64  69  63  6f  6d  20  20  20  20  70  72  6f  6a  65  74
D:     6f  44  6f  73  65  73  20  20  20  20  00  00  00  00  00  00
D:     00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
D:     00  00  00  00  00  00  00  00  00  00  10  00  00  15  31  2e
D:     32  2e  38  34  30  2e  31  30  30  30  38  2e  33  2e  31  2e
D:     31  2e  31  21  00  00  19  01  00  00  00  40  00  00  11  31
D:     2e  32  2e  38  34  30  2e  31  30  30  30  38  2e  31  2e  32
D:     21  00  00  19  03  00  00  00  40  00  00  11  31  2e  32  2e
D:     38  34  30  2e  31  30  30  30  38  2e  31  2e  32  21  00  00
D:     19  05  00  00  00  40  00  00  11  31  2e  32  2e  38  34  30
D:     2e  31  30  30  30  38  2e  31  2e  32  21  00  00  1b  07  00
D:     00  00  40  00  00  13  31  2e  32  2e  38  34  30  2e  31  30
D:     30  30  38  2e  31  2e  32  2e  31  21  00  00  19  09  00  00
D:     00  40  00  00  11  31  2e  32  2e  38  34  30  2e  31  30  30
D:     30  38  2e  31  2e  32  21  00  00  19  0b  00  00  00  40  00
D:     00  11  31  2e  32  2e  38  34  30  2e  31  30  30  30  38  2e
D:     31  2e  32  21  00  00  19  0d  00  00  00  40  00  00  11  31
D:     2e  32  2e  38  34  30  2e  31  30  30  30  38  2e  31  2e  32
D:     21  00  00  19  0f  00  00  00  40  00  00  11  31  2e  32  2e
D:     38  34  30  2e  31  30  30  30  38  2e  31  2e  32  21  00  00
D:     19  11  00  00  00  40  00  00  11  31  2e  32  2e  38  34  30
D:     2e  31  30  30  30  38  2e  31  2e  32  21  00  00  19  13  00
D:     00  00  40  00  00  11  31  2e  32  2e  38  34  30  2e  31  30
D:     30  30  38  2e  31  2e  32  50  00  01  86  51  00  00  04  00
D:     00  3f  e0  52  00  00  0f  31  2e  32  2e  34  30  2e  30  2e
D:     31  33  2e  31  2e  31  54  00  00  1f  00  1b  31  2e  32  2e
D:     38  34  30  2e  31  30  30  30  38  2e  35  2e  31  2e  34  2e
D:     31  2e  32  2e  31  2e  31  00  00  54  00  00  1f  00  1b  31
D:     2e  32  2e  38  34  30  2e  31  30  30  30  38  2e  35  2e  31
D:     2e  34  2e  31  2e  32  2e  32  2e  31  00  00  54  00  00  1f
D:     00  1b  31  2e  32  2e  38  34  30  2e  31  30  30  30  38  2e
D: Parsing an A-ASSOCIATE PDU
D: Accept Parameters:
D: ====================== BEGIN A-ASSOCIATE-AC =====================
D: Their Implementation Class UID:    1.2.40.0.13.1.1
D: Their Implementation Version Name: dcm4che-1.4.27
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    MyAETitle
D: Called Application Name:     server-dicom
D: Their Max PDU Receive Size:  16352
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        3 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        5 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        7 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        9 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        11 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        13 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        15 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        17 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        19 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D: Accepted Extended Negotiation: None
D: Accepted Common Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response:  None
D: ======================= END A-ASSOCIATE-AC ======================
I: Association Accepted
<generator object Association.send_c_move at 0x0B8294E0>
I: Move SCU Request Identifier:
I:
I: # DICOM Dataset
I: (0008, 0052) Query/Retrieve Level                CS: 'STUDY'
I: (0020, 000d) Study Instance UID                  UI: 1.2.392.200036.9116.2.5.
1.37.2418728817.1457303667.507231
I:
I: Sending Move Request: MsgID 1
D: ===================== OUTGOING DIMSE MESSAGE ====================
D: Message Type                  : C-MOVE RQ
D: Message ID                    : 1
D: Affected SOP Class UID        : Study Root Query/Retrieve Information Model -
 MOVE
D: Move Destination              : projetoDoses
D: Identifier                    : Present
D: Priority                      : Low
D: ======================= END DIMSE MESSAGE =======================
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
D: ===================== INCOMING DIMSE MESSAGE ====================
D: Message Type                  : C-MOVE RSP
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Study Root Query/Retrieve Information Model -
 MOVE
D: Remaining Sub-operations      : 737
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 0
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: DIMSE Status                  : 0xff00
D: ======================= END DIMSE MESSAGE =======================
D:
I: Move SCP Response: 1 (Pending)
I: Sub-Operations Remaining: 737, Completed: 0, Failed: 0, Warning: 0
W: None
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
D: ===================== INCOMING DIMSE MESSAGE ====================
D: Message Type                  : C-MOVE RSP
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Study Root Query/Retrieve Information Model -
 MOVE
D: Remaining Sub-operations      : 737
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 0
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: DIMSE Status                  : 0xff00
D: ======================= END DIMSE MESSAGE =======================
D:
I: Move SCP Response: 2 (Pending)
I: Sub-Operations Remaining: 737, Completed: 0, Failed: 0, Warning: 0
W: None
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
D: ===================== INCOMING DIMSE MESSAGE ====================
D: Message Type                  : C-MOVE RSP
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Study Root Query/Retrieve Information Model -
 MOVE
D: Remaining Sub-operations      : 737
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 0
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: DIMSE Status                  : 0xff00
D: ======================= END DIMSE MESSAGE =======================
D:
I: Move SCP Response: 3 (Pending)
I: Sub-Operations Remaining: 737, Completed: 0, Failed: 0, Warning: 0
W: None
scaramallion commented 6 years ago

What are you using as the SCP for the C-MOVE operation? Could you post the command you're using to start dcm4che's dcmqrscp app? I'm not seeing any indication in the log that any C-STORE requests are being received by your AE (at the very least there should be an INCOMING DIMSE MESSAGE with Message Type C-STORE RQ).

esthevaov commented 6 years ago

Hello mr. Scaramallion, so, I am testing this program in a hospital I study in, so I don't really have access to the DICOM server, but I testes the server using Clear Canvas and the getscu function from DCMTK and they all work, so I still think there is something wrong with my program.

So, I am not using a dcm4che's dcmqrscp app. Just saw that the Hospital is using the dcm4che implementation, but I have no access to the settings of it :(

I am still trying to figure out why my AE do not receive any C-STORE RQ, but I thought the problem could be related to the association, that the server is not targeting my AE as a C_STORE server...

Do you have any idea what could I do to try to find if the server I am accessing is sending C_STORE RQ? Or any other think I could test to find out what is missing?

Sorry for the lack of information. A question you may know how to answer:

Thanks for your help!

esthevaov commented 6 years ago

I also tried changing the

response = assoc.send_c_move(d, MyAETitle, query_model=query_model)

to

response = assoc.send_c_get(d, query_model=query_model)

but it's even worse, instead of receiving a C-GET RSP saying that I have remaining Sub-operations, I receive that all of the Sub-operations failed, with DIMSE status 0xa702. And after that a message with a Failed SOP Instance UID List with a size of 42672.

scaramallion commented 6 years ago

Ah, sorry. I assumed from the IP address that you used that you were running the SCP locally.

With pynetdicom if you want to receive datasets from a QR SCP using the same AE you're running as an SCU you're better off using the C-GET service since at the moment C-MOVE only works when sending to different AEs unless you start the AE in a thread.

I've tested getscu.py and movescu.py against dcmtk's dcmqrscp with the current master and they work OK, could you update and try again? Note that the on_c_* functions require dataset, context and info parameters after #167

If it still doesn't work could you post the log from a C-GET request?

esthevaov commented 6 years ago

Hello mr. @scaramallion, Everything worked as you said. I changed to C-GET and changed the StorageSOPClassList (Because some of them was not supported by my server and this was crashing my program) and it worked... But I noticed that the reading of the files is a bit slow. Considering that every study is composed of hundreds of DCM files and all, it is becoming a bit too slow for my application.

The only data that I need is in the header of the file, so the image is discarded, and I only need the files that the exposure changes (because in many of them the exposure is the same as the last file). Do you know if there is a way for me to only request this kind of data, instead of the entire file? I think this would greatly increase the speed of the program.

Thanks a lot for your help until now!

scaramallion commented 6 years ago

It depends on what data you need from the SOPs stored on the SCP. You may be able to send a C-FIND query that returns what you're interested in, which would return the corresponding values for the elements in the query without returning the entire dataset.

esthevaov commented 6 years ago

Hello mr @scaramallion , I have been testing and I don't think the C-FIND is enough to get the information I need. Things like Exposure Time and X-Ray Tube Current are Optional Keys, and apparently my SCP do not support them on the C-FIND request. I think the C-GET will be the one needed.

Do you know if there is a way for me to receive which Optional Keys are supported from my SCP? To get a list of them or something like that?

Thanks!

Here is the dataset I get as a response, as you can see, a lot of information in given as '', so I assume is unsupported information :

D: Find SCP Response: 1 (0xff01 - Pending)
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
D:
D: # DICOM Dataset
D: (0008, 0000) Group Length                        UL: 196
D: (0008, 0008) Image Type                          CS: ['ORIGINAL', 'PRIMARY','AXIAL']
D: (0008, 0016) SOP Class UID                       UI: CT Image Storage
D: (0008, 0018) SOP Instance UID                    UI: 1.2.392.200036.9116.2.5.
1.37.2418728817.1457304892.204109
D: (0008, 0052) Query/Retrieve Level                CS: 'IMAGE'
D: (0008, 0054) Retrieve AE Title                   AE: 'server-dicom'
D: (0008, 0056) Instance Availability               CS: 'ONLINE'
D: (0008, 0060) Modality                            CS: 'CT'
D: (0008, 1155) Referenced SOP Instance UID         UI: ''
D: (0018, 0000) Group Length                        UL: 54
D: (0018, 0015) Body Part Examined                  CS: 'CHEST'
D: (0018, 0022) Scan Options                        CS: ''
D: (0018, 1150) Exposure Time                       IS: ''
D: (0018, 1151) X-Ray Tube Current                  IS: ''
D: (0018, 1152) Exposure                            IS: ''
D: (0018, 9345) CTDIvol                             FD: ''
D: (0020, 0000) Group Length                        UL: 140
D: (0020, 000d) Study Instance UID                  UI: 1.2.392.200036.9116.2.5.
1.37.2418728817.1457303667.507231
D: (0020, 000e) Series Instance UID                 UI: 1.2.392.200036.9116.2.5.
1.37.2418728817.1457304884.233164
D: (0020, 1041) Slice Location                      DS: ''
D: (0028, 0000) Group Length                        UL: 56
D: (0028, 0004) Photometric Interpretation          CS: 'MONOCHROME2'
D: (0028, 0010) Rows                                US: 512
D: (0028, 0100) Bits Allocated                      US: 16
D: (0028, 0101) Bits Stored                         US: ''
D: (0028, 0102) High Bit                            US: ''
D: (0088, 0000) Group Length                        UL: 16
D: (0088, 0130) Storage Media File-set ID           SH: ''
D: (0088, 0140) Storage Media File-set UID          UI: ''
edmcdonagh commented 6 years ago

My go-to guide for C-FIND parameters is this one: https://www.medicalconnections.co.uk/kb/Query_Parameters

It would be very unusual for a PACS to support any keys that would not ordinarily be useful in a clinical patient image search. The dream of being able to search on values useful to a QA Physicist will only be met by a specialised DICOM store.

The only way of finding out which keys are supported is to review the documentation of the PACS you are querying. They will usually describe matching keys and keys that you can't match on but will be returned.

Be aware, on some PACS you may find if you try to include a key that is not supported nothing will be returned! It doesn't look like that is the case for you though.

The other thing to note is that C-GET is not well supported as an SCP. It does have network and configuration advantage over C-MOVE followed by C-STORE, but is rarely used.

esthevaov commented 6 years ago

Hello mr. @edmcdonagh ,

I have been discussing with @scaramallion and he told me that the current version of Pynetdicom3 still not supports the use of C-MOVE if you are working with the same AE for the C-MOVE RQ and C_STORE operations, that's why I changed to C-GET.

My intention is to sum up all Exposure values and CTDIvol from the Patient considering all of it's studies, but considering that this could mean on sifting over thousands of .dcm files, the process is getting a bit time consuming, considering that the C-GET and C-MOVE operations are quite slow.

That's why I was trying to use C-FIND, as I only need the header of the files. But apparently my SCP do not support the information that I need.

Now I am back to find a faster use of the C-GET op.

edmcdonagh commented 6 years ago

If I'm understanding you correctly, you have the same AET for the C-MOVE SCP and the C-STORE SCP. If this is right, then that would never work, no matter which software you use, as the only way the C-MOVE SCP knows where to initiate the C-STORE to is by looking up the supplied AET in the C-MOVE request against it's configuration of remote nodes or C-STORE SCPs.

To do what you are trying to do, you will need to get the whole dataset - I can't see any way around that.

The other thing to consider is if there might be a different source of information? It depends on exactly what you are trying to do, but have you looked at http://openrem.org? Currently we are using the old pydicom and an adapted version of pynetdicom, but when this pynetdicom3 is released we'll begin moving over.

esthevaov commented 6 years ago

I have the same AET for the C-MOVE SCU and the C-STORE SCP, so the AET that requests the C-MOVE is the one that will C-STORE the dataset, but that doesn't work with Pynetdicom3.

Yeah, I also think the only way is getting the hole dataset, what is a bum, considering that the image is just useless data for me, and is what consumes the longest to donwload.

I will look into that openrem.org, thanks for your help, @edmcdonagh .

varshit-i commented 4 years ago

I need to Retrieve – Pull Images from the PACS server.

I am using dicomserver.co.uk server for Retrieve – Pull Images.

I am receiving status key is missing

Here is my code -

import argparse
import logging
import os
import socket
import sys
import time

from pydicom.dataset import Dataset, FileDataset
from pydicom.filewriter import write_file
from pydicom.uid import ExplicitVRLittleEndian, ImplicitVRLittleEndian, \
                        ExplicitVRBigEndian, UID

from pynetdicom3 import AE
from pynetdicom3 import StorageSOPClassList, QueryRetrieveSOPClassList
from pynetdicom3 import pynetdicom_uid_prefix
from pynetdicom3.pdu_primitives import SCP_SCU_RoleSelectionNegotiation

logger = logging.Logger('getscu')
stream_logger = logging.StreamHandler()
formatter = logging.Formatter('%(levelname).1s: %(message)s')
stream_logger.setFormatter(formatter)
logger.addHandler(stream_logger)
logger.setLevel(logging.ERROR)

logger.setLevel(logging.DEBUG)
pynetdicom_logger = logging.getLogger('pynetdicom3')
pynetdicom_logger.setLevel(logging.DEBUG)

logger.debug('$getscu.py v{0!s} {1!s} $'.format('0.1.0', '2016-02-15'))
logger.debug('')

MyAETitle = "MyAETitle"
ServerIP = 'dicomserver.co.uk'
ServerPort = 104
ServerTitle = 'server-dicom'

ae = AE(ae_title=MyAETitle,
        port=104,
        scu_sop_class=QueryRetrieveSOPClassList,
        scp_sop_class=StorageSOPClassList)

### Set the extended negotiation SCP/SCU role selection to allow us to receive
###   C-STORE requests for the supported SOP classes
ext_neg = []
for context in ae.presentation_contexts_scu:
    tmp = SCP_SCU_RoleSelectionNegotiation()
    tmp.sop_class_uid = context.AbstractSyntax
    tmp.scu_role = False
    tmp.scp_role = True

    ext_neg.append(tmp)

### Request association with remote
assoc = ae.associate(ServerIP, ServerPort, ServerTitle, ext_neg=ext_neg)

### Create query dataset
d = Dataset()
#d.StudyInstanceUID = '1.2.392.200036.9116.2.5.1.37.2418728817.1457303667.507231'
d.QueryRetrieveLevel = "STUDY"
query_model = 'S'

def on_c_store(dataset):
    """
    Function replacing ApplicationEntity.on_store(). Called when a dataset is
    received following a C-STORE. Write the received dataset to file

    Parameters
    ----------
    dataset - pydicom.Dataset
        The DICOM dataset sent via the C-STORE

    Returns
    -------
    status : pynetdicom.sop_class.Status or int
        A valid return status code, see PS3.4 Annex B.2.3 or the
        StorageServiceClass implementation for the available statuses
    """
    mode_prefix = 'UN'
    mode_prefixes = {'CT Image Storage' : 'CT',
                     'Enhanced CT Image Storage' : 'CTE',
                     'MR Image Storage' : 'MR',
                     'Enhanced MR Image Storage' : 'MRE',
                     'Positron Emission Tomography Image Storage' : 'PT',
                     'Enhanced PET Image Storage' : 'PTE',
                     'RT Image Storage' : 'RI',
                     'RT Dose Storage' : 'RD',
                     'RT Plan Storage' : 'RP',
                     'RT Structure Set Storage' : 'RS',
                     'Computed Radiography Image Storage' : 'CR',
                     'Ultrasound Image Storage' : 'US',
                     'Enhanced Ultrasound Image Storage' : 'USE',
                     'X-Ray Angiographic Image Storage' : 'XA',
                     'Enhanced XA Image Storage' : 'XAE',
                     'Nuclear Medicine Image Storage' : 'NM',
                     'Secondary Capture Image Storage' : 'SC'}

    try:
        mode_prefix = mode_prefixes[dataset.SOPClassUID.__str__()]
    except:
        pass

    filename = '{0!s}.{1!s}'.format(mode_prefix, dataset.SOPInstanceUID)
    logger.info('Storing DICOM file: {0!s}'.format(filename))

    if os.path.exists(filename):
        logger.warning('DICOM file already exists, overwriting')

    meta = Dataset()
    meta.MediaStorageSOPClassUID = dataset.SOPClassUID
    meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID
    meta.ImplementationClassUID = pynetdicom_uid_prefix

    ds = FileDataset(filename, {}, file_meta=meta, preamble=b"\0" * 128)
    ds.update(dataset)
    ds.is_little_endian = True
    ds.is_implicit_VR = True
    ds.save_as(filename)

    return 0x0000 # Success

ae.on_c_store = on_c_store

### Send query
if assoc.is_established:
    response = assoc.send_c_move(d, MyAETitle, query_model=query_model)
    print(response)
    for (status, dataset) in response:
        logger.warning(dataset)
    time.sleep(1)
    if response is not None:
        for value in response:
            pass
    assoc.release()

### done
ae.quit()`

My debug log is -

C:\Users\Varshit.J\AppData\Local\Programs\Python\Python36\python.exe D:/python/pacs/pac.py dicomserver.co.uk 104 *
D: $getscu.py v0.1.0 2016-02-15 $
D: 
I: Requesting Association
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.0.1.0
D: Our Implementation Version Name:   PYNETDICOM3_010
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    MyAETitle       
D: Called Application Name:     server-dicom    
D: Our Max PDU Receive Size:    16382
D: Presentation Contexts:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.1.1
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        3 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.2.1
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        5 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.3.1
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        7 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.31
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        9 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.1.2
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        11 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.2.2
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        13 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.3.2
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        15 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.1.3
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        17 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.2.3
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:   Context ID:        19 (Proposed)
D:     Abstract Syntax: =1.2.840.10008.5.1.4.1.2.3.3
D:     Proposed SCP/SCU Role: 1/0
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D: Requested Extended Negotiation: None
D: Requested Common Extended Negotiation: None
D: Requested User Identity Negotiation: None
D: ======================= END A-ASSOCIATE-RQ ======================
D: Constructing Associate RQ PDU
D: PDU Type: Associate Accept, PDU Length: 822 + 6 bytes PDU header
D:     Only dumping 512 bytes.
D:     02  00  00  00  03  36  00  01  00  00  73  65  72  76  65  72
D:     2d  64  69  63  6f  6d  20  20  20  20  4d  79  41  45  54  69
D:     74  6c  65  20  20  20  20  20  20  20  00  00  00  00  00  00
D:     00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
D:     00  00  00  00  00  00  00  00  00  00  10  00  00  15  31  2e
D:     32  2e  38  34  30  2e  31  30  30  30  38  2e  33  2e  31  2e
D:     31  2e  31  21  00  00  1b  01  00  00  00  40  00  00  13  31
D:     2e  32  2e  38  34  30  2e  31  30  30  30  38  2e  31  2e  32
D:     2e  31  21  00  00  1b  03  00  00  00  40  00  00  13  31  2e
D:     32  2e  38  34  30  2e  31  30  30  30  38  2e  31  2e  32  2e
D:     31  21  00  00  1b  05  00  00  00  40  00  00  13  31  2e  32
D:     2e  38  34  30  2e  31  30  30  30  38  2e  31  2e  32  2e  31
D:     21  00  00  1b  07  00  00  00  40  00  00  13  31  2e  32  2e
D:     38  34  30  2e  31  30  30  30  38  2e  31  2e  32  2e  31  21
D:     00  00  1b  09  00  00  00  40  00  00  13  31  2e  32  2e  38
D:     34  30  2e  31  30  30  30  38  2e  31  2e  32  2e  31  21  00
D:     00  1b  0b  00  00  00  40  00  00  13  31  2e  32  2e  38  34
D:     30  2e  31  30  30  30  38  2e  31  2e  32  2e  31  21  00  00
D:     1b  0d  00  00  00  40  00  00  13  31  2e  32  2e  38  34  30
D:     2e  31  30  30  30  38  2e  31  2e  32  2e  31  21  00  00  1b
D:     0f  00  00  00  40  00  00  13  31  2e  32  2e  38  34  30  2e
D:     31  30  30  30  38  2e  31  2e  32  2e  31  21  00  00  1b  11
D:     00  00  00  40  00  00  13  31  2e  32  2e  38  34  30  2e  31
D:     30  30  30  38  2e  31  2e  32  2e  31  21  00  00  1b  13  00
D:     00  00  40  00  00  13  31  2e  32  2e  38  34  30  2e  31  30
D:     30  30  38  2e  31  2e  32  2e  31  50  00  01  9f  51  00  00
D:     04  00  01  00  00  52  00  00  26  31  2e  32  2e  38  32  36
D:     2e  30  2e  31  2e  33  36  38  30  30  34  33  2e  31  2e  32
D:     2e  31  30  30  2e  38  2e  34  30  2e  31  32  30  2e  30  54
D:     00  00  1f  00  1b  31  2e  32  2e  38  34  30  2e  31  30  30
D:     30  38  2e  35  2e  31  2e  34  2e  31  2e  32  2e  31  2e  31
D:     00  01  54  00  00  1f  00  1b  31  2e  32  2e  38  34  30  2e
D: Parsing an A-ASSOCIATE PDU
D: Accept Parameters:
D: ====================== BEGIN A-ASSOCIATE-AC =====================
D: Their Implementation Class UID:    1.2.826.0.1.3680043.1.2.100.8.40.120.0
D: Their Implementation Version Name: DicomObjects.NET
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    MyAETitle       
D: Called Application Name:     server-dicom    
D: Their Max PDU Receive Size:  65536
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        3 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        5 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        7 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        9 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        11 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        13 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        15 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        17 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D:   Context ID:        19 (Accepted)
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =1.2.840.10008.1.2.1
D: Accepted Extended Negotiation: None
D: Accepted Common Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response:  None
D: ======================= END A-ASSOCIATE-AC ======================
I: Association Accepted
<generator object Association.send_c_move at 0x00000173A59434C0>
I: Move SCU Request Identifiers:
I: 
I: # DICOM Dataset
I: (0008, 0052) Query/Retrieve Level                CS: 'STUDY'
I: 
I: Sending Move Request: MsgID 1
Traceback (most recent call last):
  File "D:/python/pacs/pac.py", line 109, in <module>
    for (status, dataset) in response:
  File "C:\Users\Varshit.J\AppData\Local\Programs\Python\Python36\lib\site-packages\pynetdicom3-0.1.0-py3.6.egg\pynetdicom3\association.py", line 1135, in send_c_move
    self.dimse.send_msg(primitive, context_id)
  File "C:\Users\Varshit.J\AppData\Local\Programs\Python\Python36\lib\site-packages\pynetdicom3-0.1.0-py3.6.egg\pynetdicom3\dimse.py", line 232, in send_msg
    self.on_send_dimse_message(dimse_msg)
  File "C:\Users\Varshit.J\AppData\Local\Programs\Python\Python36\lib\site-packages\pynetdicom3-0.1.0-py3.6.egg\pynetdicom3\dimse.py", line 372, in on_send_dimse_message
    callback[type(message)](message)
  File "C:\Users\Varshit.J\AppData\Local\Programs\Python\Python36\lib\site-packages\pynetdicom3-0.1.0-py3.6.egg\pynetdicom3\dimse.py", line 661, in debug_send_c_move_rq
    .format(cs.MoveDestination.decode('utf-8')))
AttributeError: 'str' object has no attribute 'decode'
scaramallion commented 4 years ago

I would suggest updating to the latest release to see if that helps. If it doesn't, please create a new issue.

You might also want to look at the QR Move SCU example.

varshit-i commented 4 years ago

@scaramallion I have tried with the latest version. I have searched for a working example but didn't find any working example. Do you have any reference that is working?

scaramallion commented 4 years ago

There's also the movescu.py application you can take a look at (documentation page here).

varshit-i commented 4 years ago

@scaramallion While trying movescu.py I am getting the following response -


I: Requesting Association I: Association Accepted I: Sending Move Request: MsgID 1 I: I: # Request Identifier I: (0008, 0052) Query/Retrieve Level CS: 'SERIES' I: I: Move SCP Result: 0xA801 (Failure) I: Sub-Operations Remaining: 0, Completed: 0, Failed: 0, Warning: 0 I: Releasing Association

scaramallion commented 4 years ago

Troubleshooting Query/Retrieve SCU

If you have any more problems please create a new issue.