DICOM Basics using Java - Query and Retrieve Operations (C-GET)

How the C-GET Composite Service Works

This article is part of my series of articles on the DICOM standard. If you are totally new to DICOM, please have a quick look at my earlier article titled “DICOM Explained: How Medical Images Are Stored, Moved and Shared in 2026” for a quick introduction to the standard. It may also be useful to look at the other tutorials in this series to get up to speed on a number of topics including DICOM Encoding, SOPs and IODs. Before you dive into this article, please have a look at my previous DICOM networking-related tutorials on DICOM Verification and on DICOM Associations as a basic understanding of those topics is required to understand the material that is covered here. This tutorial also assumes that you know the basics of Java or any equivalent object-oriented language such as C# or C++. A basic understanding of networking will also be useful to have but is not mandatory.

Introduction

In this tutorial, we are going to continue exploring how query retrieval operations are implemented using the DICOM standard. We have looked at two other composite operations in this group already. They are namely, the the C-FIND and the the C-MOVE operations. C-FIND, as you will recall, allows you to query for DICOM data (images, structured reports, etc), and the C-MOVE operation allow you to then retrieve this data (as well as move this data to a specified destination). We are now going to look at one more way to retrieve DICOM data using a more aptly named operation called the C-GET composite operation which works slightly different under the covers than the C-MOVE operation. C-GET is not as popular as C-MOVE, but very useful nevertheless. Most users coming from a non-healthcare background will also be able to relate to it much more easily since it operates very much like a GET request works when using the HTTP protocol.

The PixelMed Java DICOM Toolkit - Quick Overview

For the purposes of illustrating many aspects of DICOM that I plan to cover in this tutorial series, I will be using a freely available and powerful DICOM toolkit called PixelMed Java DICOM Toolkit. This is a completely stand-alone DICOM toolkit that provides functionality for DICOM file and directory processing, image viewing as well as DICOM networking-related operations. This toolkit is completely free for both commercial or non-profit use. It is well documented and also has a small discussion forum and mailing list for users. The list of features contained within this toolkit is quite comprehensive. Please keep in mind that the use of this toolkit in my tutorial does not in anyway imply my official endorsement of it for implementing a production application. Every situation is unique, and only you are ultimately in the best position to decide that. This article is also not meant to be a tutorial on this toolkit, and my focus here is simply to tie DICOM theory to what a practical (although simple) implementation might look like. So, if your goal is to learn how to use the PixelMed library, I would encourage you to visit its website or check out the discussion forum or StackOverflow discussion pages for any assistance.

Before We Get Started…

Much like my previous programming examples, I will use the most bare minimum code and approach to help illustrate the concepts that I cover in this tutorial. This means that the code I write here is best suited to simply show the concept that I am trying to explain and is not necessarily the most efficient code to deploy in real life and in your production application.

To get started, you will need to configure a few things on your machine including a Java development environment as well as the PixelMed toolkit before you can run the example if you want to try this out yourself.

  • Download and install the Eclipse Java IDE from here (or use any other IDE you prefer)
  • Download the PixelMed toolkit library from here
  • Ensure that the PixelMed.jar library is included in your Java project’s class path (some examples may require additional runtime dependencies such as JAI Image IO Tools that can be found on PixelMed software download. Look for a tar compressed file called pixelmedjavadicom_dependencyrelease.YYYYMMDD.tar.bz2 or something similar)
  • You can find the source code and images used in this tutorial on GitHub
  • You can download more DICOM images from this site if you want as well

PACS Server Requirement

In addition to the tools described above, you will also need a DICOM server to execute some of the operations described in this tutorial. If you don't have access to one, you have one of two options. First option is to use Dr. Dave Harvey's free online PACS server provided here. Dr. Dave Harvey is a radiologist by background who runs MedicalConnections, a software company which provides medical imaging-related technology consulting services for clients as well toolkits for developers in the DICOM space. Although I have never used his toolkit, I have reached out to him for help on DICOM matters in the past, and he was kind enough to point me to some very useful material to read or look at regarding DICOM. So, check out his toolkit if you are looking for a commercial solution for your DICOM requirement that is compatible with the Microsoft platform. Another option is to download one of the many open source PACS servers available on the Internet. Orthanc Server is one such tool. Please see my article on getting started with Orthanc Server for more information.

