pydicom / pynetdicom

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

VR LO length error trying to movescu from test server #919

Closed joelhirschhorn closed 4 months ago

joelhirschhorn commented 5 months ago

Hi, sorry for the naive question or if this is the wrong place to ask this question - I am new to pynetdicom and trying to run movescu to transfer files from the test server at www.dicomserver.co.uk using the command line from Terminal on a Mac as follows: python -m pynetdicom movescu -d www.dicomserver.co.uk 104 --store --store-aet JNHSTOR001 -od ./out -aet JNHSTOR001 --store-port 11112 -aem JNHSTOR001 -k QueryRetrieveLevel=STUDY -k PatientName=PAT001 This gives an error (and gives the same error if the QueryRetrieveLevel is PATIENT, or if I set store-port to 104 using sudo, or other minor changes) "...pydicom/valuerep.py:443: UserWarning: The value length (202) exceeds the maximum length of 64 allowed for VR LO" and another similar error with 201 instead of 202. No files are transferred. I can run findscu for the same test server successfully with the same PATIENT level keys.

scaramallion commented 5 months ago

That's a warning, not an error. From memory, I don't think www.dicomserver.co.uk actually transfers datasets for C-MOVE or C-GET.

joelhirschhorn commented 5 months ago

Thanks so much for the quick reply. Is there a test server that can actually transfer files so I can see if I'm doing things correctly?

