DICOMweb provides RESTful APIs for DICOM operations, enabling web-based medical imaging applications.
DICOMweb Overview
QIDO-RS (Query)
QIDO-RS queries for studies, series, or instances using HTTP GET.
Query Studies
The following QIDO client demonstrates querying for studies using standard HTTP requests. The query parameters map to DICOM attributes, and results are returned in DICOM JSON format. Pagination support enables handling large result sets.
public class QidoClient
{
private readonly HttpClient httpClient;
private readonly string baseUrl;
public QidoClient(string baseUrl)
{
this.baseUrl = baseUrl.TrimEnd('/');
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/dicom+json"));
}
public async Task<List<StudyResult>> QueryStudies(
string patientName = null,
string patientID = null,
string studyDate = null,
string modality = null,
int limit = 100,
int offset = 0)
{
// Build query URL
var queryParams = new List<string>();
if (!string.IsNullOrEmpty(patientName))
queryParams.Add($"PatientName={Uri.EscapeDataString(patientName)}");
if (!string.IsNullOrEmpty(patientID))
queryParams.Add($"PatientID={Uri.EscapeDataString(patientID)}");
if (!string.IsNullOrEmpty(studyDate))
queryParams.Add($"StudyDate={studyDate}");
if (!string.IsNullOrEmpty(modality))
queryParams.Add($"ModalitiesInStudy={modality}");
queryParams.Add($"limit={limit}");
queryParams.Add($"offset={offset}");
// Include return fields
queryParams.Add("includefield=StudyInstanceUID");
queryParams.Add("includefield=PatientName");
queryParams.Add("includefield=PatientID");
queryParams.Add("includefield=StudyDate");
queryParams.Add("includefield=StudyDescription");
queryParams.Add("includefield=ModalitiesInStudy");
queryParams.Add("includefield=NumberOfStudyRelatedSeries");
queryParams.Add("includefield=NumberOfStudyRelatedInstances");
string url = $"{baseUrl}/studies?{string.Join("&", queryParams)}";
HttpResponseMessage response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
return ParseStudyResults(json);
}
private List<StudyResult> ParseStudyResults(string json)
{
var results = new List<StudyResult>();
// Parse DICOM JSON format
var jsonArray = JsonConvert.DeserializeObject<JArray>(json);
foreach (JObject item in jsonArray)
{
results.Add(new StudyResult
{
StudyInstanceUID = GetDicomValue(item, "0020000D"),
PatientName = GetDicomValue(item, "00100010"),
PatientID = GetDicomValue(item, "00100020"),
StudyDate = GetDicomValue(item, "00080020"),
StudyDescription = GetDicomValue(item, "00081030"),
Modality = GetDicomValue(item, "00080061"),
NumberOfSeries = GetDicomValue(item, "00201206"),
NumberOfInstances = GetDicomValue(item, "00201208")
});
}
return results;
}
private string GetDicomValue(JObject item, string tag)
{
var element = item[tag];
if (element == null) return "";
var value = element["Value"];
if (value == null || !value.HasValues) return "";
return value[0]?.ToString() ?? "";
}
}
public class StudyResult
{
public string StudyInstanceUID { get; set; }
public string PatientName { get; set; }
public string PatientID { get; set; }
public string StudyDate { get; set; }
public string StudyDescription { get; set; }
public string Modality { get; set; }
public string NumberOfSeries { get; set; }
public string NumberOfInstances { get; set; }
}
Query Series and Instances
Hierarchical queries drill down from studies to series to instances. Each level uses the parent’s UID to narrow the scope. These endpoints follow RESTful URL patterns with UIDs in the path.
public async Task<List<SeriesResult>> QuerySeries(string studyInstanceUID)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}/series";
HttpResponseMessage response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
// Parse and return series results
return ParseSeriesResults(json);
}
public async Task<List<InstanceResult>> QueryInstances(
string studyInstanceUID,
string seriesInstanceUID)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}/series/{seriesInstanceUID}/instances";
HttpResponseMessage response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
// Parse and return instance results
return ParseInstanceResults(json);
}
WADO-RS (Retrieve)
WADO-RS retrieves DICOM objects, metadata, or rendered images.
Retrieve Studies/Series/Instances
WADO-RS supports multiple retrieval modes: raw DICOM, metadata-only, rendered images, and thumbnails. Use the Accept header to specify desired format. Rendered image retrieval enables direct web display without client-side DICOM decoding.
public class WadoClient
{
private readonly HttpClient httpClient;
private readonly string baseUrl;
public WadoClient(string baseUrl)
{
this.baseUrl = baseUrl.TrimEnd('/');
httpClient = new HttpClient();
}
// Retrieve entire study as DICOM
public async Task<List<byte[]>> RetrieveStudy(string studyInstanceUID)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(
"multipart/related",
new NameValueHeaderValue("type", "\"application/dicom\"")));
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await ParseMultipartDicom(response);
}
// Retrieve single instance as DICOM
public async Task<byte[]> RetrieveInstance(
string studyInstanceUID,
string seriesInstanceUID,
string sopInstanceUID)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}" +
$"/series/{seriesInstanceUID}/instances/{sopInstanceUID}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/dicom"));
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
// Retrieve rendered image (PNG/JPEG)
public async Task<byte[]> RetrieveRenderedImage(
string studyInstanceUID,
string seriesInstanceUID,
string sopInstanceUID,
int? frame = null,
string quality = null)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}" +
$"/series/{seriesInstanceUID}/instances/{sopInstanceUID}/rendered";
var queryParams = new List<string>();
if (frame.HasValue)
queryParams.Add($"frame={frame.Value}");
if (!string.IsNullOrEmpty(quality))
queryParams.Add($"quality={quality}");
if (queryParams.Any())
url += "?" + string.Join("&", queryParams);
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("image/png"));
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
// Retrieve thumbnail
public async Task<byte[]> RetrieveThumbnail(
string studyInstanceUID,
string seriesInstanceUID,
string sopInstanceUID)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}" +
$"/series/{seriesInstanceUID}/instances/{sopInstanceUID}/thumbnail";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("image/jpeg"));
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
// Retrieve metadata only (no pixel data)
public async Task<string> RetrieveMetadata(
string studyInstanceUID,
string seriesInstanceUID,
string sopInstanceUID)
{
string url = $"{baseUrl}/studies/{studyInstanceUID}" +
$"/series/{seriesInstanceUID}/instances/{sopInstanceUID}/metadata";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/dicom+json"));
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
private async Task<List<byte[]>> ParseMultipartDicom(HttpResponseMessage response)
{
var results = new List<byte[]>();
var content = response.Content;
var multipartContent = await content.ReadAsMultipartAsync();
foreach (var part in multipartContent.Contents)
{
byte[] dicomBytes = await part.ReadAsByteArrayAsync();
results.Add(dicomBytes);
}
return results;
}
}
STOW-RS (Store)
STOW-RS stores DICOM objects using HTTP POST.
Store DICOM Objects
STOW-RS uploads DICOM objects using HTTP POST with multipart content. Multiple instances can be uploaded in a single request for efficiency. The server responds with a JSON document indicating success or failure for each instance.
public class StowClient
{
private readonly HttpClient httpClient;
private readonly string baseUrl;
public StowClient(string baseUrl)
{
this.baseUrl = baseUrl.TrimEnd('/');
httpClient = new HttpClient();
}
public async Task<StowResponse> StoreInstances(List<string> dicomFilePaths)
{
string url = $"{baseUrl}/studies";
// Create multipart content
var multipartContent = new MultipartContent("related",
$"----Boundary{Guid.NewGuid():N}");
multipartContent.Headers.ContentType.Parameters.Add(
new NameValueHeaderValue("type", "\"application/dicom\""));
foreach (string filePath in dicomFilePaths)
{
byte[] dicomBytes = File.ReadAllBytes(filePath);
var dicomContent = new ByteArrayContent(dicomBytes);
dicomContent.Headers.ContentType =
new MediaTypeHeaderValue("application/dicom");
multipartContent.Add(dicomContent);
}
HttpResponseMessage response = await httpClient.PostAsync(url, multipartContent);
string responseJson = await response.Content.ReadAsStringAsync();
return new StowResponse
{
Success = response.IsSuccessStatusCode,
StatusCode = (int)response.StatusCode,
ResponseBody = responseJson
};
}
public async Task<StowResponse> StoreInstance(byte[] dicomBytes)
{
string url = $"{baseUrl}/studies";
var content = new ByteArrayContent(dicomBytes);
content.Headers.ContentType = new MediaTypeHeaderValue("application/dicom");
HttpResponseMessage response = await httpClient.PostAsync(url, content);
string responseJson = await response.Content.ReadAsStringAsync();
return new StowResponse
{
Success = response.IsSuccessStatusCode,
StatusCode = (int)response.StatusCode,
ResponseBody = responseJson
};
}
}
public class StowResponse
{
public bool Success { get; set; }
public int StatusCode { get; set; }
public string ResponseBody { get; set; }
}
DICOMweb URL Patterns
DICOMweb URL Structure
======================
Base URL: https://dicom.example.com/dicomweb
QIDO-RS (Query):
GET /studies?PatientName=Doe*&StudyDate=20240115
GET /studies/{studyUID}/series
GET /studies/{studyUID}/series/{seriesUID}/instances
WADO-RS (Retrieve):
GET /studies/{studyUID} → All DICOM
GET /studies/{studyUID}/series/{seriesUID} → Series DICOM
GET /studies/{studyUID}/.../instances/{instanceUID} → Instance
GET /studies/{studyUID}/.../instances/{uid}/metadata → JSON metadata
GET /studies/{studyUID}/.../instances/{uid}/rendered → PNG/JPEG
GET /studies/{studyUID}/.../instances/{uid}/frames/1 → Specific frame
STOW-RS (Store):
POST /studies → Store to any study
POST /studies/{studyUID} → Store to specific study
Usage Examples
These examples demonstrate typical DICOMweb workflows including searching for studies, retrieving rendered images for web display, and uploading new DICOM files to a server.
// QIDO-RS: Find studies
var qidoClient = new QidoClient("https://dicom.example.com/dicomweb");
var studies = await qidoClient.QueryStudies(
patientName: "Doe*",
studyDate: "20240101-20240131",
modality: "CT"
);
Console.WriteLine($"Found {studies.Count} studies");
foreach (var study in studies)
{
Console.WriteLine($" {study.PatientName} - {study.StudyDate} - {study.StudyDescription}");
}
// WADO-RS: Retrieve rendered image for web display
var wadoClient = new WadoClient("https://dicom.example.com/dicomweb");
byte[] imageBytes = await wadoClient.RetrieveRenderedImage(
studies[0].StudyInstanceUID,
seriesUID,
instanceUID
);
File.WriteAllBytes("image.png", imageBytes);
// STOW-RS: Upload DICOM files
var stowClient = new StowClient("https://dicom.example.com/dicomweb");
var storeResult = await stowClient.StoreInstances(
new List<string> { "image1.dcm", "image2.dcm" }
);
Console.WriteLine($"Storage {(storeResult.Success ? "succeeded" : "failed")}");
Benefits of DICOMweb
| Aspect | Traditional DICOM | DICOMweb |
|---|---|---|
| Protocol | Custom TCP | HTTP/HTTPS |
| Firewall | Port 104 needed | Standard web ports |
| Load Balancing | Complex | Standard web LB |
| Authentication | AE Title | OAuth, JWT, SAML |
| Caching | Custom | HTTP caching |
| Web Integration | Complex | Native REST |
DICOMweb enables modern web-based medical imaging applications with standard web technologies.