“I dream of lost vocabularies that might express some of what we no longer can..” ~ Jack Gilbert, The Great Fires

Retrieving DICOM Data using C-GET

C-GET is another (and more 'modern') way for retrieval of DICOM data that you may be interested in. Unlike the C-MOVE operation (with a slightly counter-intuitive name) that lets you both retrieve data but also send data to an entirely different destination, the C-GET operation does exactly what you think it would (or should) which is to get the DICOM data that you are interested in. It also uses a single DICOM association (instead of two like C-MOVE does) to do the data retrieval operation making it easier to use from a configuration standpoint since there are fewer ports to deal with.

Despite its more intuitive name, and even though data can be sent data back to the caller using the same DICOM association, it may be a bit surprising to learn that the C-MOVE operation still remains the more popular method used for data retrieval. There are primarily two reasons for this. The first reason is that C-MOVE will only send data to callers who are already registered in the AE lookup tables making PACS administrators usually a bit more trusting of this approach. The second reason for C-MOVE's popularity is because it is simpler to implement than C-GET which requires far more negotiations and role switching between the same devices since all of this activity must happen over a single connection. For these reasons, a vast number of PACS manufacturers have historically not supported the C-GET operation*. Still, C-GET has a number of uses in diagnostic imaging such as when pulling images over the Internet from a PACS system deployed within a hospital system (which C-MOVE can only do with a lot of difficulty since additional ports and AE titles have to configured), and is also useful when retrieving images directly on the same association without needing the requesting application to run a separate C-STORE SCP (which is required with C-MOVE).The diagram below should hopefully illustrate how the C-GET operation works.

Retrieve with C-GET saravanansubramanian.com Device A C-GET SCU + C-STORE SCP Device B C-GET SCP + C-STORE SCU, e.g. a PACS A-ASSOCIATE-RQ contexts: Study Root Q/R - GET, plus every storage SOP class expected back, each with SCP role negotiated for Device A A-ASSOCIATE-AC 1. QUERY (OPTIONAL) to learn the UIDs to retrieve C-FIND-RQ + Identifier C-FIND-RSP Pending ... Success see the C-FIND article 2. RETRIEVE everything below travels on the SAME association C-GET-RQ + Identifier Query/Retrieve Level and the Study / Series / SOP Instance UIDs C-STORE-RQ + data set roles reverse: Device B is now the C-STORE SCU, Device A the C-STORE SCP C-STORE-RSP status C-GET-RSP status = Pending Remaining / Completed / Failed / Warning sub-operation counts ... C-STORE-RQ / RSP repeated for every instance, with a Pending C-GET-RSP after each ... C-GET-RSP status = Success (0000) or Warning / Failure if any sub-operations failed A-RELEASE-RQ A-RELEASE-RP No callback connection is needed, which is why C-GET works through firewalls and NAT where C-MOVE does not. The price: the SCU must be able to receive (act as a C-STORE SCP) on the connection it opened, which older archives do not support.

From a DICOM communications perspective, a DIMSE command ('C-GET-RQ') coupled with a DICOM IOD object containing the criteria for the data that needs to be moved to a specified destination is transmitted from the client (C-GET-SCU) to the service provider (C-GET SCP). The criteria is specified through the "matching keys" attribute that we saw during our tutorials on C-FIND and C-MOVE. In addition, query/retrieve level information is also transmitted which helps specify what we want to retrieve (an entire study, series, image, etc). A character set may sometimes also be specified to control the character encoding of the value representations of the DICOM data that is returned. The C-GET SCP then switches roles to a C-STORE SCU and sends a 'C-STORE-RQ' command and any data matching the criteria to the specified destination. The C-GET SCP continues to communicate the status of the C-STORE operation such as the number of files transferred, number of files pending for transmission, any warnings and errors that arise during the operation, etc. The C-GET SCU may also initiate a cancel operation (using 'C-CANCEL-GET-RQ') at anytime at which time any C-STORE operations that are in progress will also be canceled, and a status of 'cancelled' in communicated back to the C-GET SCU. The C-GET request and response structures are shown below for reference. Please see the official DICOM documentation for more details as that is pretty much all the theory I can cover in this article.

