DICOM Basics using .NET and C# - Understanding Worklists and MPPS
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore two related DICOM services that are essential for radiology workflow: Modality Worklist (MWL) and Modality Performed Procedure Step (MPPS).
The Modality Worklist service allows imaging devices (modalities) to retrieve scheduled procedure information from a hospital information system, eliminating manual data entry errors. MPPS complements this by allowing modalities to report back the status of procedures as they are performed.
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
- An MWL Server: Orthanc with Worklist plugin or DCM4CHEE
- You can find all the code demonstrated in this tutorial on GitHub here
“The best way to predict the future is to create it.” ~ Peter Drucker
The Theory Behind Worklist and MPPS
The Modality Worklist exists to solve the Master Patient Index (MPI) problem at the point of imaging. Before MWL, technologists manually entered patient demographics at the modality console, leading to typos, misidentifications, and inconsistencies. Studies could become orphaned or misattributed. MWL provides a single source of truth: the scheduling system (RIS/HIS) that already verified patient identity during registration.
From an information architecture perspective, MWL implements data replication at the point of need. Rather than requiring modalities to have real-time access to patient databases (which would create tight coupling and availability concerns), MWL uses a pull-based query model. The modality queries only when needed, receiving a snapshot of relevant scheduled procedures. This loose coupling allows modalities to operate semi-independently while still receiving authoritative patient data.
The Scheduled Procedure Step Sequence reflects the reality that a single order can involve multiple imaging steps. A CT exam might include scout images, contrast phases, and delayed images. Each step has its own scheduling attributes while sharing patient and study context. This hierarchical model (Study → Requested Procedure → Scheduled Procedure Step) enables complex multi-step protocols while maintaining traceability to the original order.
MPPS addresses the bidirectional synchronization problem. While MWL pushes scheduling information to modalities, MPPS closes the loop by reporting what actually happened back to the information system. This is crucial because scheduled procedures don't always proceed as planned: exams may be cancelled, additional images may be acquired, or different protocols may be used. MPPS provides the performed reality to contrast with the scheduled intent.
The MPPS state model (IN PROGRESS → COMPLETED or DISCONTINUED) implements a finite state machine that captures the exam lifecycle. N-CREATE signals the start (state transition to IN PROGRESS), while N-SET can update attributes during the exam and signal completion. This state machine enables real-time tracking of exam status across the enterprise.
Understanding Modality Worklist
The Modality Worklist (MWL) is a DICOM service that allows modalities to query for scheduled procedures. Here's a typical workflow:
- Patient arrives for scheduled imaging exam
- Technologist queries the worklist from the modality
- Worklist returns matching scheduled procedures
- Technologist selects the correct procedure
- Patient demographics are automatically populated
- Imaging begins with correct patient and study information
MWL SOP Class and Key Attributes
Modality Worklist uses the C-FIND operation with a specific SOP Class:
| SOP Class | UID |
|---|---|
| Modality Worklist Information Model - FIND | 1.2.840.10008.5.1.4.31 |
Key query attributes include:
Patient Level:
| Tag | Name | Description |
|---|---|---|
| (0010,0010) | Patient Name | Patient’s full name |
| (0010,0020) | Patient ID | Hospital patient identifier |
| (0010,0030) | Patient Birth Date | Date of birth |
| (0010,0040) | Patient Sex | M, F, or O |
Study/Procedure Level:
| Tag | Name | Description |
|---|---|---|
| (0008,0050) | Accession Number | Unique order identifier |
| (0020,000D) | Study Instance UID | Unique study identifier |
| (0032,1060) | Requested Procedure Description | Description of the procedure |
| (0040,1001) | Requested Procedure ID | Procedure identifier |
Scheduled Procedure Step Sequence (0040,0100):
| Tag | Name | Description |
|---|---|---|
| (0008,0060) | Modality | CT, MR, US, etc. |
| (0040,0001) | Scheduled Station AE Title | Target modality AE title |
| (0040,0002) | Scheduled Procedure Step Start Date | Scheduled date |
| (0040,0003) | Scheduled Procedure Step Start Time | Scheduled time |
| (0040,0007) | Scheduled Procedure Step Description | Step description |
Step 1 of 3: Creating the MWL Query
Let's implement a Modality Worklist query using fo-dicom:
using System;
using System.Diagnostics;
using FellowOakDicom;
using System.Threading;
using System.Threading.Tasks;
using FellowOakDicom.Network;
using System.Threading;
using System.Threading.Tasks;
using FellowOakDicom.Network.Client;
namespace ModalityWorklistQuery
{
public class Program
{
private static readonly string DicomServerHost = "localhost";
private static readonly int DicomServerPort = 4242;
private static readonly string RemoteAeTitle = "ORTHANC";
private static readonly string LocalAeTitle = "FODICOM_MWL";
private static readonly bool UseTls = false;
public static async Task Main(string[] args)
{
try
{
LogToDebugConsole("=== Modality Worklist Query Tutorial ===");
LogToDebugConsole($" MWL Server: {DicomServerHost}:{DicomServerPort}");
LogToDebugConsole($" Remote AE: {RemoteAeTitle}");
LogToDebugConsole($" Local AE: {LocalAeTitle}");
// Create and send MWL query
var client = CreateMwlClient();
LogToDebugConsole("Sending Modality Worklist query...");
await client.SendAsync(CancellationToken.None);
LogToDebugConsole("Modality Worklist query completed.");
}
catch (Exception e)
{
LogToDebugConsole($"Error: {e.Message}");
}
}
private static void LogToDebugConsole(string message)
{
Debug.WriteLine(message);
}
}
}
Step 2 of 3: Building the Query Request
Now let's build the C-FIND request with appropriate return keys:
private static DicomClient CreateMwlClient()
{
var client = DicomClientFactory.Create(DicomServerHost, DicomServerPort, UseTls, LocalAeTitle, RemoteAeTitle);
// Create MWL C-FIND request
var request = DicomCFindRequest.CreateWorklistQuery();
// Patient Level Return Keys
request.Dataset.AddOrUpdate(DicomTag.PatientName, "");
request.Dataset.AddOrUpdate(DicomTag.PatientID, "");
request.Dataset.AddOrUpdate(DicomTag.PatientBirthDate, "");
request.Dataset.AddOrUpdate(DicomTag.PatientSex, "");
// Study/Procedure Level Return Keys
request.Dataset.AddOrUpdate(DicomTag.AccessionNumber, "");
request.Dataset.AddOrUpdate(DicomTag.StudyInstanceUID, "");
request.Dataset.AddOrUpdate(DicomTag.RequestedProcedureDescription, "");
request.Dataset.AddOrUpdate(DicomTag.RequestedProcedureID, "");
// Scheduled Procedure Step Sequence (required for MWL)
var scheduledProcedureStep = new DicomDataset();
scheduledProcedureStep.Add(DicomTag.Modality, "");
scheduledProcedureStep.Add(DicomTag.ScheduledStationAETitle, "");
scheduledProcedureStep.Add(DicomTag.ScheduledProcedureStepStartDate, "");
scheduledProcedureStep.Add(DicomTag.ScheduledProcedureStepStartTime, "");
scheduledProcedureStep.Add(DicomTag.ScheduledProcedureStepDescription, "");
scheduledProcedureStep.Add(DicomTag.ScheduledProcedureStepID, "");
request.Dataset.AddOrUpdate(
new DicomSequence(DicomTag.ScheduledProcedureStepSequence, scheduledProcedureStep));
// Optional: Filter by today's date
// scheduledProcedureStep.AddOrUpdate(DicomTag.ScheduledProcedureStepStartDate,
// DateTime.Today.ToString("yyyyMMdd"));
// Optional: Filter by modality
// scheduledProcedureStep.AddOrUpdate(DicomTag.Modality, "CT");
// Attach response handler
request.OnResponseReceived += OnMwlResponseReceived;
await client.AddRequestAsync(request);
// Add association event handlers
client.AssociationAccepted += (s, e) =>
LogToDebugConsole($"Association accepted by: {e.Association.RemoteHost}");
client.AssociationRejected += (s, e) =>
LogToDebugConsole($"Association rejected: {e.Reason}");
client.AssociationReleased += (s, e) =>
LogToDebugConsole("Association released.");
return client;
}
Step 3 of 3: Handling the Response
Process the MWL response to display scheduled procedures:
private static void OnMwlResponseReceived(DicomCFindRequest request, DicomCFindResponse response)
{
if (response.Status == DicomStatus.Pending)
{
LogToDebugConsole("--- Scheduled Procedure Found ---");
LogToDebugConsole($" Patient Name: {response.Dataset.GetSingleValueOrDefault(DicomTag.PatientName, "")}");
LogToDebugConsole($" Patient ID: {response.Dataset.GetSingleValueOrDefault(DicomTag.PatientID, "")}");
LogToDebugConsole($" Accession #: {response.Dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, "")}");
LogToDebugConsole($" Procedure: {response.Dataset.GetSingleValueOrDefault(DicomTag.RequestedProcedureDescription, "")}");
// Extract Scheduled Procedure Step information
var sps = response.Dataset.GetSequence(DicomTag.ScheduledProcedureStepSequence);
if (sps != null && sps.Items.Count > 0)
{
var spsItem = sps.Items[0];
LogToDebugConsole($" Modality: {spsItem.GetSingleValueOrDefault(DicomTag.Modality, "")}");
LogToDebugConsole($" Scheduled: {spsItem.GetSingleValueOrDefault(DicomTag.ScheduledProcedureStepStartDate, "")} {spsItem.GetSingleValueOrDefault(DicomTag.ScheduledProcedureStepStartTime, "")}");
}
LogToDebugConsole("");
}
if (response.Status == DicomStatus.Success)
{
LogToDebugConsole("--- MWL Query Complete ---");
}
}
Sample output from a worklist query:
=== Modality Worklist Query Tutorial ===
MWL Server: localhost:4242
Remote AE: ORTHANC
Local AE: FODICOM_MWL
Sending Modality Worklist query...
Association accepted by: localhost
--- Scheduled Procedure Found ---
Patient Name: Smith^John
Patient ID: PAT001
Accession #: ACC12345
Procedure: CT Chest with Contrast
Modality: CT
Scheduled: 20250121 093000
--- Scheduled Procedure Found ---
Patient Name: Johnson^Mary
Patient ID: PAT002
Accession #: ACC12346
Procedure: CT Abdomen/Pelvis
Modality: CT
Scheduled: 20250121 100000
--- MWL Query Complete ---
Association released.
Modality Worklist query completed.
Understanding MPPS (Modality Performed Procedure Step)
MPPS allows modalities to report procedure status back to the information system. It uses N-CREATE and N-SET operations:
| Operation | When Used |
|---|---|
| N-CREATE (IN PROGRESS) | When procedure begins |
| N-SET (COMPLETED) | When procedure completes successfully |
| N-SET (DISCONTINUED) | When procedure is stopped/cancelled |
The MPPS SOP Class is:
| SOP Class | UID |
|---|---|
| Modality Performed Procedure Step | 1.2.840.10008.3.1.2.3.3 |
Orthanc Worklist Configuration
To test MWL with Orthanc, enable the Worklist plugin and create .wl files:
{
"Plugins": ["libOrthancWorklistsPlugin.so"],
"WorklistsDatabase": "/path/to/worklists"
}
Create worklist files (.wl) containing DICOM datasets with scheduled procedure information.
Best Practices
- Always filter queries by date to reduce result set size
- Filter by modality type when querying from a specific device
- Implement caching for frequently accessed worklist data
- Handle network timeouts and implement retry logic
- Log all worklist interactions for troubleshooting
Conclusion
Modality Worklist and MPPS are essential DICOM services for integrating imaging modalities with hospital information systems. MWL eliminates manual data entry at the modality, reducing errors and improving workflow efficiency. MPPS provides real-time procedure status updates back to the scheduling system.
Together, these services form the foundation of the IHE Scheduled Workflow profile, which is widely implemented in healthcare facilities worldwide. Understanding and implementing these services is crucial for anyone developing medical imaging integration solutions.
Please check out the next tutorial in this series where we cover DICOM Structured Reports.