DICOM Basics using Java - Introduction to DICOMweb

Introduction

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 "Introduction to the DICOM Standard" for a quick introduction. In this tutorial, we will explore DICOMweb, which is the modern RESTful approach to accessing and manipulating DICOM data over HTTP/HTTPS.

What is DICOMweb?

DICOMweb is a set of RESTful services for medical imaging defined in the DICOM standard. It provides a simpler, web-friendly alternative to the traditional DICOM network protocols (DIMSE). DICOMweb makes it easier to build web applications, mobile apps, and cloud-based solutions for medical imaging.

The three core DICOMweb services are:

  • QIDO-RS (Query based on ID for DICOM Objects - RESTful Services) - Search for DICOM objects
  • WADO-RS (Web Access to DICOM Objects - RESTful Services) - Retrieve DICOM objects
  • STOW-RS (Store Over the Web - RESTful Services) - Store DICOM objects

Benefits of DICOMweb

  • Simplicity - Uses standard HTTP methods (GET, POST) familiar to web developers
  • Interoperability - Works with standard web technologies and infrastructure
  • Scalability - Can leverage HTTP caching, load balancing, and CDNs
  • Security - Uses standard HTTPS and authentication mechanisms
  • Mobile-friendly - Easier to access from mobile devices and web browsers

The Theory Behind DICOMweb

DICOMweb represents a fundamental architectural shift from connection-oriented stateful protocols to stateless RESTful services. Understanding this shift helps explain both DICOMweb's advantages and its design decisions.

The Limitations of Traditional DICOM Networking

Traditional DICOM networking (DIMSE over TCP) was designed in the early 1990s with assumptions that don't match modern IT infrastructure:

  • Stateful Associations: Each operation requires establishing an association (negotiating presentation contexts), which adds latency and doesn't work well through firewalls/load balancers
  • Binary Protocol: The DIMSE protocol is not human-readable and requires specialized tools to debug
  • Non-Standard Port: Port 104/11112 is often blocked by firewalls, unlike HTTP/HTTPS (80/443)
  • No Caching: Every request hits the origin server; no HTTP caching infrastructure can help
  • Complex Security: TLS for DICOM requires bilateral certificate exchange, unlike web PKI

RESTful Design Principles

DICOMweb follows REST (Representational State Transfer) architectural constraints:

  • Stateless: Each request contains all information needed; no session state on server
  • Resource-Based: Studies, series, and instances are addressable resources with URLs
  • Uniform Interface: Standard HTTP methods (GET, POST) with standard semantics
  • Cacheable: Responses can indicate cacheability, enabling CDN and proxy caching
  • Layered System: Clients don't know if they're talking directly to origin or through proxies

The Multipart Response Challenge

One complexity in DICOMweb is handling DICOM's binary format over HTTP. WADO-RS uses multipart/related responses to return multiple DICOM instances in a single HTTP response. This is necessary because:

  • A study may contain hundreds of instances
  • Each instance must maintain its binary DICOM format
  • HTTP doesn't natively support returning multiple "files" in one response

The multipart format wraps each DICOM instance with boundary markers and content-type headers, allowing extraction of individual instances from the response stream.

Cloud-Native Medical Imaging

DICOMweb enables "cloud-native" medical imaging architectures:

  • Horizontal Scaling: Stateless servers can scale behind load balancers
  • Edge Caching: CDNs can cache frequently-accessed images closer to users
  • API Gateways: Standard OAuth2/OIDC authentication at the gateway layer
  • Microservices: Different services can handle QIDO, WADO, STOW independently
  • Serverless: Functions can process DICOMweb requests without persistent servers

Tools for Tutorial

“The Web as I envisaged it, we have not seen it yet. The future is still so much bigger than the past.” ~ Tim Berners-Lee

QIDO-RS: Querying DICOM Objects

QIDO-RS is the RESTful equivalent of C-FIND. It allows you to search for studies, series, and instances using HTTP GET requests with query parameters.

