Query/Retrieve (Find) Service Examples

The DICOM Query/Retrieve Service provides a mechanism for a service user to query the SOP Instances managed by a QR SCP. The QR (Find) SOP classes allow an SCU to receive a list of attributes matching the requested query. This is accomplished through the DIMSE C-FIND service.

Query/Retrieve (Find) SCU

Associate with a peer DICOM Application Entity and request the SCP search for SOP Instances with a Patient Name matching CITIZEN^Jan using Patient Root Query/Retrieve Information Model - Find at the 'PATIENT' level.

The value of the Query Retrieve Level determines what SOP Instances are actually transferred, you can find all the possible query level values in the following table. In this example we are querying for all the available data of a specific patient, so 'PATIENT' level is the appropriate one.

Query Retrieve Level

Effect

PATIENT

All SOP Instances related to a patient shall be transferred

STUDY

All SOP Instances related to a study shall be transferred

SERIES

All SOP Instances related to a series shall be transferred

IMAGE

Selected individual SOP Instances shall be transferred

from pydicom.dataset import Dataset

from pynetdicom import AE, debug_logger
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelFind

debug_logger()

ae = AE()
ae.add_requested_context(PatientRootQueryRetrieveInformationModelFind)

# Create our Identifier (query) dataset
ds = Dataset()
ds.PatientName = 'CITIZEN^Jan'
ds.QueryRetrieveLevel = 'PATIENT'

# Associate with the peer AE at IP 127.0.0.1 and port 11112
assoc = ae.associate("127.0.0.1", 11112)
if assoc.is_established:
    # Send the C-FIND request
    responses = assoc.send_c_find(ds, PatientRootQueryRetrieveInformationModelFind)
    for (status, identifier) in responses:
        if status:
            print('C-FIND 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')

The responses received from the SCP are dependent on the Identifier dataset keys and values, the Query/Retrieve level and the information model. For example, provided the optional attribute SOP Classes in Study is supported, the following query dataset should yield C-FIND responses containing the various SOP Class UIDs that make are in each study for a patient with Patient ID 1234567.

ds = Dataset()
ds.SOPClassesInStudy = ''
ds.PatientID = '1234567'
ds.StudyInstanceUID = ''
ds.QueryRetrieveLevel = 'STUDY'

Query/Retrieve (Find) SCP

The following represents a toy implementation of a Query/Retrieve (Find) SCP where the SCU has sent the following Identifier dataset under the Patient Root Query Retrieve Information Model - Find context.

ds = Dataset()
ds.PatientName = 'CITIZEN^Jan'
ds.QueryRetrieveLevel = 'PATIENT'

This is a very bad way of managing stored SOP Instances, in reality its probably best to store the instance attributes in a database and run the query against that, which is the approach taken by the qrscp application.

Check the handler implementation documentation to see the requirements for the evt.EVT_C_FIND handler.

 import os

 from pydicom import dcmread
 from pydicom.dataset import Dataset

 from pynetdicom import AE, evt
 from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelFind

 # Implement the handler for evt.EVT_C_FIND
 def handle_find(event):
     """Handle a C-FIND request event."""
     ds = event.identifier

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

     if 'QueryRetrieveLevel' not in ds:
         # Failure
         yield 0xC000, None
         return

     if ds.QueryRetrieveLevel == 'PATIENT':
         if 'PatientName' in ds:
             if ds.PatientName not in ['*', '', '?']:
                 matching = [
                     inst for inst in instances if inst.PatientName == ds.PatientName
                 ]

             # Skip the other possible values...

         # Skip the other possible attributes...

     # Skip the other QR levels...

     for instance in matching:
         # Check if C-CANCEL has been received
         if event.is_cancelled:
             yield (0xFE00, None)
             return

         identifier = Dataset()
         identifier.PatientName = instance.PatientName
         identifier.QueryRetrieveLevel = ds.QueryRetrieveLevel

         # Pending
         yield (0xFF00, identifier)

handlers = [(evt.EVT_C_FIND, handle_find)]

# Initialise the Application Entity and specify the listen port
ae = AE()

# Add the supported presentation context
ae.add_supported_context(PatientRootQueryRetrieveInformationModelFind)

# Start listening for incoming association requests
ae.start_server(("127.0.0.1", 11112), evt_handlers=handlers)