C-GET-RQ - request message fields

Message fieldTagVRVMDescription
Command Group Length(0000,0000)UL1The even number of bytes from the end of this value field to the beginning of the next group
Affected SOP Class UID(0000,0002)UI1SOP Class UID associated with the operation
Command Field(0000,0100)US1Distinguishes the DIMSE-C operation conveyed by this message - 0010H for C-GET-RQ
Message ID(0000,0110)US1Implementation-specific value that distinguishes this message from other messages
Priority(0000,0700)US1LOW = 0002H, MEDIUM = 0000H, HIGH = 0001H
Command Data Set Type(0000,0800)US1Any value other than 0101H - indicates that a data set is present in the message
Identifier(no tag)--Data set that encodes what to retrieve: the Query/Retrieve Level and the unique keys (Study / Series / SOP Instance UIDs)

C-GET-RSP - response message fields

Message fieldTagVRVMDescription
Command Group Length(0000,0000)UL1The even number of bytes from the end of this value field to the beginning of the next group
Affected SOP Class UID(0000,0002)UI1SOP Class UID associated with the operation
Command Field(0000,0100)US1Distinguishes the DIMSE-C operation conveyed by this message - 8010H for C-GET-RSP
Message ID Being Responded To(0000,0120)US1Set to the value of the Message ID (0000,0110) used in the associated request
Command Data Set Type(0000,0800)US10101H (Null) if no data set is present; any other value indicates a data set is included
Status(0000,0900)US1Result of the operation - Success, Pending, Warning, Failure or Cancel (the status codes are defined in Annex C)
Number of Remaining Sub-operations(0000,1020)US1Number of C-STORE sub-operations still to be invoked
Number of Completed Sub-operations(0000,1021)US1Number of C-STORE sub-operations that completed successfully
Number of Failed Sub-operations(0000,1022)US1Number of C-STORE sub-operations that failed
Number of Warning Sub-operations(0000,1023)US1Number of C-STORE sub-operations that generated warning responses
Identifier(no tag)--Optional data set carrying status detail, e.g. the list of failed SOP Instance UIDs

Adapted from DICOM PS3.7, Section 9.3.3 (C-GET Protocol). A data set that accompanies a command is not itself a command field, hence "no tag".

Example of C-GET Operation

