DICOM Basics using Java - Understanding DICOM Structured Reports
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOM Structured Reports (SR), which provide a standardized way to encode clinical findings, measurements, and observations. Unlike free-text reports, Structured Reports use a tree structure with coded concepts that enable computer processing and data mining.
Structured Reports are increasingly important in modern radiology workflows, especially with AI-assisted diagnosis, quantitative imaging, and clinical decision support systems.
Prerequisites
Before you begin, ensure you have the following:
- Java JDK installed (Java 8 or later)
- PixelMed Java DICOM Toolkit
- Output directory (C:\temp) for saving generated files
- You can find all the code demonstrated in this tutorial on GitHub here
“The greatest glory in living lies not in never falling, but in rising every time we fall.” ~ Nelson Mandela
The Theory Behind Structured Reporting
Structured Reports represent a fundamental shift from human-readable documentation to machine-processable clinical knowledge. This transformation enables what informatics researchers call "computable clinical content" - findings that can be automatically aggregated, analyzed, and integrated into decision support systems.
The Semantic Interoperability Problem
Traditional radiology reports are prose documents optimized for human readers. While effective for communication between physicians, they present significant challenges for computational use:
- Ambiguity: "The lesion appears slightly larger" - larger than what? By how much?
- Inconsistent Terminology: "mass," "nodule," "lesion," "growth" may mean the same thing
- Implicit Context: "Normal" depends on patient age, clinical history, and comparison studies
- Narrative Structure: Natural language processing struggles with complex medical prose
Structured Reports solve these problems by encoding clinical content as directed acyclic graphs (DAGs) of coded concepts with explicit relationships and quantitative values.
The SR Content Tree Model
An SR document is fundamentally a tree structure where:
- Nodes are Content Items (containers, codes, text, numbers, images, etc.)
- Edges are Relationships (CONTAINS, HAS OBS CONTEXT, INFERRED FROM, etc.)
- Leaves are terminal values (measurements, coded findings, text descriptions)
This tree structure maps naturally to clinical reasoning: a finding CONTAINS sub-findings, which are INFERRED FROM specific images, observed by a particular physician (HAS OBS CONTEXT).
The Role of Coding Systems
Structured Reports derive their power from standardized coding systems that provide unambiguous concept identification:
- SNOMED CT: ~350,000 clinical concepts covering anatomy, findings, procedures
- LOINC: Laboratory and clinical observations, document types
- RadLex: Radiology-specific lexicon with ~75,000 terms
- UCUM: Unified Code for Units of Measure (ensuring "5 cm" is unambiguous)
When a report encodes a finding as RadLex code "RID4919" rather than the text "hepatocellular carcinoma," it becomes globally unambiguous, translatable, and computationally processable.
AI/ML Integration
Structured Reports are increasingly important as the output format for AI/ML algorithms in radiology. When a deep learning model detects a pulmonary nodule, encoding its findings as a TID 1500 Measurement Report ensures that:
- The finding can be displayed by any SR-capable viewer
- Measurements can be automatically tracked longitudinally
- Decision support systems can process the AI output
- The finding links back to the specific image and coordinates
Understanding Structured Reports
DICOM Structured Reports differ from traditional text reports in several important ways:
| Aspect | Text Report | Structured Report |
|---|---|---|
| Format | Free text | Hierarchical tree |
| Encoding | Plain text | Coded concepts |
| Machine readable | Limited | Full support |
| Standards | Varies | DICOM, SNOMED, LOINC |
| Data mining | Difficult | Straightforward |
SR Template Types
DICOM defines several SR SOP Classes for different use cases:
| SOP Class | UID | Use Case |
|---|---|---|
| Basic Text SR | 1.2.840.10008.5.1.4.1.1.88.11 | Simple text reports |
| Enhanced SR | 1.2.840.10008.5.1.4.1.1.88.22 | Numeric measurements |
| Comprehensive SR | 1.2.840.10008.5.1.4.1.1.88.33 | Full functionality |
| Comprehensive 3D SR | 1.2.840.10008.5.1.4.1.1.88.34 | With 3D coordinates |
SR Content Structure
Structured Reports use Content Items as building blocks. Each item contains:
- Value Type: CONTAINER, TEXT, CODE, NUM, DATE, TIME, PNAME, UIDREF, IMAGE
- Concept Name: Coded entry describing what the item represents
- Value: The actual content (text, code, number, etc.)
- Relationship: How this item relates to its parent (CONTAINS, HAS OBS CONTEXT, etc.)
Step 1: Setting Up the Demo
Here's how to create a Structured Report using PixelMed:
package com.saravanansubramanian.dicom.pixelmedtutorial;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.pixelmed.dicom.*;
public class CreateStructuredReportDemo {
public static void main(String[] args) {
try {
System.out.println("=== DICOM Structured Report Creation Demo ===\n");
// Output file path
String outputFilePath = "C:\\temp\\sample_sr.dcm";
// Create the SR
AttributeList srList = createChestXRayReport();
// Add file meta information
FileMetaInformation.addFileMetaInformation(
srList,
TransferSyntax.ExplicitVRLittleEndian,
"OurSourceAET"
);
// Write to file
srList.write(outputFilePath);
System.out.println("Structured Report created: " + outputFilePath);
System.out.println("\n--- SR Contents ---");
System.out.println(srList.toString());
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
Step 2: Building Patient and Study Information
Like any DICOM object, Structured Reports require patient and study identification:
private static AttributeList createChestXRayReport() throws Exception {
AttributeList list = new AttributeList();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
SimpleDateFormat timeFormat = new SimpleDateFormat("HHmmss");
Date now = new Date();
String currentDate = dateFormat.format(now);
String currentTime = timeFormat.format(now);
// === SOP Common Module ===
Attribute sopClassUID = new UniqueIdentifierAttribute(TagFromName.SOPClassUID);
sopClassUID.addValue(SOPClass.BasicTextSRStorage);
list.put(sopClassUID);
Attribute sopInstanceUID = new UniqueIdentifierAttribute(TagFromName.SOPInstanceUID);
sopInstanceUID.addValue(UniqueIdentifierAttribute.createUID());
list.put(sopInstanceUID);
// === Patient Module ===
Attribute patientName = new PersonNameAttribute(TagFromName.PatientName);
patientName.addValue("Doe^John^A");
list.put(patientName);
Attribute patientID = new LongStringAttribute(TagFromName.PatientID);
patientID.addValue("PAT123456");
list.put(patientID);
Attribute patientBirthDate = new DateAttribute(TagFromName.PatientBirthDate);
patientBirthDate.addValue("19650315");
list.put(patientBirthDate);
Attribute patientSex = new CodeStringAttribute(TagFromName.PatientSex);
patientSex.addValue("M");
list.put(patientSex);
// === General Study Module ===
Attribute studyInstanceUID = new UniqueIdentifierAttribute(TagFromName.StudyInstanceUID);
studyInstanceUID.addValue(UniqueIdentifierAttribute.createUID());
list.put(studyInstanceUID);
Attribute studyDate = new DateAttribute(TagFromName.StudyDate);
studyDate.addValue(currentDate);
list.put(studyDate);
Attribute studyTime = new TimeAttribute(TagFromName.StudyTime);
studyTime.addValue(currentTime);
list.put(studyTime);
Attribute accessionNumber = new ShortStringAttribute(TagFromName.AccessionNumber);
accessionNumber.addValue("ACC123456");
list.put(accessionNumber);
Attribute referringPhysician = new PersonNameAttribute(TagFromName.ReferringPhysicianName);
referringPhysician.addValue("Smith^Jane^Dr");
list.put(referringPhysician);
Attribute studyID = new ShortStringAttribute(TagFromName.StudyID);
studyID.addValue("STUDY001");
list.put(studyID);
// Continue with series and document modules...
}
Step 3: Adding SR Document Modules
Structured Reports have specific modules for series and document information:
// === SR Document Series Module ===
Attribute modality = new CodeStringAttribute(TagFromName.Modality);
modality.addValue("SR");
list.put(modality);
Attribute seriesInstanceUID = new UniqueIdentifierAttribute(TagFromName.SeriesInstanceUID);
seriesInstanceUID.addValue(UniqueIdentifierAttribute.createUID());
list.put(seriesInstanceUID);
Attribute seriesNumber = new IntegerStringAttribute(TagFromName.SeriesNumber);
seriesNumber.addValue("1");
list.put(seriesNumber);
// === SR Document General Module ===
Attribute instanceNumber = new IntegerStringAttribute(TagFromName.InstanceNumber);
instanceNumber.addValue("1");
list.put(instanceNumber);
Attribute contentDate = new DateAttribute(TagFromName.ContentDate);
contentDate.addValue(currentDate);
list.put(contentDate);
Attribute contentTime = new TimeAttribute(TagFromName.ContentTime);
contentTime.addValue(currentTime);
list.put(contentTime);
// Completion flag - COMPLETE or PARTIAL
Attribute completionFlag = new CodeStringAttribute(TagFromName.CompletionFlag);
completionFlag.addValue("COMPLETE");
list.put(completionFlag);
// Verification flag - VERIFIED or UNVERIFIED
Attribute verificationFlag = new CodeStringAttribute(TagFromName.VerificationFlag);
verificationFlag.addValue("VERIFIED");
list.put(verificationFlag);
Step 4: Building the Content Tree
The heart of a Structured Report is its content tree. PixelMed's ContentItemFactory helps build this:
// === Build the SR Content Tree ===
ContentItemFactory cif = new ContentItemFactory();
// Root container - document title
ContentItem root = cif.new ContainerContentItem(
null, // parent (null for root)
null, // relationship type (null for root)
new CodedSequenceItem("18782-3", "LN", "Radiology Study Observation"),
true, // continuity of content: SEPARATE
"DCMR", // template mapping resource
"2000" // template identifier
);
// Add observation context - observer type
ContentItem observerContext = cif.new CodeContentItem(
root,
"HAS OBS CONTEXT",
new CodedSequenceItem("121005", "DCM", "Observer Type"),
new CodedSequenceItem("121006", "DCM", "Person")
);
// Add observer name
ContentItem observerName = cif.new PersonNameContentItem(
root,
"HAS OBS CONTEXT",
new CodedSequenceItem("121008", "DCM", "Person Observer Name"),
"Johnson^Robert^Dr"
);
Step 5: Adding Findings and Impressions
Clinical findings are organized in sections within the content tree:
// Add findings section
ContentItem findingsSection = cif.new ContainerContentItem(
root,
"CONTAINS",
new CodedSequenceItem("121070", "DCM", "Findings"),
true,
null,
null
);
// Add text findings
ContentItem finding1 = cif.new TextContentItem(
findingsSection,
"CONTAINS",
new CodedSequenceItem("121071", "DCM", "Finding"),
"The heart size is within normal limits. No cardiomegaly."
);
ContentItem finding2 = cif.new TextContentItem(
findingsSection,
"CONTAINS",
new CodedSequenceItem("121071", "DCM", "Finding"),
"The lungs are clear without focal consolidation, effusion, or pneumothorax."
);
ContentItem finding3 = cif.new TextContentItem(
findingsSection,
"CONTAINS",
new CodedSequenceItem("121071", "DCM", "Finding"),
"The mediastinal contours are unremarkable."
);
// Add impression section
ContentItem impressionSection = cif.new ContainerContentItem(
root,
"CONTAINS",
new CodedSequenceItem("121072", "DCM", "Impression"),
true,
null,
null
);
ContentItem impression = cif.new TextContentItem(
impressionSection,
"CONTAINS",
new CodedSequenceItem("121073", "DCM", "Impression Description"),
"No acute cardiopulmonary abnormality."
);
// Convert the content tree to DICOM attributes
StructuredReport sr = new StructuredReport(root);
AttributeList srContentList = sr.getAttributeList();
// Copy SR content to our list
list.putAll(srContentList);
return list;
Coded Vocabularies
Structured Reports use standard coding schemes for interoperability:
| Scheme | Designator | Examples |
|---|---|---|
| DICOM | DCM | Document types, relationships |
| LOINC | LN | Lab tests, document sections |
| SNOMED CT | SCT | Clinical concepts, anatomy |
| RadLex | RADLEX | Radiology-specific terms |
| UCUM | UCUM | Units of measurement |
Relationship Types
Content items are linked using defined relationship types:
| Relationship | Description |
|---|---|
| CONTAINS | Parent contains child items |
| HAS OBS CONTEXT | Observation context (who, when) |
| HAS ACQ CONTEXT | Acquisition context |
| HAS CONCEPT MOD | Concept modifier |
| INFERRED FROM | Derived from other content |
| SELECTED FROM | Reference to source data |
Important Considerations
- Template Compliance: Follow DICOM SR templates for specific use cases (TID 1500 for measurements, etc.)
- Code Selection: Use appropriate coding schemes for your domain
- Verification: Set appropriate completion and verification flags
- References: Link to source images using IMAGE content items when applicable
Conclusion
DICOM Structured Reports provide a powerful way to encode clinical findings in a machine-readable format. While more complex than plain text reports, they enable advanced workflows like automated data extraction, clinical decision support, and integration with AI systems.
Understanding the tree structure, coded concepts, and relationship types is key to creating compliant Structured Reports that can be exchanged and processed across different healthcare systems. In the next tutorial in this series, I will cover DICOM transfer syntaxes and image compression. See you then!