If you are able to look at what I've been trying for a few weeks off and on now and can provide help, that would be great - I need to transfer thousands of dicom files from our hospital's PACS DICOM server and have tried to get this to work on a small scale either through pynetdicom commands or through code from the pynetdicom tutorials - but no files are ever moved. The C-MOVE fails with status 0xC000. When the folks at our radiology IT department look at their end, they can see the C-MOVE request but tell me that their server doesn't know where to send the files. I've given them my IP address and storage server name (and a port when I've used --store or tried to set up a storage server SCP). I've tried variations on the following command lines (with or without --store, listening on different ports); I've also tried setting up a storage scp server first and then running movescu and also running code based on tutorials below (patient and PACS server information with PATIENT ID, etc.) Thanks again

Command lines I've tried from Jupyter notebook:

1

!python3 -m pynetdicom movescu --verbose --output-directory dicomtest --store-aet JNHSTOR001 --store --called-aet **OUR PACS NAME** **OUR PACS URL** 104 -k QueryRetrieveLevel=STUDY -k \(0008,0050\)=**PATIENT ID**

I also tried running

!python -m pynetdicom storescp 11112 --verbose -aet JNHSTOR001 --output-directory /Users/joelh/dicomtest

from one Jupyter notebook and then running

!python3 -m pynetdicom movescu --verbose --store-aet JNHSTOR001 --called-aet **OUR PACS NAME** **OUR PACS URL** 104 -k QueryRetrieveLevel=STUDY -k \(0008,0050\)=**PATIENT ID**

from a second notebook.

Code blocks I've tried:

Code block #1

from pydicom.dataset import Dataset

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

debug_logger()

def handle_store(event):
    """Handle a C-STORE service request"""
    # Ignore the request and return Success
    return 0x0000

handlers = [(evt.EVT_C_STORE, handle_store)]

# Initialise the Application Entity
ae = AE()

# Add a requested presentation context
ae.add_requested_context(StudyRootQueryRetrieveInformationModelMove)

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

# Start our Storage SCP in non-blocking mode, listening on port 11120
ae.ae_title = 'JNHSTOR001'
scp = ae.start_server(("127.0.0.1", 11120), block=False, evt_handlers=handlers)
ds = Dataset()
ds.SOPClassesInStudy = ''
ds.PatientID = '**PATIENT ID**'
ds.PatientName = ''
ds.StudyInstanceUID = '**UID FOR A SPECIFIC STUDY**'
ds.QueryRetrieveLevel = 'STUDY'
ds.StudyID= ''
ds.StudyDate= ''
remote_host = '**ADDRESS OF OUR PACS SERVER**'
remote_port = 104
remote_aet = b'**NAME OF OUR PACS SERVER**'
assoc = ae.associate(remote_host, remote_port, ae_title=remote_aet)
if assoc.is_established:
    # Use the C-MOVE service to send the identifier
    responses = assoc.send_c_move(ds, 'JNHSTOR001', StudyRootQueryRetrieveInformationModelMove)

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

Code block #2

def query_pacs():
    ae = AE(ae_title=b'JNHSTOR001')
    ae.add_requested_context(StudyRootQueryRetrieveInformationModelFind)
    from pydicom.dataset import Dataset
    ds = Dataset()
    ds.SOPClassesInStudy = ''
    ds.PatientID = '**PATIENTID**'
    ds.PatientName = ''
    ds.StudyInstanceUID = ''
    ds.QueryRetrieveLevel = 'STUDY'
    ds.StudyID= ''
    ds.StudyDate= ''
    remote_host = '**ADDRESS OF PACS SERVER**'
    remote_port = 104
    remote_aet = b'**NAME OF PACS SERVER**'
    assoc = ae.associate(remote_host, remote_port, ae_title=remote_aet)
    if assoc.is_established:
        # Send the C-FIND request
        studies = assoc.send_c_find(ds, StudyRootQueryRetrieveInformationModelFind)
        for (status, identifier) in studies:
            if status:
                print('C-FIND query status: 0x{0:04X}'.format(status.Status))
            else:
                print('Connection timed out, was aborted or received invalid response')
        for (status, dataset) in studies:
            if status:
                # If the status is OK, extract and save DICOM file
                file_meta = dataset.file_meta
                filename = f"{dataset.SOPInstanceUID}.dcm"
                with open(filename, 'wb') as file:
                    file_meta.write(file)
                    dataset.write(file)

        # Release the association
        assoc.release()
    else:
        print('Association rejected, aborted or never connected')

if __name__ == "__main__":
    debug_logger()
    query_pacs()
scaramallion commented 5 months ago

Could you post the debug_logger() output for both the Store SCP and Move SCU, please? With any identifying information removed.

Also the pynetdicom version.

Could you also post the information that the PACS people have given you for what they've used to register your Move SCU and Store SCP (minus the IP addresses)?

joelhirschhorn commented 5 months ago

Hi,

Thanks so much for looking at this.

debug_logger() for code block 1 (MOVESCU)

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.2.0.2
D: Our Implementation Version Name:   PYNETDICOM_202
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    JNHSTOR001
D: Called Application Name:     **PACS ID**
D: Our Max PDU Receive Size:    16382
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - MOVE
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: 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.840.113845.1.1
D: Their Implementation Version Name: Syn7,3,100,260
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    JNHSTOR001
D: Called Application Name:     **PACS ID**
D: Their Max PDU Receive Size:  65000
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - MOVE
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit 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 Move Request: MsgID 1
I: 
I: # Request Identifier
I: (0008,0020) DA (no value available)                     # 0 StudyDate
I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
I: (0008,0062) UI (no value available)                     # 0 SOPClassesInStudy
I: (0010,0010) PN (no value available)                     # 0 PatientName
I: (0010,0020) LO [**PATIENTID**]                                # 1 PatientID
I: (0020,000D) UI [**UNIQUEID**] # 1 StudyInstanceUID
I: (0020,0010) SH (no value available)                     # 0 StudyID
I: 
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              : JNHSTOR001
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: Presentation Context ID       : 1
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Study Root Query/Retrieve Information Model - MOVE
D: Remaining Sub-operations      : 0
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 0
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: Status                        : 0xC000
D: ============================ END DIMSE MESSAGE =============================
D: 
I: Move SCP Result: 0xC000 (Failure)
I: Sub-Operations Remaining: 0, Completed: 0, Failed: 0, Warning: 0
D: pydicom.read_dataset() TransferSyntax="Little Endian Explicit"
I: Releasing Association

debug_logger() output for code block 2 (after fixing some python errors by importing some additional things from pynetdicom) was a bunch of successful C-FIND requests, one for each study for that patient, but no files were written in the working directory

-d mode for the pynetdicom storescp command:

!python -m pynetdicom storescp 11112 -d -aet JNHSTOR001 --output-directory **DIRECTORY PATH**

was just:

D: storescp.py v0.6.0
D:

until I had to stop the notebook.

-d mode for the following pynetdicom query:

!python3 -m pynetdicom movescu -d --output-directory dicomtest -aet JNHSTOR001 --store-aet JNHSTOR001 --store --called-aet **PACS NAME** **IP ADDRESS** 104 -k QueryRetrieveLevel=STUDY -k PatientID=**PATIENT ID** -k \(0008,0020\)=**DATE**

was:

D: movescu.py v0.4.0
D: 
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.2.0.2
D: Our Implementation Version Name:   PYNETDICOM_202
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    JNHSTOR001
D: Called Application Name:     **PACS NAME**
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: 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: =Patient Root Query/Retrieve Information Model - MOVE
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:        5 (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:        7 (Proposed)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - FIND
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:        9 (Proposed)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - MOVE
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:        11 (Proposed)
D:     Abstract Syntax: =Study 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:        13 (Proposed)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - FIND
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:        15 (Proposed)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - MOVE
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:        17 (Proposed)
D:     Abstract Syntax: =Patient/Study Only 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:        19 (Proposed)
D:     Abstract Syntax: =Composite Instance Root Retrieve - MOVE
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:        21 (Proposed)
D:     Abstract Syntax: =Composite Instance Root Retrieve - 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:        23 (Proposed)
D:     Abstract Syntax: =Composite Instance Retrieve Without Bulk Data - 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: 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.840.113845.1.1
D: Their Implementation Version Name: Syn7,3,100,260
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    JNHSTOR001
D: Called Application Name:     **PACS NAME**
D: Their Max PDU Receive Size:  65000
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =Patient Root Query/Retrieve Information Model - FIND
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        3 (Accepted)
D:     Abstract Syntax: =Patient Root Query/Retrieve Information Model - MOVE
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        5 (Rejected - Abstract Syntax Not Supported)
D:     Abstract Syntax: =Patient Root Query/Retrieve Information Model - GET
D:   Context ID:        7 (Accepted)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - FIND
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        9 (Accepted)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - MOVE
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        11 (Accepted)
D:     Abstract Syntax: =Study Root Query/Retrieve Information Model - GET
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        13 (Accepted)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - FIND
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D:   Context ID:        15 (Accepted)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - MOVE
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        17 (Rejected - Abstract Syntax Not Supported)
D:     Abstract Syntax: =Patient/Study Only Query/Retrieve Information Model - GET
D:   Context ID:        19 (Accepted)
D:     Abstract Syntax: =Composite Instance Root Retrieve - MOVE
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        21 (Accepted)
D:     Abstract Syntax: =Composite Instance Root Retrieve - GET
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        23 (Accepted)
D:     Abstract Syntax: =Composite Instance Retrieve Without Bulk Data - GET
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit 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 Move Request: MsgID 1
I: 
I: # Request Identifier
I: (0008,0020) DA [**DATE**]                               # 1 StudyDate
I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
I: (0010,0020) LO [**PATIENT ID**]                                # 1 PatientID
I: 
D: ========================== OUTGOING DIMSE MESSAGE ==========================
D: Message Type                  : C-MOVE RQ
D: Message ID                    : 1
D: Affected SOP Class UID        : Patient Root Query/Retrieve Information Model - MOVE
D: Move Destination              : STORESCP
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: Presentation Context ID       : 3
D: Message ID Being Responded To : 1
D: Affected SOP Class UID        : Patient Root Query/Retrieve Information Model - MOVE
D: Remaining Sub-operations      : 0
D: Completed Sub-operations      : 0
D: Failed Sub-operations         : 0
D: Warning Sub-operations        : 0
D: Identifier                    : None
D: Status                        : 0xC000
D: ============================ END DIMSE MESSAGE =============================
D: 
I: Move SCP Result: 0xC000 (Failure)
I: Sub-Operations Remaining: 0, Completed: 0, Failed: 0, Warning: 0
D: pydicom.read_dataset() TransferSyntax="Little Endian Explicit"
I: Releasing Association

The folks I was working with only asked for my IP address and the name I was using for my Store SCP - they didn't give me any information other than the name of their DICOM server and their IP address and port (104)

scaramallion commented 5 months ago

This part of the C-MOVE response in the log tells me that the problem is either a bad query on your part (less likely), or something is wrong with the PACS end (more likely).

D: Remaining Sub-operations : 0
D: Completed Sub-operations : 0
D: Failed Sub-operations : 0
D: Warning Sub-operations : 0
D: Identifier : None
D: Status : 0xC000

A successful query would give you non-zero sub-operations required. If the query was successful but the storage failed then you'd see non-zero failed sub-operations. A bad query will usually give a different status response, but may result in 0xC000.

But typically the 0xC000 status response is a sign that the Move SCP doesn't like something non-query related or hasn't been configured correctly (such as not recognising the calling AE title). I have a feeling they haven't added the AE title of your Move SCU, and therefore are rejecting the Move requests, which in practice is resulting in the 0xC000 response rather than an association rejection or abort. Both the Move SCU and Store SCP typically have to be registered with the PACS.

If you have access to another existing system that you know works as a Move SCU you could test this by configuring it to send the datasets to your Store SCP.

joelhirschhorn commented 5 months ago

Thanks again for looking at this. Working with the IT folks here, who did have my IP port and Store SCP name, I got this to work(!) by starting up a storescp from one notebook: !python -m pynetdicom storescp 11112 --verbose -aet JNHSTOR001 --output-directory dicomtest and then from a separate notebook running movescu, not using the --store option (apparently the movescu query with --store had STORESCP as the Store SCP name even though I had used the --store-aet JNHSTOR001 flag): !python3 -m pynetdicom movescu -d --output-directory dicomtest -aet JNHSTOR001 --called-aet DICOMSERVERNAME DICOM SERVER IP -k QueryRetrieveLevel=PATIENT -k PatientID=PATIENTID

We however only got this to run on the PATIENT level query, which returns a ton of files, and won't be suitable across thousands of patients. So, my hopefully last questions are:

  1. Is it possible to run movescu at the QueryRetrieveLevel=STUDY, and if so, which keywords are allowed/required to be included?
  2. If we need to run movescu at the PATIENT level, and I specify the date with -k StudyDate = YYYYMMDD, or the Study UID with -k StudyInstanceUID Study UID, will it only move the studies from that date or for that Study UID? Or can that only be done at the STUDY level?
  3. In case I accidentally kick off an enormous C-MOVE query with storescp and movescu (the query for one patient took about 2 hours to move ~20,000 files), is there a way of killing this at my end? Or once it hits their server, do they need to kill it at their end? Thanks again!
scaramallion commented 5 months ago

Ah, you need to use --move-aet to specify the destination AE title.

  1. https://www.medicalconnections.co.uk/kb/Query-Parameters
  2. It depends on the implementation of the Move SCP, but I would guess most likely only with the STUDY level
  3. The Move SCP is really responsible for handling queries to prevent locking their system up, but worst case you should be able to send a C-CANCEL request or just abort, although for that you'll have to write your own implementation.

movescu isn't really intended to be a fully functioned application, it's more of a working demonstration.