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 the Patient Root Query/Retrieve Information Model - Find at the Patient level.

from pydicom.dataset import Dataset

from pynetdicom import AE
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelFind

# Initialise the Application Entity
ae = AE()

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

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

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

if assoc.is_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-FIND query status: 0x{0:04x}'.format(status.Status))

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

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

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, 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.

import os

from pydicom import dcmread
from pydicom.dataset import Dataset

from pynetdicom import AE
from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelFind

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

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

# Implement the AE.on_c_store callback
def on_c_find(ds, context, info):
    """Respond to a C-FIND request Identifier `ds`.

    Parameters
    ----------
    ds : pydicom.dataset.Dataset
        The Identifier dataset send by the peer.
    context : namedtuple
        The presentation context that the dataset was sent under.
    info : dict
        Information about the association and query/retrieve request.

    Yields
    ------
    status : int or pydicom.dataset.Dataset
        The status returned to the peer AE in the C-FIND response. Must be
        a valid C-FIND status value for the applicable Service Class as
        either an ``int`` or a ``Dataset`` object containing (at a
        minimum) a (0000,0900) *Status* element.
    identifier : pydicom.dataset.Dataset
        If the status is 'Pending' then the *Identifier* ``Dataset`` for a
        matching SOP Instance. The exact requirements for the C-FIND
        response *Identifier* are Service Class specific (see the
        DICOM Standard, Part 4).

        If the status is 'Failure' or 'Cancel' then yield ``None``.

        If the status is 'Success' then yield ``None``, however yielding a
        final 'Success' status is not required and will be ignored if
        necessary.
    """
    # 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 possibile values...

        # Skip the other possible attributes...

    # Skip the other QR levels...

    for instance in matching:
        identifier = Dataset()
        identifier.PatientName = instance.PatientName
        identifier.QueryRetrieveLevel = ds.QueryRetrieveLevel

        # Pending
        yield (0xFF00, identifier)

ae.on_c_find = on_c_find

# Start listening for incoming association requests
ae.start()