Modality Performed Procedure Step Management Service Examples¶
The DICOM Modality Performed Procedure Step Management (MPPS) service allows an Application Entity to log or track procedures performed by a modality through the N-CREATE, N-SET, N-EVENT-REPORT and N-GET services. It has three SOP Classes:
Modality Performed Procedure Step SOP Class, used with N-CREATE and N-SET to create and set the SOP Instance’s attribute values
Modality Performed Procedure Step Retrieve SOP Class, used with N-GET to retrieve the SOP Instance’s attribute values
Modality Performed Procedure Step Notification SOP Class, used with N-EVENT-REPORT to notify a peer of the status of a procedure.
MPPS is usually used in combination with the Modality Worklist Service Class to provide the modality a mechanism for notifying the RIS (or PACS) that requested the procedure of it’s current status. The modality is typically the SCU and the RIS (or PACS) the SCP.
MPPS - Create SCU¶
Associate with a peer and request the use of the MPPS Service to create a new SOP Instance.
from pydicom.dataset import Dataset
from pydicom.uid import generate_uid
from pynetdicom import AE, debug_logger
from pynetdicom.sop_class import (
ModalityPerformedProcedureStep,
CTImageStorage
)
from pynetdicom.status import code_to_category
debug_logger()
ct_study_uid = generate_uid()
mpps_instance_uid = generate_uid()
# Our N-CREATE *Attribute List*
def build_attr_list():
ds = Dataset()
# Performed Procedure Step Relationship
ds.ScheduledStepAttributesSequence = [Dataset()]
step_seq = ds.ScheduledStepAttributesSequence
step_seq[0].StudyInstanceUID = ct_study_uid
step_seq[0].ReferencedStudySequence = []
step_seq[0].AccessionNumber = '1'
step_seq[0].RequestedProcedureID = "1"
step_seq[0].RequestedProcedureDescription = 'Some procedure'
step_seq[0].ScheduledProcedureStepID = "1"
step_seq[0].ScheduledProcedureStepDescription = 'Some procedure step'
step_seq[0].ScheduledProcedureProtocolCodeSequence = []
ds.PatientName = 'Test^Test'
ds.PatientID = '123456'
ds.PatientBirthDate = '20000101'
ds.PatientSex = 'O'
ds.ReferencedPatientSequence = []
# Performed Procedure Step Information
ds.PerformedProcedureStepID = "1"
ds.PerformedStationAETitle = 'SOMEAE'
ds.PerformedStationName = 'Some station'
ds.PerformedLocation = 'Some location'
ds.PerformedProcedureStepStartDate = '20000101'
ds.PerformedProcedureStepStartTime = '1200'
ds.PerformedProcedureStepStatus = 'IN PROGRESS'
ds.PerformedProcedureStepDescription = 'Some description'
ds.PerformedProcedureTypeDescription = 'Some type'
ds.PerformedProcedureCodeSequence = []
ds.PerformedProcedureStepEndDate = None
ds.PerformedProcedureStepEndTime = None
# Image Acquisition Results
ds.Modality = 'CT'
ds.StudyID = "1"
ds.PerformedProtocolCodeSequence = []
ds.PerformedSeriesSequence = []
return ds
# Initialise the Application Entity
ae = AE()
# Add a requested presentation context
ae.add_requested_context(ModalityPerformedProcedureStep)
# Associate with peer AE at IP 127.0.0.1 and port 11112
assoc = ae.associate("127.0.0.1", 11112)
if assoc.is_established:
# Use the N-CREATE service to send a request to create a SOP Instance
# should return the Instance itself
status, attr_list = assoc.send_n_create(
build_attr_list(),
ModalityPerformedProcedureStep,
mpps_instance_uid
)
# Check the status of the display system request
if status:
print('N-CREATE request status: 0x{0:04x}'.format(status.Status))
# If the MPPS request succeeded the status category may
# be either Success or Warning
category = code_to_category(status.Status)
if category in ['Warning', 'Success']:
# `attr_list` is a pydicom Dataset containing attribute values
print(attr_list)
else:
print('Connection timed out, was aborted or received invalid response')
# Release the association
assoc.release()
else:
print('Association rejected, aborted or never connected')
MPPS - Set SCU¶
Once the MPPS SOP Instance has successfully been created, the modality can send
one or more N-SET requests to the MPPS SCP in order to update the attributes
of the SOP Instance. When the procedure has been completed a final N-SET
request is sent containing a Modification List with an (0040,0252) Performed
Procedure Step Status of "COMPLETED"
.
# Continuing on from the previous example...
# Modality performs the procedure, update the MPPS SCP
# In performing the procedure a series with ten CT Image Storage
# SOP Instances is generated
ct_series_uid = generate_uid()
ct_instance_uids = [generate_uid() for ii in range(10)]
# Our N-SET *Modification List*
def build_mod_list(series_instance, sop_instances):
ds = Dataset()
ds.PerformedSeriesSequence = [Dataset()]
series_seq = ds.PerformedSeriesSequence
series_seq[0].PerformingPhysicianName = None
series_seq[0].ProtocolName = "Some protocol"
series_seq[0].OperatorName = None
series_seq[0].SeriesInstanceUID = series_instance
series_seq[0].SeriesDescription = "some description"
series_seq[0].RetrieveAETitle = None
series_seq[0].ReferencedImageSequence = []
img_seq = series_seq[0].ReferencedImageSequence
for uid in sop_instances:
img_ds = Dataset()
img_ds.ReferencedSOPClassUID = CTImageStorage
img_ds.ReferencedSOPInstanceUID = uid
img_seq.append(img_ds)
series_seq[0].ReferencedNonImageCompositeSOPInstanceSequence = []
return ds
# Our final N-SET *Modification List*
final_ds = Dataset()
final_ds.PerformedProcedureStepStatus = "COMPLETED"
final_ds.PerformedProcedureStepEndDate = "20000101"
final_ds.PerformedProcedureStepEndTime = "1300"
# Associate with peer again
assoc = ae.associate("127.0.0.1", 11112)
if assoc.is_established:
# Use the N-SET service to update the SOP Instance
status, attr_list = assoc.send_n_set(
build_mod_list(ct_series_uid, ct_instance_uids),
ModalityPerformedProcedureStep,
mpps_instance_uid
)
if status:
print('N-SET request status: 0x{0:04x}'.format(status.Status))
category = code_to_category(status.Status)
if category in ['Warning', 'Success']:
# Send completion
status, attr_list = assoc.send_n_set(
final_ds,
ModalityPerformedProcedureStep,
mpps_instance_uid
)
if status:
print('Final N-SET request status: 0x{0:04x}'.format(status.Status))
else:
print('Connection timed out, was aborted or received invalid response')
assoc.release()
MPPS SCP¶
The following represents a toy implementation of an MPPS SCP (Modality Performed Procedure Step SOP Class only).
Check the
handler implementation documentation
to see the requirements for the evt.EVT_N_CREATE
and evt.EVT_N_SET
handlers.
from pydicom.dataset import Dataset
from pynetdicom import AE, evt
from pynetdicom.sop_class import ModalityPerformedProcedureStep
managed_instances = {}
# Implement the evt.EVT_N_CREATE handler
def handle_create(event):
# MPPS' N-CREATE request must have an *Affected SOP Instance UID*
req = event.request
if req.AffectedSOPInstanceUID is None:
# Failed - invalid attribute value
return 0x0106, None
# Can't create a duplicate SOP Instance
if req.AffectedSOPInstanceUID in managed_instances:
# Failed - duplicate SOP Instance
return 0x0111, None
# The N-CREATE request's *Attribute List* dataset
attr_list = event.attribute_list
# Performed Procedure Step Status must be 'IN PROGRESS'
if "PerformedProcedureStepStatus" not in attr_list:
# Failed - missing attribute
return 0x0120, None
if attr_list.PerformedProcedureStepStatus.upper() != 'IN PROGRESS':
return 0x0106, None
# Skip other tests...
# Create a Modality Performed Procedure Step SOP Class Instance
# DICOM Standard, Part 3, Annex B.17
ds = Dataset()
# Add the SOP Common module elements (Annex C.12.1)
ds.SOPClassUID = ModalityPerformedProcedureStep
ds.SOPInstanceUID = req.AffectedSOPInstanceUID
# Update with the requested attributes
ds.update(attr_list)
# Add the dataset to the managed SOP Instances
managed_instances[ds.SOPInstanceUID] = ds
# Return status, dataset
return 0x0000, ds
# Implement the evt.EVT_N_SET handler
def handle_set(event):
req = event.request
if req.RequestedSOPInstanceUID not in managed_instances:
# Failure - SOP Instance not recognised
return 0x0112, None
ds = managed_instances[req.RequestedSOPInstanceUID]
# The N-SET request's *Modification List* dataset
mod_list = event.attribute_list
# Skip other tests...
ds.update(mod_list)
# Return status, dataset
return 0x0000, ds
handlers = [(evt.EVT_N_CREATE, handle_create), (evt.EVT_N_SET, handle_set)]
# Initialise the Application Entity and specify the listen port
ae = AE()
# Add the supported presentation context
ae.add_supported_context(ModalityPerformedProcedureStep)
# Start listening for incoming association requests
ae.start_server(("127.0.0.1", 11112), evt_handlers=handlers)