Let us proceed to look at a quick example using the PixelMed toolkit to perform the C-GET SCU operation. I am going to connect to the public DICOM server made available by MedicalConnections information on which is provided here. Please read the instructions provided in that link to configure the code as necessary for your particular situation. I am going to send a request to it to first find any DICOM studies pertaining to a specific patient, and have it send any matched studies sent to my Java client. In the overall operation, my Java client is acting in the role of a C-GET SCU and switches roles to C-STORE SCP as needed, and the remote MedicalConnections DICOM server is acting as both a C-GET SCP and as a C-STORE SCU. The code should be pretty self explanatory if you understood the theory behind this operation which was explained in the previous sections.

    package com.saravanansubramanian.dicom.pixelmedtutorial;

    import java.io.File;
    import java.io.IOException;
    import java.util.HashSet;
    import java.util.Set;

    import com.pixelmed.dicom.Attribute;
    import com.pixelmed.dicom.AttributeList;
    import com.pixelmed.dicom.AttributeTag;
    import com.pixelmed.dicom.CodeStringAttribute;
    import com.pixelmed.dicom.DicomException;
    import com.pixelmed.dicom.SOPClass;
    import com.pixelmed.dicom.SpecificCharacterSet;
    import com.pixelmed.dicom.StoredFilePathStrategy;
    import com.pixelmed.dicom.TagFromName;
    import com.pixelmed.dicom.UniqueIdentifierAttribute;
    import com.pixelmed.network.DicomNetworkException;
    import com.pixelmed.network.FindSOPClassSCU;
    import com.pixelmed.network.GetSOPClassSCU;
    import com.pixelmed.network.IdentifierHandler;
    import com.pixelmed.network.ReceivedObjectHandler;

    public class DicomCGetFunctionalityDemo {

        public static void main(String arg[]) {
            
            //Summary of what we are doing here:
            
            //1. Perform a C-FIND operation for all studies matching a specific patient
            //2. For each study found, retrieve all DICOM objects belonging to the study
            //3. The C-FIND operation should help us find and later specify the SOP classes 
            //   that we need to provide for the C-GET operation
            //4. As each file is received, write the information about the incoming data to the console

            try {

                // use the default character set for VR encoding - override this as necessary
                SpecificCharacterSet specificCharacterSet = new SpecificCharacterSet((String[]) null);
                AttributeList identifier = new AttributeList();

                // build the attributes that you would like to retrieve as well as passing in
                // any search criteria
                identifier.putNewAttribute(TagFromName.QueryRetrieveLevel).addValue("STUDY"); // specific query root
                identifier.putNewAttribute(TagFromName.PatientName, specificCharacterSet).addValue("Bowen*");
                identifier.putNewAttribute(TagFromName.PatientID, specificCharacterSet);
                identifier.putNewAttribute(TagFromName.PatientBirthDate);
                identifier.putNewAttribute(TagFromName.PatientSex);
                identifier.putNewAttribute(TagFromName.StudyInstanceUID);
                identifier.putNewAttribute(TagFromName.SOPInstanceUID);
                identifier.putNewAttribute(TagFromName.StudyDescription);
                identifier.putNewAttribute(TagFromName.StudyDate);
                identifier.putNewAttribute(TagFromName.SOPClassesInStudy);

                // retrieve all studies belonging to patient with name 'Bowen'
                new FindSOPClassSCU("www.dicomserver.co.uk", 104, "MEDCONN", "OurFindScu",
                        SOPClass.StudyRootQueryRetrieveInformationModelFind, identifier, new OurFindHandler());
                

            } catch (Exception e) {
                e.printStackTrace(System.err); // in real life, do something about this exception
                System.exit(0);
            }
        }

    }

    class OurFindHandler extends IdentifierHandler {

        private static String GetSCP_Address = "www.dicomserver.co.uk";
        private static String GetSCP_AE_Title = "MEDCONN";
        private static int GetSCP_Port_Number = 104;
        private static String GetSCU_AE_TITLE = "JavaClient";
        File pathToStoreIncomingDicomFiles = new File("c:\\RetrievedDicomData");

        public static int resultsFound = 0;

        @SuppressWarnings("unchecked")
        @Override
        public void doSomethingWithIdentifier(AttributeList attributeListForFindResult) throws DicomException {
            resultsFound++;
            System.out.println("Matched result:" + attributeListForFindResult);

            String studyInstanceUID = attributeListForFindResult.get(TagFromName.StudyInstanceUID)
                    .getSingleStringValueOrEmptyString();
            System.out.println("studyInstanceUID of matched result:" + studyInstanceUID);

            Set<String> setofSopClassesExpected = new HashSet<String>();
            Attribute sopClassesInStudy = attributeListForFindResult.get(TagFromName.SOPClassesInStudy);
            if (sopClassesInStudy != null) {
                String[] sopClassesInStudyList = sopClassesInStudy.getStringValues();
                for (String sopClassInStudy : sopClassesInStudyList) {
                    setofSopClassesExpected.add(sopClassInStudy);
                }
            } else {
                //if SOP class data for study is not found, then supply all storage SOP classes
                setofSopClassesExpected = (Set<String>) SOPClass.getSetOfStorageSOPClasses();
            }

            try {

                AttributeList identifier = new AttributeList();
                {
                    AttributeTag tag = TagFromName.QueryRetrieveLevel;
                    Attribute attribute = new CodeStringAttribute(tag);
                    attribute.addValue("STUDY");
                    identifier.put(tag, attribute);
                }
                {
                    AttributeTag tag = TagFromName.StudyInstanceUID;
                    Attribute attribute = new UniqueIdentifierAttribute(tag);
                    attribute.addValue(studyInstanceUID);
                    identifier.put(tag, attribute);
                }

                //please see PixelMed documentation if you want to dig deeper into the parameters and their relevance
                new GetSOPClassSCU(GetSCP_Address, 
                        GetSCP_Port_Number, 
                        GetSCP_AE_Title, 
                        GetSCU_AE_TITLE,
                        SOPClass.StudyRootQueryRetrieveInformationModelGet, 
                        identifier, 
                        new IdentifierHandler(), //override and provide your own handler if you need to do anything else
                        pathToStoreIncomingDicomFiles, 
                        StoredFilePathStrategy.BYSOPINSTANCEUIDINSINGLEFOLDER,
                        new OurCGetOperationStoreHandler(), 
                        setofSopClassesExpected, 
                        0, 
                        true, 
                        false, 
                        false);

            } catch (Exception e) {
                System.out.println("Error during get operation" + e); // in real life, do something about this exception
                e.printStackTrace(System.err);
            }
        }

    }

    class OurCGetOperationStoreHandler extends ReceivedObjectHandler {
        
        @Override
        public void sendReceivedObjectIndication(String filename, String transferSyntax, String calledAetTitle)
                throws DicomNetworkException, DicomException, IOException {
            
            System.out.println("Incoming data from " + calledAetTitle + "...");
            System.out.println("filename:" + filename);
            System.out.println("transferSyntax:" + transferSyntax);

        }

    }

