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:

  1. Received the complete image data
  2. Written it to durable (typically redundant) storage
  3. 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:

StepOperationDescription
1C-STOREModality sends images to PACS
2N-ACTIONModality requests commitment for specific SOP instances
3VerificationPACS verifies images are safely stored
4N-EVENT-REPORTPACS sends confirmation (success/failure)
5CleanupModality can safely delete committed images

Key SOP Classes and Attributes

Storage Commitment uses the Storage Commitment Push Model:

ElementValue
SOP Class UID1.2.840.10008.1.20.1
SOP Instance UID1.2.840.10008.1.20.1.1

Key attributes in the N-ACTION request:

TagNameDescription
(0008,1195)Transaction UIDLinks request to response
(0008,1199)Referenced SOP SequenceList 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 IDMeaning
1All instances successfully committed
2Some or all instances failed

The response includes:

SequenceContent
Referenced SOP SequenceSuccessfully committed instances
Failed SOP SequenceFailed instances with reasons

Failure Reason Codes

CodeMeaning
0110Processing failure
0112No such object instance
0213Resource 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!