DICOM Basics using .NET and C# - Presentation States (GSPS)
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOM Presentation States, which allow you to store display preferences separately from image data. This enables consistent image display across different workstations and over time.
Presentation States store information like window width/level settings, annotations, shutters, and spatial transformations. When a viewer loads both an image and its presentation state, it can recreate the exact display settings used when the state was created.
Prerequisites
Before you begin, ensure you have the following:
- A .NET development environment (Visual Studio or Visual Studio Code)
- The Fellow Oak DICOM library (fo-dicom) installed via NuGet
- Basic understanding of DICOM concepts from previous tutorials
- You can find all the code demonstrated in this tutorial on GitHub here
“A picture is worth a thousand words, but the right window setting is worth a thousand diagnoses.” ~ Unknown Radiologist
The Theory Behind Presentation States
Medical images, particularly CT and MR, store raw acquisition data with wide dynamic ranges. A CT chest might have Hounsfield units from -1000 (air) to +3000 (dense bone), but a typical display can only show 256 gray levels. The radiologist must select which portion of that range to display - the "window." Different windows reveal different anatomy: lung window shows parenchyma, mediastinal window shows soft tissue, bone window shows skeletal detail. Presentation States separate this viewing intelligence from raw data.
The separation achieves a crucial goal: data integrity. The original image remains untouched while display preferences are stored separately. When a radiologist adjusts window/level and adds measurement annotations, these changes don't modify the acquisition. This means the original data is always available for re-interpretation, and the presentation state documents exactly how the image was viewed during diagnosis.
Graphic annotations present a philosophical challenge: should annotations be "burned in" to pixels or overlaid dynamically? Burned-in annotations permanently modify image data - simple but irreversible. Presentation State annotations overlay at display time, preserving the original. GSPS supports both graphic objects (arrows, circles, lines) and text labels, organized into layers that can be shown or hidden.
The grayscale pipeline in DICOM Part 14 defines the mathematical transformations from stored pixel values to displayed luminance: Modality LUT transformation (using either Rescale Slope/Intercept or a Modality LUT -- these are mutually exclusive alternatives for the same stage), VOI LUT (windowing), and Presentation LUT (display calibration). Presentation States can specify VOI LUT settings that slot into this pipeline, ensuring consistent appearance across different workstations calibrated to the Grayscale Standard Display Function.
For hanging protocols, Presentation States enable workflow automation. A protocol can specify that chest CT studies display with lung window in the main viewport, with a matching GSPS pre-applied. The radiologist sees images consistently prepared without manual adjustment, improving efficiency and reducing variability.
Presentation State Types
DICOM defines several types of Presentation States:
| Type | SOP Class UID | Use Case |
|---|---|---|
| Grayscale Softcopy PS | 1.2.840.10008.5.1.4.1.1.11.1 | Most common; grayscale images |
| Color Softcopy PS | 1.2.840.10008.5.1.4.1.1.11.2 | Color images |
| Pseudo-Color Softcopy PS | 1.2.840.10008.5.1.4.1.1.11.3 | Apply color LUT to grayscale |
| Blending Softcopy PS | 1.2.840.10008.5.1.4.1.1.11.4 | Fusion (PET/CT) |
Key Modules in Presentation States
- Softcopy VOI LUT: Window Width/Level settings
- Graphic Annotation: Text labels and graphic shapes
- Graphic Layer: Layer organization for annotations
- Display Shutter: Hide portions of the image
- Displayed Area: Zoom and pan settings
- Spatial Transformation: Rotation and flip
Step 1 of 3: Creating a Grayscale Presentation State
Here's how to create a GSPS with window settings:
using System;
using System.Diagnostics;
using System.IO;
using FellowOakDicom;
namespace DicomPresentationStates
{
public class Program
{
private static readonly string OutputPath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Output");
public static void Main(string[] args)
{
try
{
LogToDebugConsole("=== DICOM Presentation States Demo ===");
if (!Directory.Exists(OutputPath))
{
Directory.CreateDirectory(OutputPath);
}
CreateGrayscalePresentationState();
}
catch (Exception e)
{
LogToDebugConsole($"Error: {e.Message}");
}
}
private static void CreateGrayscalePresentationState()
{
var dataset = new DicomDataset();
string currentDate = DateTime.Now.ToString("yyyyMMdd");
string currentTime = DateTime.Now.ToString("HHmmss");
// Referenced image UIDs (use actual UIDs from your images)
string referencedStudyUID = "1.2.3.4.5.6.7.8.9";
string referencedSeriesUID = "1.2.3.4.5.6.7.8.9.1";
string referencedSOPInstanceUID = "1.2.3.4.5.6.7.8.9.1.1";
string referencedSOPClassUID = DicomUID.CTImageStorage.UID;
//---------------------------------------------------------------
// SOP Common Module
//---------------------------------------------------------------
dataset.Add(DicomTag.SOPClassUID,
DicomUID.GrayscaleSoftcopyPresentationStateStorage);
dataset.Add(DicomTag.SOPInstanceUID, DicomUID.Generate());
//---------------------------------------------------------------
// Patient Module (must match referenced image)
//---------------------------------------------------------------
dataset.Add(DicomTag.PatientName, "Doe^John");
dataset.Add(DicomTag.PatientID, "PAT123");
dataset.Add(DicomTag.PatientBirthDate, "19700101");
dataset.Add(DicomTag.PatientSex, "M");
//---------------------------------------------------------------
// General Study Module (same as referenced study)
//---------------------------------------------------------------
dataset.Add(DicomTag.StudyInstanceUID, referencedStudyUID);
dataset.Add(DicomTag.StudyDate, currentDate);
dataset.Add(DicomTag.StudyTime, currentTime);
dataset.Add(DicomTag.AccessionNumber, "ACC123");
//---------------------------------------------------------------
// Presentation State Module
//---------------------------------------------------------------
dataset.Add(DicomTag.Modality, "PR"); // Presentation State
dataset.Add(DicomTag.SeriesInstanceUID, DicomUID.Generate());
dataset.Add(DicomTag.SeriesNumber, "100");
dataset.Add(DicomTag.InstanceNumber, "1");
// User-friendly labels
dataset.Add(DicomTag.ContentLabel, "CT_LUNG_WINDOW");
dataset.Add(DicomTag.ContentDescription, "Lung window preset");
dataset.Add(DicomTag.PresentationCreationDate, currentDate);
dataset.Add(DicomTag.PresentationCreationTime, currentTime);
dataset.Add(DicomTag.ContentCreatorName, "Tech^John");
// Add referenced image
AddReferencedImage(dataset, referencedSeriesUID,
referencedSOPClassUID, referencedSOPInstanceUID);
// Add VOI LUT (window settings)
AddVoiLut(dataset, referencedSOPClassUID, referencedSOPInstanceUID,
windowCenter: -600, windowWidth: 1500); // Lung window
// Save the file
string outputFile = Path.Combine(OutputPath, "presentation_state.dcm");
var dicomFile = new DicomFile(dataset);
dicomFile.Save(outputFile);
LogToDebugConsole($"Presentation State created: {outputFile}");
}
private static void LogToDebugConsole(string message)
{
Debug.WriteLine(message);
}
}
}
Step 2 of 3: Adding Referenced Images and VOI LUT
The presentation state must reference the images it applies to:
private static void AddReferencedImage(DicomDataset dataset,
string seriesUID, string sopClassUID, string sopInstanceUID)
{
//---------------------------------------------------------------
// Referenced Series Sequence
//---------------------------------------------------------------
var refSeriesSeq = new DicomSequence(DicomTag.ReferencedSeriesSequence);
var refSeriesItem = new DicomDataset();
refSeriesItem.Add(DicomTag.SeriesInstanceUID, seriesUID);
// Referenced Image Sequence
var refImageSeq = new DicomSequence(DicomTag.ReferencedImageSequence);
var refImageItem = new DicomDataset();
refImageItem.Add(DicomTag.ReferencedSOPClassUID, sopClassUID);
refImageItem.Add(DicomTag.ReferencedSOPInstanceUID, sopInstanceUID);
refImageSeq.Items.Add(refImageItem);
refSeriesItem.Add(refImageSeq);
refSeriesSeq.Items.Add(refSeriesItem);
dataset.Add(refSeriesSeq);
}
private static void AddVoiLut(DicomDataset dataset,
string sopClassUID, string sopInstanceUID,
double windowCenter, double windowWidth)
{
//---------------------------------------------------------------
// Softcopy VOI LUT Module (Window Width/Level)
//---------------------------------------------------------------
var voiLutSeq = new DicomSequence(DicomTag.SoftcopyVOILUTSequence);
var voiLutItem = new DicomDataset();
// Reference the image this applies to
var refImageSeq = new DicomSequence(DicomTag.ReferencedImageSequence);
var refImageItem = new DicomDataset();
refImageItem.Add(DicomTag.ReferencedSOPClassUID, sopClassUID);
refImageItem.Add(DicomTag.ReferencedSOPInstanceUID, sopInstanceUID);
refImageSeq.Items.Add(refImageItem);
voiLutItem.Add(refImageSeq);
// Window settings
voiLutItem.Add(DicomTag.WindowCenter, windowCenter.ToString());
voiLutItem.Add(DicomTag.WindowWidth, windowWidth.ToString());
voiLutItem.Add(DicomTag.VOILUTFunction, "LINEAR");
voiLutSeq.Items.Add(voiLutItem);
dataset.Add(voiLutSeq);
}
Step 3 of 3: Adding Graphic Annotations
Presentation states can also include text and graphic annotations:
private static void AddGraphicLayer(DicomDataset dataset)
{
//---------------------------------------------------------------
// Graphic Layer Module
//---------------------------------------------------------------
var graphicLayerSeq = new DicomSequence(DicomTag.GraphicLayerSequence);
var layerItem = new DicomDataset();
layerItem.Add(DicomTag.GraphicLayer, "FINDINGS");
layerItem.Add(DicomTag.GraphicLayerOrder, "1");
layerItem.Add(DicomTag.GraphicLayerDescription, "Radiologist findings");
graphicLayerSeq.Items.Add(layerItem);
dataset.Add(graphicLayerSeq);
}
private static void AddTextAnnotation(DicomDataset dataset,
string sopClassUID, string sopInstanceUID,
string text, double[] boundingBox)
{
//---------------------------------------------------------------
// Graphic Annotation Module
//---------------------------------------------------------------
var annotationSeq = new DicomSequence(DicomTag.GraphicAnnotationSequence);
var annotationItem = new DicomDataset();
// Reference the image
var refImageSeq = new DicomSequence(DicomTag.ReferencedImageSequence);
var refImageItem = new DicomDataset();
refImageItem.Add(DicomTag.ReferencedSOPClassUID, sopClassUID);
refImageItem.Add(DicomTag.ReferencedSOPInstanceUID, sopInstanceUID);
refImageSeq.Items.Add(refImageItem);
annotationItem.Add(refImageSeq);
// Layer assignment
annotationItem.Add(DicomTag.GraphicLayer, "FINDINGS");
// Text Object Sequence
var textSeq = new DicomSequence(DicomTag.TextObjectSequence);
var textItem = new DicomDataset();
textItem.Add(DicomTag.BoundingBoxAnnotationUnits, "PIXEL");
textItem.Add(DicomTag.UnformattedTextValue, text);
textItem.Add(DicomTag.BoundingBoxTopLeftHandCorner, boundingBox[0], boundingBox[1]);
textItem.Add(DicomTag.BoundingBoxBottomRightHandCorner, boundingBox[2], boundingBox[3]);
textSeq.Items.Add(textItem);
annotationItem.Add(textSeq);
annotationSeq.Items.Add(annotationItem);
dataset.Add(annotationSeq);
}
Common CT Window Presets
Here are standard window presets for CT imaging:
| Preset | Window Width | Window Center | Use Case |
|---|---|---|---|
| Lung | 1500 | -600 | Pulmonary parenchyma |
| Mediastinum | 350 | 50 | Mediastinal structures |
| Soft Tissue | 400 | 40 | General soft tissue |
| Bone | 2000 | 300 | Skeletal structures |
| Brain | 80 | 40 | Brain parenchyma |
| Subdural | 200 | 75 | Subdural hematoma |
| Stroke | 40 | 40 | Acute ischemia |
| Liver | 150 | 30 | Hepatic lesions |
| Abdomen | 400 | 50 | Abdominal organs |
Reading Presentation States
To read and apply a presentation state:
public static void ReadPresentationState(string psFilePath)
{
var file = DicomFile.Open(psFilePath);
var dataset = file.Dataset;
// Verify it's a Presentation State
var sopClass = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, "");
LogToDebugConsole($"SOP Class: {sopClass}");
LogToDebugConsole($"Modality: {dataset.GetSingleValueOrDefault(DicomTag.Modality, "")}");
LogToDebugConsole($"Label: {dataset.GetSingleValueOrDefault(DicomTag.ContentLabel, "")}");
// Read VOI LUT settings
var voiLutSeq = dataset.GetSequence(DicomTag.SoftcopyVOILUTSequence);
if (voiLutSeq != null && voiLutSeq.Items.Count > 0)
{
var voiItem = voiLutSeq.Items[0];
var windowCenter = voiItem.GetSingleValueOrDefault(DicomTag.WindowCenter, "");
var windowWidth = voiItem.GetSingleValueOrDefault(DicomTag.WindowWidth, "");
LogToDebugConsole($"Window Center: {windowCenter}");
LogToDebugConsole($"Window Width: {windowWidth}");
}
}
Best Practices
- Match patient data: PS must match the referenced image's patient info
- Use meaningful labels: Content Label should identify the preset clearly
- Document creator: Include who created the presentation state
- One PS per purpose: Create separate states for different presets
- Test compatibility: Verify your viewer supports the PS features used
Conclusion
Presentation States provide a powerful way to capture and share display settings separately from image data. This enables workflow improvements such as hanging protocols that automatically apply the right window settings, annotations that persist across viewing sessions, and standardized display presets across institutions.
Understanding Presentation States is essential for building diagnostic viewers and PACS integration. The most commonly used type is Grayscale Softcopy Presentation State (GSPS), which handles window/level settings and annotations for grayscale images like CT and MR.
Please check out the next tutorial in this series where we cover DICOM Key Object Selection documents.