Trying to perform a C-MOVE with a SCP pynetdicom

Hi!
First of all, congrats for the community! I’m really glad to see there’s an active and supportive community for Orthanc.

Here’s my problem: I’m trying to create a SCP server that will receive a C-MOVE from an Orthanc server - my last goal is that this app becomes like a “vna” to manage the exams storaging - hopefully in the end I’ll be able to share the solution with the community.

Everything about working more deeply with DICOM is kind of new to me, so please correct me if I’m wrong about any assumption.

So my plan consists in give the exams to Orthanc whenever he asks with a Q/R, but, whenever I’m handling a C-MOVE, I’m receiving this exception in pynetdicom:

ValueError: No presentation context for ‘CT Image Storage’ has been accepted by the peer with ‘JPEG Extended (Process 2 and 4)’ transfer syntax for the SCU role

Even though my orthanc config file is like this:

    "AcceptedTransferSyntaxes": ["*"],

Here’s my pynetdicom code example:

import os

from pydicom import dcmread
from pydicom.dataset import Dataset

from src.setup.setup import _setup_argparser, setup_supported_context
from pynetdicom import AE, StoragePresentationContexts, AllStoragePresentationContexts,evt
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelMove

# Implement the evt.EVT_C_MOVE handler
def handle_move(event):
    """Handle a C-MOVE request event."""
    # ds = event.identifier
    print('handling move')

    known_aet_dict = get_known_aet()
    try:
        (addr, port) = known_aet_dict[event.move_destination]
    except KeyError:
        # Unknown destination AE
        yield (None, None)
        return

    # Yield the IP address and listen port of the destination AE
    yield (addr, port)

    # Import stored SOP Instances
    instances = []
    matching = []
    fdir = '/path/to/dataset/'
    for fpath in os.listdir(fdir):
        instances.append(dcmread(os.path.join(fdir, fpath)))

    # if ds.QueryRetrieveLevel == 'PATIENT':
    #     if 'PatientID' in ds:
    matching = [
        inst for inst in instances #if inst.PatientID == ds.PatientID
    ]

        # Skip the other possible attributes...

    # Skip the other QR levels...

    # Yield the total number of C-STORE sub-operations required
    yield len(matching)

    # Yield the matching instances
    for instance in matching:
        # Check if C-CANCEL has been received
        if event.is_cancelled:
            yield (0xFE00, None)
            return
        
        ae = AE(ae_title='ORTHANC')
         transfer_syntax = ALL_TRANSFER_SYNTAXES[0:16]
    
    ae.add_supported_context(Verification, transfer_syntax)
 
    ae.add_supported_context(PatientRootQueryRetrieveInformationModelFind, transfer_syntax)
    ae.add_supported_context(PatientRootQueryRetrieveInformationModelMove, transfer_syntax)
    ae.add_supported_context(PatientRootQueryRetrieveInformationModelGet, transfer_syntax)
    ae.add_supported_context(StudyRootQueryRetrieveInformationModelFind, transfer_syntax)
    ae.add_supported_context(StudyRootQueryRetrieveInformationModelMove, transfer_syntax)
    ae.add_supported_context(StudyRootQueryRetrieveInformationModelGet, transfer_syntax)
    ae.add_supported_context(PatientStudyOnlyQueryRetrieveInformationModelFind, transfer_syntax)
    ae.add_supported_context(PatientStudyOnlyQueryRetrieveInformationModelMove, transfer_syntax)
    ae.add_supported_context(PatientStudyOnlyQueryRetrieveInformationModelGet, transfer_syntax)
    ae.add_supported_context(CompositeInstanceRootRetrieveMove, transfer_syntax)
    ae.add_supported_context(CompositeInstanceRootRetrieveGet, transfer_syntax)
    ae.add_supported_context(CompositeInstanceRetrieveWithoutBulkDataGet, transfer_syntax)
    ae.add_supported_context(RepositoryQuery, transfer_syntax)
    ae.add_supported_context(CTImageStorage, transfer_syntax)
    ae.add_supported_context(ComputedRadiographyImageStorage, transfer_syntax)
    ae.add_supported_context('1.2.840.10008.5.1.4.1.1.2', transfer_syntax)
    ae.add_supported_context("1.2.840.10008.5.1.4.1.1.2.1", transfer_syntax)
    
    # ae.p
    ae.add_requested_context("1.2.840.10008.5.1.4.1.1.2.1", transfer_syntax)
    ae.add_requested_context('1.2.840.10008.5.1.4.1.1.2',transfer_syntax)
    ae.add_requested_context(CTImageStorage,transfer_syntax)
    # ae.requested_contexts = StoragePresentationContexts
 
        assoc = ae.associate(addr,port)
        # Pending
        
        if assoc.is_established:
            for cx in assoc.accepted_contexts:
                cx._as_scp = True
    
            assoc.send_c_store(dataset=instance)        
            assoc.release()

        yield (0x0000, instance)

def get_known_aet():
    return {'ORTHANC':('127.0.0.1', 4242)}


And here’s how I’m running my orthanc (w/ docker compose):

version: '3.1'  # Secrets are only available since this version of Docker Compose
services:
  orthanc:
    build: orthanc
    command: /run/secrets/  # Path to the configuration files (stored as secrets)
    ports:
      - 4242:4242
      - 8042:8042
    secrets:
      - orthanc.json
    environment:
      - ORTHANC_NAME=HelloWorld
      - VERBOSE_ENABLED="true"
      - VERBOSE_STARTUP="true"
      - ORTHANC__PYTHON_VERBOSE="true"
secrets:
  orthanc.json:
    file: orthanc.configuration.json

and the dockerfile:

FROM jodogne/orthanc-plugins:1.12.4

# This example is using a virtual env that is not mandatory when using Docker containers
# but recommended since python 3.11 and Debian bookworm based images where you get a warning
# when installing system-wide packages.
RUN apt-get update && apt install -y python3-venv
RUN python3 -m venv /.venv

RUN /.venv/bin/pip install pydicom
ENV PYTHONPATH=/.venv/lib64/python3.11/site-packages/

RUN mkdir /python
COPY * /python/

So I would like to understand if there’s something I’m doing wrong with Orthanc, or if this is actually a problem with my pynetdicom and possibly a limitation.
Thanks!

I actually managed to fix this by declaring directly the transfer syntax for each presentation context instead of using “transfersyntax” array