Re: Connect to ORTHANC with Pynetdicom

1,266 views
Skip to first unread message

Sébastien Jodogne

unread,
Feb 17, 2019, 3:03:41 AM2/17/19
to John Bell, Orthanc Users
You are wrongly starting Orthanc. The following command does not provide Orthanc with your configuration file:

./Orthanc --verbose

You should launch instead:

./Orthanc --verbose Configuration.json


HTH,
Sébastien-


On Sun, 17 Feb 2019 at 07:47, 'John Bell' via Orthanc Users <orthan...@googlegroups.com> wrote:
Hello,

I am using Orthanc as a PACS server for testing.

I am running on MacOS using ./Orthanc --verbose.

I wish to query and retrieve images based on the Patient ID. So far, i am stuck on doing a C-Find request from my local machine.

When performing the C-Find, Orthanc is outputting the following information.

I0217 19:44:08.164480 CommandDispatcher.cpp:491] Association Received from AET PYNETDICOM on IP 127.0.0.1
I0217 19:44:08.164669 main.cpp:195] Incoming connection from AET PYNETDICOM on IP 127.0.0.1, calling AET ORTHANC
I0217 19:44:08.164716 CommandDispatcher.cpp:689] Association Acknowledged (Max Send PDV: 16370)
I0217 19:44:08.169743 main.cpp:215] Incoming Find request from AET PYNETDICOM on IP 127.0.0.1, calling AET ORTHANC
W0217 19:44:08.169782 CommandDispatcher.cpp:812] Rejected Find request from remote DICOM modality with AET "PYNETDICOM" and hostname "127.0.0.1"
I0217 19:44:08.169804 CommandDispatcher.cpp:909] DIMSE failure (aborting association): DIMSE Caller passed in an illegal association

This appears to be related to DicomModalities in the configuration file for Orthanc.

What is wrong with my configuration?
Does anyone have a working version of Pynetdicom for C-Find and C-Move?

Please see below for a more detailed explanation of the steps that i have taken.


The steps i have followed are shown below.

1) Configure Orthanc for client

  "DicomServerEnabled" : true,
 
// The DICOM Application Entity Title
 
"DicomAet" : "ORTHANC",
 
// Check whether the called AET corresponds during a DICOM request
 
"DicomCheckCalledAet" : false,
 
// The DICOM port
 
"DicomPort" : 4242,


 
// The list of the known DICOM modalities
 
"DicomModalities" : {
   
"PYNETDICOM" : [ "PYNETDICOM", "127.0.0.1", 4242 ]
 
},.

2) Start Orthanc

./Orthanc --verbose

3) Upload images (Drag/Drop)

4) Create Pynetdicom echo script

from pynetdicom import AE
from pynetdicom.sop_class import VerificationSOPClass


import logging


LOGGER
= logging.getLogger('pynetdicom')
LOGGER
.setLevel(logging.DEBUG)


# Initialise the Application Entity
ae
= AE()


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


# Associate with peer AE at IP 127.0.0.1 and port 11112
assoc
= ae.associate('127.0.0.1', 4242)


if assoc.is_established:
   
# Use the C-ECHO service to send the request
   
# returns the response status a pydicom Dataset
    status
= assoc.send_c_echo()


   
# Check the status of the verification request
   
if status:
       
# If the verification request succeeded this will be 0x0000
       
print('C-ECHO request 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')

Response....

I: Requesting Association
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.2.0
D: Our Implementation Version Name:   PYNETDICOM_120
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Our Max PDU Receive Size:    16382
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Verification SOP Class
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntaxes:
D:       =Implicit VR Little Endian
D:       =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 ======================
D: Accept Parameters:
D: ====================== BEGIN A-ASSOCIATE-AC =====================
D: Their Implementation Class UID:    1.2.276.0.7230010.3.0.3.6.2
D: Their Implementation Version Name: OFFIS_DCMTK_362
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Their Max PDU Receive Size:  16384
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D: Role Selection: None
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ======================= END A-ASSOCIATE-AC ======================
I: Association Accepted
I: Sending Echo Request: MsgID 1
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Received Echo Response (Status: Success)
C-ECHO request status: 0x0000

Orthanc log.....

I0217 19:43:34.197128 CommandDispatcher.cpp:491] Association Received from AET PYNETDICOM on IP 127.0.0.1
I0217 19:43:34.197223 main.cpp:195] Incoming connection from AET PYNETDICOM on IP 127.0.0.1, calling AET ANY-SCP
I0217 19:43:34.197257 CommandDispatcher.cpp:689] Association Acknowledged (Max Send PDV: 16370)
I0217 19:43:34.200992 main.cpp:215] Incoming Echo request from AET PYNETDICOM on IP 127.0.0.1, calling AET ANY-SCP
I0217 19:43:34.201039 CommandDispatcher.cpp:922] Received Echo Request
I0217 19:43:34.205005 CommandDispatcher.cpp:892] DUL Peer Requested Release
I0217 19:43:34.205042 CommandDispatcher.cpp:899] Association Release