Key QIDO-RS Endpoints:

  • /studies - Search for studies
  • /studies/{studyUID}/series - Search for series within a study
  • /studies/{studyUID}/series/{seriesUID}/instances - Search for instances

Common Query Parameters:

  • PatientName, PatientID - Patient matching
  • StudyDate, StudyTime - Date/time filtering
  • Modality, ModalitiesInStudy - Modality filtering
  • AccessionNumber - Accession matching
  • limit, offset - Pagination
  • includefield - Specify fields to return

Example: QIDO-RS Query in Java

package com.saravanansubramanian.dicom.pixelmedtutorial;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

/**
 * DICOM QIDO-RS Query Demo
 */
public class QidoRsQueryDemo {

    // Orthanc DICOMweb base URL (requires DICOMweb plugin)
    private static final String DICOMWEB_BASE_URL = "http://localhost:8042/dicom-web";

    public static void main(String[] args) {
        try {
            System.out.println("=== DICOM QIDO-RS Query Demo ===\n");

            // Query all studies with limit
            System.out.println("--- Query All Studies (limit 10) ---");
            queryStudies(null, null, null, 10, 0);

            // Query by patient name
            System.out.println("\n--- Query by Patient Name ---");
            queryStudies("Doe*", null, null, 10, 0);

            // Query by modality
            System.out.println("\n--- Query CT Studies ---");
            queryStudies(null, "CT", null, 10, 0);

            // Query by date range
            System.out.println("\n--- Query by Date Range ---");
            queryStudies(null, null, "20240101-20241231", 10, 0);

            System.out.println("\n=== QIDO-RS Demo Complete ===");

        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    /**
     * Query for studies with optional filters
     */
    private static void queryStudies(String patientName, String modality,
            String studyDate, int limit, int offset) {

        try {
            StringBuilder urlBuilder = new StringBuilder(DICOMWEB_BASE_URL + "/studies?");

            // Add query parameters
            if (patientName != null && !patientName.isEmpty()) {
                urlBuilder.append("PatientName=")
                    .append(URLEncoder.encode(patientName, "UTF-8")).append("&");
            }
            if (modality != null && !modality.isEmpty()) {
                urlBuilder.append("ModalitiesInStudy=")
                    .append(URLEncoder.encode(modality, "UTF-8")).append("&");
            }
            if (studyDate != null && !studyDate.isEmpty()) {
                urlBuilder.append("StudyDate=")
                    .append(URLEncoder.encode(studyDate, "UTF-8")).append("&");
            }

            // Pagination
            urlBuilder.append("limit=").append(limit).append("&");
            urlBuilder.append("offset=").append(offset);

            // Specify fields to include
            urlBuilder.append("&includefield=").append(URLEncoder.encode("00100010", "UTF-8")); // PatientName
            urlBuilder.append("&includefield=").append(URLEncoder.encode("00100020", "UTF-8")); // PatientID
            urlBuilder.append("&includefield=").append(URLEncoder.encode("0020000D", "UTF-8")); // StudyInstanceUID

            String url = urlBuilder.toString();
            System.out.println("URL: " + url);

            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/dicom+json");

            int responseCode = conn.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            if (responseCode == 200) {
                try (BufferedReader reader = new BufferedReader(
                        new InputStreamReader(conn.getInputStream()))) {
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    System.out.println("Response length: " + response.length() + " chars");
                }
            } else {
                System.out.println("Error: " + conn.getResponseMessage());
            }

            conn.disconnect();

        } catch (Exception e) {
            System.out.println("Error querying studies: " + e.getMessage());
        }
    }
}

“Any sufficiently advanced technology is indistinguishable from magic.” ~ Arthur C. Clarke

WADO-RS: Retrieving DICOM Objects

WADO-RS is used to retrieve DICOM objects. It supports multiple response formats including DICOM (multipart/related) and JSON metadata.

Key WADO-RS Endpoints:

  • /studies/{studyUID} - Retrieve all instances in a study
  • /studies/{studyUID}/series/{seriesUID} - Retrieve all instances in a series
  • /studies/{studyUID}/series/{seriesUID}/instances/{instanceUID} - Retrieve a specific instance
  • /studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/rendered - Retrieve rendered image

STOW-RS: Storing DICOM Objects

STOW-RS allows you to store DICOM objects on a server using HTTP POST with multipart/related content.

Key STOW-RS Endpoints:

  • POST /studies - Store DICOM instances
  • POST /studies/{studyUID} - Store instances to a specific study

Example: STOW-RS Store in Java

package com.saravanansubramanian.dicom.pixelmedtutorial;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.util.UUID;

/**
 * DICOM STOW-RS Store Demo
 */
public class StowRsStoreDemo {

    private static final String DICOMWEB_BASE_URL = "http://localhost:8042/dicom-web";

    public static void main(String[] args) {
        try {
            System.out.println("=== DICOM STOW-RS Store Demo ===\n");

            String dicomFilePath = "C:\\path\\to\\sample.dcm";
            storeDicomFile(dicomFilePath);

            System.out.println("\n=== STOW-RS Demo Complete ===");

        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    /**
     * Store a DICOM file using STOW-RS
     */
    private static void storeDicomFile(String filePath) {
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                System.out.println("File not found: " + filePath);
                return;
            }

            String boundary = "----DicomBoundary" + UUID.randomUUID().toString();
            String url = DICOMWEB_BASE_URL + "/studies";

            System.out.println("Storing file: " + filePath);
            System.out.println("URL: " + url);

            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setDoOutput(true);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type",
                "multipart/related; type=\"application/dicom\"; boundary=" + boundary);
            conn.setRequestProperty("Accept", "application/dicom+json");

            byte[] fileContent = Files.readAllBytes(file.toPath());

            try (OutputStream out = conn.getOutputStream()) {
                // Write multipart boundary and headers
                out.write(("--" + boundary + "\r\n").getBytes());
                out.write("Content-Type: application/dicom\r\n\r\n".getBytes());

                // Write DICOM file content
                out.write(fileContent);

                // Write closing boundary
                out.write(("\r\n--" + boundary + "--\r\n").getBytes());
            }

            int responseCode = conn.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            if (responseCode == 200 || responseCode == 202) {
                System.out.println("File stored successfully!");
            } else {
                System.out.println("Error: " + conn.getResponseMessage());
            }

            conn.disconnect();

        } catch (Exception e) {
            System.out.println("Error storing file: " + e.getMessage());
        }
    }
}

DICOMweb Response Format

DICOMweb typically returns data in JSON format (application/dicom+json). The JSON structure uses DICOM tag numbers as keys with value representations:

[
  {
    "00100010": {
      "vr": "PN",
      "Value": [
        {
          "Alphabetic": "DOE^JOHN"
        }
      ]
    },
    "00100020": {
      "vr": "LO",
      "Value": ["12345"]
    },
    "0020000D": {
      "vr": "UI",
      "Value": ["1.2.3.4.5.6.7.8.9"]
    }
  }
]

Setting Up Orthanc with DICOMweb

To use DICOMweb with Orthanc, you need to enable the DICOMweb plugin in your Orthanc configuration:

{
  "Plugins": ["DICOMweb"],
  "DicomWeb": {
    "Enable": true,
    "Root": "/dicom-web/",
    "EnableWado": true,
    "WadoRoot": "/wado",
    "Ssl": false
  }
}

Conclusion

DICOMweb provides a modern, web-friendly approach to working with DICOM data. Its RESTful architecture makes it easier to build web applications, mobile apps, and cloud-based medical imaging solutions. The three core services (QIDO-RS, WADO-RS, and STOW-RS) provide complete query, retrieve, and store capabilities over HTTP. This concludes the DICOM Java programming series. I hope you have found these tutorials helpful in understanding the many aspects of the DICOM standard and how to implement them using Java. For more information about DICOM, please visit my series of articles on the DICOM standard.