19:05:36,356 DEBUG [main] AssociationInitiator:330 - establishing Association
16:44:30,921 DEBUG [main] FindSOPClassSCU:330 - Association[0]: Hostname: www.dicomserver.co.uk
Association[0]: Port: 104
Association[0]: Called AE Title:  MEDCONN
Association[0]: Calling AE Title: OurFindScu
[Presentation Context ID: 0x1 (result 0x0 - acceptance)
	Abstract Syntax:
		1.2.840.10008.5.1.4.1.2.2.1
	Transfer Syntax(es):
		1.2.840.10008.1.2.1
, Presentation Context ID: 0x3 (result 0x0 - acceptance)
	Abstract Syntax:
		1.2.840.10008.5.1.4.1.2.2.1
	Transfer Syntax(es):
		1.2.840.10008.1.2
, Presentation Context ID: 0x5 (result 0x0 - acceptance)
	Abstract Syntax:
		1.2.840.10008.5.1.4.1.2.2.1
	Transfer Syntax(es):
		1.2.840.10008.1.2.1
]
16:44:30,923 DEBUG [main] FindSOPClassSCU:347 - request identifier
(0x0008,0x0018) SOPInstanceUID VR=<UI> VL=<0x0> <>
(0x0008,0x0020) StudyDate VR=<DA> VL=<0x0> <>
(0x0008,0x0052) QueryRetrieveLevel VR=<CS> VL=<0x5> <STUDY>
(0x0008,0x0062) SOPClassesInStudy VR=<UI> VL=<0x0> <>
(0x0008,0x1030) StudyDescription VR=<LO> VL=<0x0> <>
(0x0010,0x0010) PatientName VR=<PN> VL=<0x6> <Bowen*>
(0x0010,0x0020) PatientID VR=<LO> VL=<0x0> <>
(0x0010,0x0030) PatientBirthDate VR=<DA> VL=<0x0> <>
(0x0010,0x0040) PatientSex VR=<CS> VL=<0x0> <>
(0x0020,0x000d) StudyInstanceUID VR=<UI> VL=<0x0> <>

