DICOM Basics using .NET and C# - Understanding DICOMweb
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOMweb, a set of RESTful web services for accessing DICOM objects over HTTP/HTTPS. DICOMweb provides a modern, web-friendly alternative to traditional DICOM DIMSE network protocols.
DICOMweb is particularly valuable for building web-based medical imaging applications, mobile viewers, and cloud-based solutions. It enables standard HTTP tools and libraries to interact with DICOM servers without requiring specialized DICOM networking implementations.
Prerequisites
Before you begin, ensure you have the following:
- A .NET development environment (Visual Studio or Visual Studio Code)
- A DICOMweb-capable server (Orthanc with DICOMweb plugin or DCM4CHEE)
- Basic understanding of REST APIs and HTTP
- You can find all the code demonstrated in this tutorial on GitHub here
“The Internet is becoming the town square for the global village of tomorrow.” ~ Bill Gates
The Theory Behind DICOMweb
Traditional DICOM networking was designed in the early 1990s when healthcare facilities had isolated networks and dedicated imaging equipment. The DIMSE protocol uses persistent associations, custom port numbers, and binary encoding - all barriers to integration with modern web infrastructure. DICOMweb represents a paradigm shift: reimagining DICOM access using the architectural principles that made the World Wide Web successful.
REST (Representational State Transfer) is the architectural style underlying HTTP. Resources are identified by URLs, manipulated through standard verbs (GET, POST), and represented in negotiated formats. DICOMweb maps DICOM operations to REST: studies, series, and instances become URL-addressable resources. GET retrieves them, POST stores them. This alignment means standard web tools - browsers, HTTP libraries, API gateways - work natively with DICOM data.
The three services (QIDO-RS, WADO-RS, STOW-RS) mirror the core DICOM operations: C-FIND becomes QIDO queries with URL parameters, C-MOVE/C-GET become WADO retrieval via GET requests, and C-STORE becomes STOW uploads via POST. The mapping isn't perfect (DIMSE has capabilities without direct REST equivalents), but covers the 80% use case elegantly.
For cloud-native architectures, DICOMweb is transformative. Load balancers, CDNs, API gateways, and container orchestration all understand HTTP natively. You can cache frequently accessed studies, geographically distribute DICOM data through CDN edge nodes, apply standard authentication (OAuth2) and authorization, and monitor traffic with standard observability tools. None of this works easily with DIMSE's custom protocol.
The JSON representation of DICOM metadata (application/dicom+json) enables frontend integration that was previously impractical. JavaScript in a browser can fetch study metadata, parse JSON natively, and render study lists without specialized DICOM libraries. This democratizes DICOM development, allowing web developers to build imaging applications without deep DICOM expertise.
DICOMweb Services Overview
DICOMweb defines three core RESTful services:
| Service | Method | Purpose |
|---|---|---|
| QIDO-RS | GET | Query based on ID for DICOM Objects - Search |
| WADO-RS | GET | Web Access to DICOM Objects - Retrieve |
| STOW-RS | POST | Store Over the Web - Upload |
QIDO-RS: Query Service
QIDO-RS enables searching for studies, series, or instances using query parameters:
| Endpoint | Description |
|---|---|
| /studies | Search all studies |
| /studies/{studyUID}/series | Search series in a study |
| /studies/{studyUID}/series/{seriesUID}/instances | Search instances |
Common query parameters:
| Parameter | Description |
|---|---|
| PatientName | Patient name (wildcards allowed) |
| PatientID | Patient identifier |
| StudyDate | Study date or range |
| Modality | Modality type (CT, MR, US, etc.) |
| limit | Maximum results to return |
| offset | Skip first N results |
WADO-RS: Retrieve Service
WADO-RS enables retrieving DICOM objects and metadata:
| Endpoint | Returns |
|---|---|
| /studies/{studyUID} | All instances in study |
| /studies/{studyUID}/series/{seriesUID} | All instances in series |
| /studies/{studyUID}/series/{seriesUID}/instances/{instanceUID} | Single instance |
| /studies/{studyUID}/metadata | Study metadata (JSON) |
| …/rendered | Rendered image (JPEG/PNG) |
STOW-RS: Store Service
STOW-RS enables uploading DICOM objects:
| Endpoint | Method | Content-Type |
|---|---|---|
| /studies | POST | multipart/related; type=“application/dicom” |
| /studies/{studyUID} | POST | Add to specific study |
Step 1 of 3: Querying with QIDO-RS
Let's implement QIDO-RS queries using HttpClient:
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace DICOMwebExample
{
public class Program
{
private static readonly string DicomWebBaseUrl = "http://localhost:8042/dicom-web";
public static async Task Main(string[] args)
{
Console.WriteLine("=== DICOMweb Services Tutorial ===");
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(DicomWebBaseUrl);
// QIDO-RS: Query for Studies
await QueryStudiesAsync(httpClient);
// WADO-RS: Retrieve example URLs
DisplayWadoRsExamples();
// STOW-RS: Store example
DisplayStowRsExample();
}
}
}
}
QIDO-RS Query Implementation
private static async Task QueryStudiesAsync(HttpClient httpClient)
{
Console.WriteLine("--- QIDO-RS: Querying for Studies ---");
try
{
// Query for all studies (limit to 10)
var qidoUrl = "/studies?limit=10";
Console.WriteLine($" GET {DicomWebBaseUrl}{qidoUrl}");
// Set Accept header for JSON response
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/dicom+json"));
var response = await httpClient.GetAsync(qidoUrl);
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($" Status: {response.StatusCode}");
Console.WriteLine($" Content-Type: {response.Content.Headers.ContentType}");
Console.WriteLine($" Response: {jsonContent.Substring(0, Math.Min(500, jsonContent.Length))}...");
}
else
{
Console.WriteLine($" Query failed: {response.StatusCode}");
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($" Connection failed: {ex.Message}");
Console.WriteLine(" Ensure Orthanc with DICOMweb plugin is running.");
}
}
Step 2 of 3: Retrieving with WADO-RS
WADO-RS supports multiple content types for different use cases:
private static void DisplayWadoRsExamples()
{
Console.WriteLine("--- WADO-RS: Retrieve Examples ---");
// Retrieve entire study as DICOM
Console.WriteLine(" Retrieve study (DICOM):");
Console.WriteLine($" GET {DicomWebBaseUrl}/studies/{{studyUID}}");
Console.WriteLine(" Accept: multipart/related; type=\"application/dicom\"");
// Retrieve study metadata as JSON
Console.WriteLine(" Retrieve metadata (JSON):");
Console.WriteLine($" GET {DicomWebBaseUrl}/studies/{{studyUID}}/metadata");
Console.WriteLine(" Accept: application/dicom+json");
// Retrieve rendered image
Console.WriteLine(" Retrieve rendered image:");
Console.WriteLine($" GET {DicomWebBaseUrl}/studies/{{studyUID}}/series/{{seriesUID}}/instances/{{instanceUID}}/rendered");
Console.WriteLine(" Accept: image/jpeg or image/png");
// Retrieve specific frames
Console.WriteLine(" Retrieve specific frame:");
Console.WriteLine($" GET .../instances/{{instanceUID}}/frames/1");
}
private static async Task RetrieveRenderedImageAsync(HttpClient httpClient,
string studyUid, string seriesUid, string instanceUid)
{
var url = $"/studies/{studyUid}/series/{seriesUid}/instances/{instanceUid}/rendered";
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("image/jpeg"));
var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var imageBytes = await response.Content.ReadAsByteArrayAsync();
Console.WriteLine($" Retrieved image: {imageBytes.Length} bytes");
// Save to file
await File.WriteAllBytesAsync("rendered_image.jpg", imageBytes);
}
}
Step 3 of 3: Storing with STOW-RS
STOW-RS uploads DICOM objects using multipart content:
private static void DisplayStowRsExample()
{
Console.WriteLine("--- STOW-RS: Store Example ---");
Console.WriteLine(" Store DICOM file:");
Console.WriteLine($" POST {DicomWebBaseUrl}/studies");
Console.WriteLine(" Content-Type: multipart/related; type=\"application/dicom\"");
}
private static async Task StoreDicomFileAsync(HttpClient httpClient, string dicomFilePath)
{
Console.WriteLine("--- STOW-RS: Storing DICOM File ---");
// Read DICOM file
var dicomBytes = await File.ReadAllBytesAsync(dicomFilePath);
// Create multipart content
using var content = new MultipartContent("related");
content.Headers.ContentType.Parameters.Add(
new NameValueHeaderValue("type", "\"application/dicom\""));
var dicomContent = new ByteArrayContent(dicomBytes);
dicomContent.Headers.ContentType = new MediaTypeHeaderValue("application/dicom");
content.Add(dicomContent);
// Send POST request
var response = await httpClient.PostAsync("/studies", content);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine($" Store successful: {response.StatusCode}");
Console.WriteLine($" Response: {result}");
}
else
{
Console.WriteLine($" Store failed: {response.StatusCode}");
}
}
Content Types
DICOMweb uses specific content types:
| Content Type | Purpose |
|---|---|
| application/dicom+json | DICOM metadata as JSON |
| multipart/related; type=“application/dicom” | DICOM objects |
| image/jpeg | Rendered JPEG images |
| image/png | Rendered PNG images |
| application/pdf | Encapsulated PDF documents |
Orthanc DICOMweb Configuration
To use DICOMweb with Orthanc, enable the plugin in your configuration:
{
"Plugins": ["libOrthancDicomWeb.so"],
"DicomWeb": {
"Enable": true,
"Root": "/dicom-web/"
}
}
Default endpoints:
- Orthanc:
http://localhost:8042/dicom-web/ - DCM4CHEE:
http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs/
Best Practices
- Use HTTPS: Always use HTTPS in production for patient data security
- Authentication: Implement OAuth2 or similar for access control
- Pagination: Use limit and offset for large result sets
- Caching: Leverage HTTP caching for metadata and rendered images
- Compression: Request gzip compression for large responses
Advantages of DICOMweb
- Uses standard HTTP/HTTPS protocols
- Works with standard web development tools
- Easier to integrate with web applications
- Firewall-friendly (standard ports)
- Supports modern authentication (OAuth2)
- JSON metadata for easy parsing
Conclusion
DICOMweb provides a modern, RESTful approach to accessing DICOM data. With QIDO-RS for querying, WADO-RS for retrieving, and STOW-RS for storing, you have all the building blocks needed to create web-based medical imaging applications.
While traditional DICOM DIMSE protocols remain important for modality integration and legacy systems, DICOMweb is ideal for building cloud-native applications, web viewers, and mobile solutions that need to access DICOM data over the internet.
Please check out the next tutorial in this series where we cover DICOM Transfer Syntax and compression.