Advanced DICOM features enable sophisticated clinical workflows beyond basic image storage.
Structured Reporting (SR)
Structured Reporting provides machine-readable encoding of radiological findings.
SR Document Structure
Creating a Structured Report
Creating an SR document involves building a tree of content items with proper relationship types and concept codes. Each measurement or finding references coded terminology for interoperability. The following implementation creates a measurement report with findings linked to source images.
public class StructuredReportCreator
{
public DicomDataSet CreateMeasurementReport(
string patientName,
string patientID,
string studyInstanceUID,
List<Measurement> measurements)
{
DicomDataSet sr = new DicomDataSet();
// SOP Common Module
sr.Add(DicomTag.SOPClassUID, SOPClasses.EnhancedSR);
sr.Add(DicomTag.SOPInstanceUID, DicomUID.Generate());
sr.Add(DicomTag.Modality, "SR");
// Patient Module
sr.Add(DicomTag.PatientName, patientName);
sr.Add(DicomTag.PatientID, patientID);
// Study Module
sr.Add(DicomTag.StudyInstanceUID, studyInstanceUID);
sr.Add(DicomTag.StudyDate, DateTime.Now.ToString("yyyyMMdd"));
sr.Add(DicomTag.StudyTime, DateTime.Now.ToString("HHmmss"));
// Series Module
sr.Add(DicomTag.SeriesInstanceUID, DicomUID.Generate());
sr.Add(DicomTag.SeriesNumber, "1");
// SR Document Content Module
sr.Add(DicomTag.ValueType, "CONTAINER");
sr.Add(DicomTag.ContinuityOfContent, "SEPARATE");
// Content Sequence - the SR tree
DicomSequence contentSeq = BuildContentTree(measurements);
sr.Add(contentSeq);
return sr;
}
private DicomSequence BuildContentTree(List<Measurement> measurements)
{
DicomSequence contentSeq = new DicomSequence(DicomTag.ContentSequence);
// Document Title
DicomDataSet titleItem = new DicomDataSet();
titleItem.Add(DicomTag.RelationshipType, "CONTAINS");
titleItem.Add(DicomTag.ValueType, "CODE");
AddConceptNameCode(titleItem, "126000", "DCM", "Imaging Report");
contentSeq.Items.Add(titleItem);
// Findings Container
DicomDataSet findingsContainer = new DicomDataSet();
findingsContainer.Add(DicomTag.RelationshipType, "CONTAINS");
findingsContainer.Add(DicomTag.ValueType, "CONTAINER");
AddConceptNameCode(findingsContainer, "121070", "DCM", "Findings");
// Nested content for findings
DicomSequence findingsContent = new DicomSequence(DicomTag.ContentSequence);
foreach (var measurement in measurements)
{
DicomDataSet measurementItem = CreateMeasurementItem(measurement);
findingsContent.Items.Add(measurementItem);
}
findingsContainer.Add(findingsContent);
contentSeq.Items.Add(findingsContainer);
return contentSeq;
}
private DicomDataSet CreateMeasurementItem(Measurement measurement)
{
DicomDataSet item = new DicomDataSet();
item.Add(DicomTag.RelationshipType, "CONTAINS");
item.Add(DicomTag.ValueType, "NUM");
// Concept Name (what is being measured)
AddConceptNameCode(item, measurement.ConceptCode,
measurement.ConceptScheme, measurement.ConceptMeaning);
// Measured Value Sequence
DicomSequence measuredValueSeq = new DicomSequence(DicomTag.MeasuredValueSequence);
DicomDataSet valueItem = new DicomDataSet();
valueItem.Add(DicomTag.NumericValue, measurement.Value.ToString());
// Units
DicomSequence unitsSeq = new DicomSequence(DicomTag.MeasurementUnitsCodeSequence);
DicomDataSet unitsItem = new DicomDataSet();
unitsItem.Add(DicomTag.CodeValue, measurement.UnitsCode);
unitsItem.Add(DicomTag.CodingSchemeDesignator, "UCUM");
unitsItem.Add(DicomTag.CodeMeaning, measurement.UnitsMeaning);
unitsSeq.Items.Add(unitsItem);
valueItem.Add(unitsSeq);
measuredValueSeq.Items.Add(valueItem);
item.Add(measuredValueSeq);
// Reference to image where measurement was made
if (!string.IsNullOrEmpty(measurement.ReferencedSOPInstanceUID))
{
DicomSequence refImageSeq = new DicomSequence(DicomTag.ReferencedSOPSequence);
DicomDataSet refItem = new DicomDataSet();
refItem.Add(DicomTag.ReferencedSOPClassUID, measurement.ReferencedSOPClassUID);
refItem.Add(DicomTag.ReferencedSOPInstanceUID, measurement.ReferencedSOPInstanceUID);
refImageSeq.Items.Add(refItem);
item.Add(refImageSeq);
}
return item;
}
private void AddConceptNameCode(DicomDataSet ds,
string code, string scheme, string meaning)
{
DicomSequence conceptSeq = new DicomSequence(DicomTag.ConceptNameCodeSequence);
DicomDataSet codeItem = new DicomDataSet();
codeItem.Add(DicomTag.CodeValue, code);
codeItem.Add(DicomTag.CodingSchemeDesignator, scheme);
codeItem.Add(DicomTag.CodeMeaning, meaning);
conceptSeq.Items.Add(codeItem);
ds.Add(conceptSeq);
}
}
public class Measurement
{
public string ConceptCode { get; set; } // e.g., "118565006" for Volume
public string ConceptScheme { get; set; } // e.g., "SCT" for SNOMED CT
public string ConceptMeaning { get; set; } // e.g., "Volume"
public double Value { get; set; } // e.g., 25.4
public string UnitsCode { get; set; } // e.g., "mm3"
public string UnitsMeaning { get; set; } // e.g., "cubic millimeter"
public string ReferencedSOPClassUID { get; set; }
public string ReferencedSOPInstanceUID { get; set; }
}
Common SR Code Systems
Presentation States (GSPS)
Grayscale Softcopy Presentation State stores how images should be displayed.
GSPS Components
Creating a Presentation State
This implementation creates a GSPS object that stores window/level settings and graphic annotations. The presentation state references specific images and can be stored separately, enabling display settings to be shared across viewers without modifying the original images.
public class PresentationStateCreator
{
public DicomDataSet CreateGSPS(
DicomImage sourceImage,
double windowCenter,
double windowWidth,
List<Annotation> annotations)
{
DicomDataSet gsps = new DicomDataSet();
// SOP Common Module
gsps.Add(DicomTag.SOPClassUID, SOPClasses.GrayscaleSoftcopyPresentationState);
gsps.Add(DicomTag.SOPInstanceUID, DicomUID.Generate());
// Patient Module (copy from source)
gsps.Add(DicomTag.PatientName, sourceImage.Attributes[DicomTag.PatientName].Value);
gsps.Add(DicomTag.PatientID, sourceImage.Attributes[DicomTag.PatientID].Value);
// Study Module (copy from source)
gsps.Add(DicomTag.StudyInstanceUID,
sourceImage.Attributes[DicomTag.StudyInstanceUID].Value);
// Series Module (new series for presentation state)
gsps.Add(DicomTag.SeriesInstanceUID, DicomUID.Generate());
gsps.Add(DicomTag.Modality, "PR"); // Presentation State
// Presentation State Module
gsps.Add(DicomTag.PresentationCreationDate, DateTime.Now.ToString("yyyyMMdd"));
gsps.Add(DicomTag.PresentationCreationTime, DateTime.Now.ToString("HHmmss"));
gsps.Add(DicomTag.ContentLabel, "OPTIMAL_WL");
gsps.Add(DicomTag.ContentDescription, "Optimized window/level settings");
gsps.Add(DicomTag.PresentationLUTShape, "IDENTITY");
// Referenced Series Sequence
AddReferencedImages(gsps, sourceImage);
// Softcopy VOI LUT Sequence (Window/Level)
AddWindowLevelSettings(gsps, windowCenter, windowWidth, sourceImage);
// Graphic Annotation Sequence
if (annotations != null && annotations.Count > 0)
{
AddAnnotations(gsps, annotations, sourceImage);
}
return gsps;
}
private void AddReferencedImages(DicomDataSet gsps, DicomImage sourceImage)
{
DicomSequence refSeriesSeq = new DicomSequence(DicomTag.ReferencedSeriesSequence);
DicomDataSet seriesItem = new DicomDataSet();
seriesItem.Add(DicomTag.SeriesInstanceUID,
sourceImage.Attributes[DicomTag.SeriesInstanceUID].Value);
DicomSequence refImageSeq = new DicomSequence(DicomTag.ReferencedImageSequence);
DicomDataSet imageItem = new DicomDataSet();
imageItem.Add(DicomTag.ReferencedSOPClassUID,
sourceImage.Attributes[DicomTag.SOPClassUID].Value);
imageItem.Add(DicomTag.ReferencedSOPInstanceUID,
sourceImage.Attributes[DicomTag.SOPInstanceUID].Value);
refImageSeq.Items.Add(imageItem);
seriesItem.Add(refImageSeq);
refSeriesSeq.Items.Add(seriesItem);
gsps.Add(refSeriesSeq);
}
private void AddWindowLevelSettings(
DicomDataSet gsps,
double windowCenter,
double windowWidth,
DicomImage sourceImage)
{
DicomSequence voiSeq = new DicomSequence(DicomTag.SoftcopyVOILUTSequence);
DicomDataSet voiItem = new DicomDataSet();
voiItem.Add(DicomTag.WindowCenter, windowCenter.ToString());
voiItem.Add(DicomTag.WindowWidth, windowWidth.ToString());
voiItem.Add(DicomTag.WindowCenterWidthExplanation, "Optimized");
// Reference to which images this applies
DicomSequence refImageSeq = new DicomSequence(DicomTag.ReferencedImageSequence);
DicomDataSet refItem = new DicomDataSet();
refItem.Add(DicomTag.ReferencedSOPClassUID,
sourceImage.Attributes[DicomTag.SOPClassUID].Value);
refItem.Add(DicomTag.ReferencedSOPInstanceUID,
sourceImage.Attributes[DicomTag.SOPInstanceUID].Value);
refImageSeq.Items.Add(refItem);
voiItem.Add(refImageSeq);
voiSeq.Items.Add(voiItem);
gsps.Add(voiSeq);
}
private void AddAnnotations(
DicomDataSet gsps,
List<Annotation> annotations,
DicomImage sourceImage)
{
DicomSequence graphicSeq = new DicomSequence(DicomTag.GraphicAnnotationSequence);
DicomDataSet annotationItem = new DicomDataSet();
// Reference to which images annotations apply
DicomSequence refImageSeq = new DicomSequence(DicomTag.ReferencedImageSequence);
DicomDataSet refItem = new DicomDataSet();
refItem.Add(DicomTag.ReferencedSOPClassUID,
sourceImage.Attributes[DicomTag.SOPClassUID].Value);
refItem.Add(DicomTag.ReferencedSOPInstanceUID,
sourceImage.Attributes[DicomTag.SOPInstanceUID].Value);
refImageSeq.Items.Add(refItem);
annotationItem.Add(refImageSeq);
// Graphic Layer
annotationItem.Add(DicomTag.GraphicLayer, "ANNOTATIONS");
// Add text annotations
var textAnnotations = annotations.Where(a => a.Type == AnnotationType.Text);
if (textAnnotations.Any())
{
DicomSequence textSeq = new DicomSequence(DicomTag.TextObjectSequence);
foreach (var text in textAnnotations)
{
DicomDataSet textItem = new DicomDataSet();
textItem.Add(DicomTag.UnformattedTextValue, text.Text);
textItem.Add(DicomTag.BoundingBoxAnnotationUnits, "PIXEL");
textItem.Add(DicomTag.BoundingBoxTopLeftHandCorner,
$"{text.TopLeft.X}\\{text.TopLeft.Y}");
textItem.Add(DicomTag.BoundingBoxBottomRightHandCorner,
$"{text.BottomRight.X}\\{text.BottomRight.Y}");
textSeq.Items.Add(textItem);
}
annotationItem.Add(textSeq);
}
// Add graphic annotations (lines, arrows, etc.)
var graphicAnnotations = annotations.Where(a => a.Type != AnnotationType.Text);
if (graphicAnnotations.Any())
{
DicomSequence graphObjSeq = new DicomSequence(DicomTag.GraphicObjectSequence);
foreach (var graphic in graphicAnnotations)
{
DicomDataSet graphItem = new DicomDataSet();
graphItem.Add(DicomTag.GraphicAnnotationUnits, "PIXEL");
graphItem.Add(DicomTag.GraphicType, GetGraphicType(graphic.Type));
graphItem.Add(DicomTag.NumberOfGraphicPoints, graphic.Points.Count.ToString());
graphItem.Add(DicomTag.GraphicData,
string.Join("\\", graphic.Points.Select(p => $"{p.X}\\{p.Y}")));
graphItem.Add(DicomTag.GraphicFilled, "N");
graphObjSeq.Items.Add(graphItem);
}
annotationItem.Add(graphObjSeq);
}
graphicSeq.Items.Add(annotationItem);
gsps.Add(graphicSeq);
// Graphic Layer Sequence
DicomSequence layerSeq = new DicomSequence(DicomTag.GraphicLayerSequence);
DicomDataSet layerItem = new DicomDataSet();
layerItem.Add(DicomTag.GraphicLayer, "ANNOTATIONS");
layerItem.Add(DicomTag.GraphicLayerOrder, "1");
layerItem.Add(DicomTag.GraphicLayerDescription, "Radiologist annotations");
layerSeq.Items.Add(layerItem);
gsps.Add(layerSeq);
}
private string GetGraphicType(AnnotationType type)
{
return type switch
{
AnnotationType.Point => "POINT",
AnnotationType.Line => "POLYLINE",
AnnotationType.Circle => "CIRCLE",
AnnotationType.Ellipse => "ELLIPSE",
AnnotationType.Rectangle => "POLYLINE",
AnnotationType.Arrow => "POLYLINE",
_ => "POLYLINE"
};
}
}
public class Annotation
{
public AnnotationType Type { get; set; }
public string Text { get; set; }
public Point TopLeft { get; set; }
public Point BottomRight { get; set; }
public List<Point> Points { get; set; } = new List<Point>();
}
public enum AnnotationType
{
Text,
Point,
Line,
Circle,
Ellipse,
Rectangle,
Arrow
}
public class Point
{
public float X { get; set; }
public float Y { get; set; }
}
Summary
Advanced DICOM features enable:
- Structured Reporting: Machine-readable findings with standardized codes
- Presentation States: Consistent display settings across viewers
- Interoperability: Data exchange with coded terminology
- Workflow Integration: Automated processing of findings and measurements
These capabilities are essential for modern radiology workflows and clinical decision support systems.