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!