DICOM Basics using Java - Storage Commitment Service
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore the DICOM Storage Commitment service, which provides a mechanism for confirming that images have been safely stored in a PACS or archive before the sending system (typically a modality) deletes its local copies.
Storage Commitment is critical in clinical environments where data integrity is paramount. When a CT scanner generates images, it needs confirmation that those images are safely stored before freeing up local storage space.
Prerequisites
Before you begin, ensure you have the following:
- Java JDK installed (Java 8 or later)
- PixelMed Java DICOM Toolkit
- A Storage Commitment-capable PACS server (DCM4CHEE or Orthanc with plugin)
- You can find all the code demonstrated in this tutorial on GitHub here
“Trust, but verify.” ~ Ronald Reagan
The Theory Behind Storage Commitment
Storage Commitment addresses a fundamental challenge in distributed medical imaging systems: ensuring data durability across unreliable network boundaries. It implements an asynchronous request-confirmation exchange pattern: the SCU sends an N-ACTION request to ask for storage commitment, and the SCP later responds with an N-EVENT-REPORT to confirm (or deny) that the referenced instances are safely stored. This is not a true two-phase commit protocol (which would require a prepare/vote phase, atomicity guarantees, and rollback capabilities), but rather a simple request-confirm pattern adapted for healthcare workflows.
The problem it solves is critical: imaging modalities have limited local storage (typically enough for a few hours to a day of acquisitions), yet they must guarantee that every acquired image reaches permanent storage before deletion. Without Storage Commitment, a network failure during C-STORE could result in images being deleted from the modality before confirmation that they were successfully archived - a potentially catastrophic loss of diagnostic data.
The Distributed Systems Challenge
Consider the inherent unreliability in the imaging chain:
- Network Failures: A C-STORE might complete from the sender's perspective but fail to fully persist at the receiver
- Storage Failures: The PACS might accept images into a buffer but crash before writing to permanent storage
- Silent Corruption: Data might be corrupted in transit without detection
Storage Commitment solves these problems by requiring the storage system to verify the integrity of stored instances by matching their SOP Instance UIDs and explicitly confirm that it has:
- Received the complete image data
- Written it to durable (typically redundant) storage
- Verified its integrity through SOP Instance UID matching
The Asynchronous Design Pattern
Unlike simple request-response protocols, Storage Commitment uses an asynchronous notification pattern. This is deliberate - verification of durable storage (e.g., RAID write completion, tape archive, cloud replication) can take significant time. The N-EVENT-REPORT may arrive seconds, minutes, or even hours after the N-ACTION request, allowing the PACS to batch verification operations efficiently.
Understanding the Protocol Flow
The Storage Commitment service uses N-ACTION and N-EVENT-REPORT DIMSE services:
| Step | Operation | Description |
|---|---|---|
| 1 | C-STORE | Modality sends images to PACS |
| 2 | N-ACTION | Modality requests commitment for specific SOP instances |
| 3 | Verification | PACS verifies images are safely stored |
| 4 | N-EVENT-REPORT | PACS sends confirmation (success/failure) |
| 5 | Cleanup | Modality can safely delete committed images |
Key SOP Classes and Attributes
Storage Commitment uses the Storage Commitment Push Model:
| Element | Value |
|---|---|
| SOP Class UID | 1.2.840.10008.1.20.1 |
| SOP Instance UID | 1.2.840.10008.1.20.1.1 |
Key attributes in the N-ACTION request:
| Tag | Name | Description |
|---|---|---|
| (0008,1195) | Transaction UID | Links request to response |
| (0008,1199) | Referenced SOP Sequence | List of instances to commit |
Building the Storage Commitment Request
Here's how to build a Storage Commitment request using PixelMed:
package com.saravanansubramanian.dicom.pixelmedtutorial;
import java.util.LinkedList;
import com.pixelmed.dicom.*;
import com.pixelmed.network.*;
public class StorageCommitmentDemo {
private static final String STORAGE_COMMITMENT_SOP_CLASS = "1.2.840.10008.1.20.1";
private static final String STORAGE_COMMITMENT_SOP_INSTANCE = "1.2.840.10008.1.20.1.1";
public static void main(String[] args) {
try {
System.out.println("=== DICOM Storage Commitment Demo ===\n");
// Connection parameters
String remoteHost = "localhost";
int remotePort = 4242;
String remoteAETitle = "ORTHANC";
String localAETitle = "STORAGECOMMIT";
// SOP Instance UIDs previously stored via C-STORE
String[] sopInstanceUIDs = {
"1.2.3.4.5.6.7.8.9.1",
"1.2.3.4.5.6.7.8.9.2",
"1.2.3.4.5.6.7.8.9.3"
};
// Corresponding SOP Class UIDs
String[] sopClassUIDs = {
SOPClass.CTImageStorage,
SOPClass.CTImageStorage,
SOPClass.CTImageStorage
};
System.out.println("Requesting commitment for " + sopInstanceUIDs.length + " images...\n");
// Build presentation contexts
LinkedList<PresentationContext> presentationContexts = new LinkedList<>();
presentationContexts.add(new PresentationContext(
(byte) 0x01,
STORAGE_COMMITMENT_SOP_CLASS,
TransferSyntax.ImplicitVRLittleEndian
));
presentationContexts.add(new PresentationContext(
(byte) 0x03,
STORAGE_COMMITMENT_SOP_CLASS,
TransferSyntax.ExplicitVRLittleEndian
));
// Create association
Association association = AssociationFactory.createNewAssociation(
remoteHost, remotePort, remoteAETitle, localAETitle,
presentationContexts, null, false, null, 0, 0
);
if (association != null) {
System.out.println("Association established.\n");
// Build the N-ACTION request
AttributeList actionInfo = buildStorageCommitmentRequest(
sopInstanceUIDs, sopClassUIDs);
System.out.println("Storage Commitment Request Details:");
System.out.println("Transaction UID: " +
actionInfo.get(TagFromName.TransactionUID).getSingleStringValueOrNull());
System.out.println("Number of references: " + sopInstanceUIDs.length);
// In production: Send N-ACTION and handle N-EVENT-REPORT
// ...
association.release();
System.out.println("\nAssociation released.");
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
Building the Referenced SOP Sequence
The core of the Storage Commitment request is the Referenced SOP Sequence:
private static AttributeList buildStorageCommitmentRequest(
String[] sopInstanceUIDs, String[] sopClassUIDs) throws Exception {
AttributeList actionInfo = new AttributeList();
// Generate unique Transaction UID
String transactionUID = UniqueIdentifierAttribute.createUID();
Attribute transactionUIDAttr = new UniqueIdentifierAttribute(TagFromName.TransactionUID);
transactionUIDAttr.addValue(transactionUID);
actionInfo.put(transactionUIDAttr);
// Build Referenced SOP Sequence
SequenceAttribute referencedSOPSequence =
new SequenceAttribute(TagFromName.ReferencedSOPSequence);
for (int i = 0; i < sopInstanceUIDs.length; i++) {
AttributeList item = new AttributeList();
Attribute sopClassAttr = new UniqueIdentifierAttribute(
TagFromName.ReferencedSOPClassUID);
sopClassAttr.addValue(sopClassUIDs[i]);
item.put(sopClassAttr);
Attribute sopInstanceAttr = new UniqueIdentifierAttribute(
TagFromName.ReferencedSOPInstanceUID);
sopInstanceAttr.addValue(sopInstanceUIDs[i]);
item.put(sopInstanceAttr);
referencedSOPSequence.addItem(item);
}
actionInfo.put(referencedSOPSequence);
return actionInfo;
}
Handling the N-EVENT-REPORT Response
The N-EVENT-REPORT arrives asynchronously and indicates commitment status:
| Event Type ID | Meaning |
|---|---|
| 1 | All instances successfully committed |
| 2 | Some or all instances failed |
The response includes:
| Sequence | Content |
|---|---|
| Referenced SOP Sequence | Successfully committed instances |
| Failed SOP Sequence | Failed instances with reasons |
Failure Reason Codes
| Code | Meaning |
|---|---|
| 0110 | Processing failure |
| 0112 | No such object instance |
| 0213 | Resource limitation |
Important Considerations
- Asynchronous: N-EVENT-REPORT may arrive minutes or hours later
- SCP Required: Need to implement SCP to receive event reports
- Transaction Tracking: Store Transaction UID to correlate responses
- Retry Logic: Implement retry for failed commitments
Conclusion
Storage Commitment is an important DICOM service for ensuring data integrity in medical imaging workflows. It provides reliable confirmation that images are safely stored before modalities free up local storage space.
While implementing a complete Storage Commitment solution requires handling asynchronous N-EVENT-REPORT responses, understanding the request building process is the first step toward a production implementation. In the next tutorial in this series, I will cover Modality Worklist (MWL) which is a critical component of healthcare imaging workflows. See you then!