5) Make a c-find request

from pydicom.dataset import Dataset

from pynetdicom import AE
from pynetdicom.sop_class import (
   
PatientRootQueryRetrieveInformationModelFind,
   
PatientRootQueryRetrieveInformationModelMove
)
import logging
LOGGER
= logging.getLogger('pynetdicom')
LOGGER
.setLevel(logging.DEBUG)
# Initialise the Application Entity
ae
= AE()

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

# Create our Identifier (query) dataset
ds
= Dataset()
ds
.PatientID = "3018030"
ds
.QueryRetrieveLevel = 'STUDY'

# Associate with peer AE at IP 127.0.0.1 and port 5678
assoc
= ae.associate('127.0.0.1', 4242, ae_title=b'ORTHANC')

if assoc.is_established:
   
print('Association Established')
   
# Use the C-FIND service to send the identifier
   
# A query_model value of 'P' means use the 'Patient Root Query Retrieve
   
#     Information Model - Find' presentation context
    responses
= assoc.send_c_find(ds, query_model='P')
   
for (status, identifier) in responses:
       
print('C-MOVE query status: 0x{0:04x}'.format(status.Status))
   
# Release the association
    assoc
.release()
   
print('Association Released')
else:
   
print('Association rejected or aborted')


ds
.clear()

Response....

I: Requesting Association
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.2.0
D: Our Implementation Version Name:   PYNETDICOM_120
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ORTHANC         
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:       =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:       =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 ======================
D: Accept Parameters:
D: ====================== BEGIN A-ASSOCIATE-AC =====================
D: Their Implementation Class UID:    1.2.276.0.7230010.3.0.3.6.2
D: Their Implementation Version Name: OFFIS_DCMTK_362
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ORTHANC         
D: Their Max PDU Receive Size:  16384
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D:   Context ID:        3 (Accepted)
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D: Role Selection: None
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ======================= END A-ASSOCIATE-AC ======================
I: Association Accepted
Association Established
I: Find SCU Request Identifiers:
I: 
I: # DICOM Dataset
I: (0008, 0052) Query/Retrieve Level                CS: 'STUDY'
I: (0010, 0020) Patient ID                          LO: '3018030'
I: 
I: Sending Find Request: MsgID 1
D: ===================== OUTGOING DIMSE MESSAGE ====================
D: Message Type                  : C-FIND RQ
D: Presentation Context ID       : 1
D: Message ID                    : 1
D: Affected SOP Class UID        : 1.2.840.10008.5.1.4.1.2.1.1
D: Identifier                    : Present
D: Priority                      : Low
D: ======================= END DIMSE MESSAGE =======================
D: Abort Parameters:
D: ========================== BEGIN A-ABORT ========================
D: Abort Source: DUL service-user
D: Abort Reason: No reason given
D: =========================== END A-ABORT =========================
E: Association Aborted

Orthanc Log....

I0217 19:44:08.164480 CommandDispatcher.cpp:491] Association Received from AET PYNETDICOM on IP 127.0.0.1
I0217 19:44:08.164669 main.cpp:195] Incoming connection from AET PYNETDICOM on IP 127.0.0.1, calling AET ORTHANC
I0217 19:44:08.164716 CommandDispatcher.cpp:689] Association Acknowledged (Max Send PDV: 16370)
I0217 19:44:08.169743 main.cpp:215] Incoming Find request from AET PYNETDICOM on IP 127.0.0.1, calling AET ORTHANC
W0217 19:44:08.169782 CommandDispatcher.cpp:812] Rejected Find request from remote DICOM modality with AET "PYNETDICOM" and hostname "127.0.0.1"
I0217 19:44:08.169804 CommandDispatcher.cpp:909] DIMSE failure (aborting association): DIMSE Caller passed in an illegal association
 


--

John Bell

unread,
Feb 17, 2019, 3:52:46 AM2/17/19
to Orthanc Users
Thank you Sebastien. That has helped to resolve the C-FIND issue and it is now returning the query correctly.

However, the next call which is C-Move so that i can retrieve the information from the PACS server.

from pydicom.dataset import Dataset

from pynetdicom import AE
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelMove


import logging
LOGGER
= logging.getLogger('pynetdicom')
LOGGER
.setLevel(logging.DEBUG)

# Initialise the Application Entity

ae
= AE(ae_title='PYNETDICOM',port=2000)


# Add a requested presentation context

ae
.add_requested_context(PatientRootQueryRetrieveInformationModelMove)