16:44:30,925 DEBUG [main] FindSOPClassSCU:347 - Using context ID 5
16:44:30,932 DEBUG [main] FindSOPClassSCU:330 - waiting for PDUs
16:44:31,417 DEBUG [main] CompositeResponseHandler:330 - sendPDataIndication():
16:44:31,418 DEBUG [main] CompositeResponseHandler:330 - sendPDataIndication(): last fragment of command seen
16:44:31,423 DEBUG [main] FindSOPClassSCU:347 - evaluateStatusAndSetSuccess:
(0x0000,0x0000) CommandGroupLength VR=<UL> VL=<0x4> [0x4c]
(0x0000,0x0002) AffectedSOPClassUID VR=<UI> VL=<0x1c> <1.2.840.10008.5.1.4.1.2.2.1

Matched result:(0x0008,0x0005) SpecificCharacterSet VR=<CS> VL=<0x0> <>
(0x0008,0x0020) StudyDate VR=<DA> VL=<0x8> <20191017>
(0x0008,0x0052) QueryRetrieveLevel VR=<CS> VL=<0x6> <STUDY >
(0x0008,0x0054) RetrieveAETitle VR=<AE> VL=<0x8> <MEDCONN >
(0x0008,0x0062) SOPClassesInStudy VR=<UI> VL=<0x20> <1.2.840.10008.5.1.4.1.1.77.1.5.1>
(0x0008,0x1030) StudyDescription VR=<LO> VL=<0x0> <>
(0x0010,0x0010) PatientName VR=<PN> VL=<0x12> <Bowen^William^^Dr >
(0x0010,0x0020) PatientID VR=<LO> VL=<0x6> <PAT004>
(0x0010,0x0030) PatientBirthDate VR=<DA> VL=<0x0> <>
(0x0010,0x0040) PatientSex VR=<CS> VL=<0x0> <>
(0x0020,0x000d) StudyInstanceUID VR=<UI> VL=<0x1a> <1.2.826.0.1.3680043.11.106>

studyInstanceUID of matched result:1.2.826.0.1.3680043.11.106

Incoming data from MEDCONN...
filename:c:\RetrievedDicomData\2.25.101703809854595919801950834747690813074
transferSyntax:1.2.840.10008.1.2.1

Incoming data from MEDCONN...
filename:c:\RetrievedDicomData\2.25.129202192333655564630684515562976033020
transferSyntax:1.2.840.10008.1.2.1

Incoming data from MEDCONN...
filename:c:\RetrievedDicomData\2.25.270772719488604832156479486332443261838
transferSyntax:1.2.840.10008.1.2.1

Incoming data from MEDCONN...
filename:c:\RetrievedDicomData\2.25.116421094412591474296935738553064334108
transferSyntax:1.2.840.10008.1.2.1

“The seeds we sow today will grow to serve as shades for weary travellers tomorrow.” ~ Nike Thaddeus

A portion of the output results seen in the console window is shown above. All DICOM objects pertaining to the matched study for the patient with the name "Bowen" are returned to the C-GET SCU, and any additional processing can be performed on these files as needed. Often, when the DICOM images are retrieved using the C-GET operation, they are viewed on high resolution monitors using either commercial or open source DICOM viewing software. From this, a radiologist may create dictations and other reports, and these in turn are pushed to other systems to enable additional patient care-related activities.

Conclusion

This concludes the article on how the C-GET composite operation works. The C-FIND, C-MOVE and C-GET composite services that we have looked at in the last three articles of my DICOM series form the foundation for all query and retrieval operations within any DICOM-related workflows. In the next tutorial in this series, I will cover the C-STORE operation which is used to push DICOM objects to remote destinations. See you then!

*You may want to search on the Internet if you are interested in digging further on this topic, but Dr. Dave Clunie has written an excellent article here on why this may be the case.