# Create out identifier (query) dataset
ds
= Dataset()
ds
.PatientID = "bc2995e9-14ea-4859-bfd5-dd030e8a2440"
ds
.QueryRetrieveLevel = 'STUDY'
ds
.PatientName = '*'
ds
.Modality = '*'
ds
.StudyInstanceUID = '*'
ds
.SeriesInstanceUID = '*'
ds
.StudyDate=''
ds
.StudyTime=''
ds
.AccessionNumber=''
ds
.StudyID=''


# Associate with peer AE at IP 127.0.0.1 and port 11112

assoc
= ae.associate('127.0.0.1', 4242, ae_title=b'ORTHANC')

if assoc.is_established:

   
# Use the C-MOVE service to send the identifier

   
# A query_model value of 'P' means use the 'Patient Root Query

   
#   Retrieve Information Model - Move' presentation context
    responses
= assoc.send_c_move(ds, b'PYNETDICOM', query_model='S')


   
for (status, identifier) in responses:

       
if status:

           
print('C-MOVE query status: 0x{0:04x}'.format(status.Status))

           
print(identifier)
           
# If the status is 'Pending' then the identifier is the C-MOVE response
           
if status.Status in (0xFF00, 0xFF01):
               
print(identifier)

       
else:
           
print('Connection timed out, was aborted or received invalid response')

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


Orthanc Log
 
I0217 21:47:12.506765 CommandDispatcher.cpp:491] Association Received from AET PYNETDICOM on IP 127.0.0.1
I0217 21:47:12.506886 main.cpp:195] Incoming connection from AET PYNETDICOM on IP 127.0.0.1, calling AET ORTHANC
I0217 21:47:12.506958 CommandDispatcher.cpp:689] Association Acknowledged (Max Send PDV: 16370)
I0217 21:47:42.581073 CommandDispatcher.cpp:909] DIMSE failure (aborting association): DIMSE No data available (timeout in non-blocking mode)


PynetDicom Output

I: Requesting Association
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.2.0
D: Our Implementation Version Name:   PYNETDICOM_120
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ORTHANC         
D: Our Max PDU Receive Size:    16382
D: Presentation Context:
D:   Context ID:        1 (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:       =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 ======================
D: Accept Parameters:
D: ====================== BEGIN A-ASSOCIATE-AC =====================
D: Their Implementation Class UID:    1.2.276.0.7230010.3.0.3.6.2
D: Their Implementation Version Name: OFFIS_DCMTK_362
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ORTHANC         
D: Their Max PDU Receive Size:  16384
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D: Role Selection: None
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ======================= END A-ASSOCIATE-AC ======================
I: Association Accepted
E: No suitable presentation context for the SCU role has been accepted by the peer for the SOP Class 'Study Root Query/Retrieve Information Model - MOVE'
Traceback (most recent call last):
  File "qrMove.py", line 37, in <module>
    responses = assoc.send_c_move(ds, b'PYNETDICOM', query_model='S')
  File "/anaconda3/lib/python3.6/site-packages/pynetdicom/association.py", line 1493, in send_c_move
    context = self._get_valid_context(sop_class, '', 'scu')
  File "/anaconda3/lib/python3.6/site-packages/pynetdicom/association.py", line 317, in _get_valid_context
    raise ValueError(msg)
ValueError: No suitable presentation context for the SCU role has been accepted by the peer for the SOP Class 'Study Root Query/Retrieve Information Model - MOVE'
D: Abort Parameters:
D: ========================== BEGIN A-ABORT ========================
D: Abort Source: DUL service-user
D: Abort Reason: No reason given
D: =========================== END A-ABORT =========================

The patient data uploaded to Orthanc 

Screen Shot 2019-02-17 at 9.48.38 PM.png




Another issue, is that after i have made a call the first time.... subsequent calls return the following error on the client side.

E: Association request failed: unable to connect to remote
E: TCP Initialisation Error: Connection refused
E: Association Aborted
Association rejected, aborted or never connected

b...@osimis.io

unread,
Feb 25, 2019, 1:46:06 PM2/25/19
to Orthanc Users
Dear John,

I was able to execute a move operation when specifying another running Orthanc instance as the destination. However, this requires that you add 

ae.add_requested_context(StudyRootQueryRetrieveInformationModelMove)

right below (or instead !) : 

ae.add_requested_context(PatientRootQueryRetrieveInformationModelMove)

(indeed I think you tried to execute a study-level query but you did not publish this capability to Orthanc)

It also seems that you want to use pynetdicom as an SCP. According to the documentation, this requires that you spin up a server by running 

ae.start_server((host, port))

(as mentioned in https://github.com/pydicom/pynetdicom.)

It does not look like you can use pynetdicom as both an SCU and an SCP in the same Python interpreter. You may try to launch a script for the SCU part and another script that listens for the SCP destination. I have not tried going that route, though.

Hope this helps!

Benjamin
Reply all
Reply to author
Forward
0 new messages