Posting response here per request:
Thanks for that. The setup that I had seemed to work so far, but sounds like you are saying it is better to basically “replace” the built-in Dicom server and then write my own handlers for the N-Create and N-Set events, or any other events that can be handled within the Server Block:
SCP = ae.start_server((‘’, port), block = False, evt_handlers = [
(pynetdicom.evt.EVT_C_STORE, HandleStore),
(pynetdicom.evt.EVT_N_CREATE, handle_create), # See my working script at the end of the post.
(pynetdicom.evt.EVT_N_SET, handle_set),
])
Is that “safer” than the other method of just running pydicomnet in a separate thread with ae.start_server((‘’, 11112), block=False, evt_handlers=handlers) set to a port different than the 4242 that is used for the built-in server and block=False ? That would have MPPS running on a separate dedicated port and possibly thread (I’m not sure how that works since the docs say that block=False starts the server in a separate thread, but that might not apply as a plug-in). I did try that with the Dicom Server enabled running on 4242 and initially that also seemed to work, so I would effectively have 2 dicom servers, one for Orthanc and the other to handle events that Orthanc does not support currently, like MPPS.
I did try your suggested setup, and after a bit of tweaking I can at least get a response with an N_CREATE event using DVTk, after adding the ae.add_supported_context(pynetdicom.sop_class.ModalityPerformedProcedureStepSOPClass). Not sure why I had to do that explicitly, otherwise it does not recognize that. I guess
If using the “dual” setup is not recommended, this arrangement seems to work.
These are snippets of that setup:
# PIP modules need to be installed in the Docker Container via the DockerFile build:
RUN pip3 install pydicom
RUN pip3 install pynetdicom
In the Python script:
import pynetdicom # https://github.com/pydicom/pynetdicom, sudo python3 -m pip install pynetdicom
import pydicom # https://github.com/pydicom/pydicom, sudo python3 -m pip install pydicom
# INIT and startup the server
ae = pynetdicom.AE()
ae.supported_contexts = pynetdicom.AllStoragePresentationContexts
ae.add_supported_context(pynetdicom.sop_class.ModalityPerformedProcedureStepSOPClass)
SCP = None
def OnChange(changeType, level, resource):
global SCP
if changeType == orthanc.ChangeType.ORTHANC_STARTED:
port = json.loads(orthanc.GetConfiguration()).get(‘DicomPort’, 4242)
SCP = ae.start_server((‘’, port), block = False, evt_handlers = [
(pynetdicom.evt.EVT_C_STORE, HandleStore),
(pynetdicom.evt.EVT_N_CREATE, handle_create),
(pynetdicom.evt.EVT_N_SET, handle_set),
])
orthanc.LogWarning(‘DICOM server using pynetdicom has started’)
elif changeType == orthanc.ChangeType.ORTHANC_STOPPED:
orthanc.LogWarning(‘Stopping pynetdicom’)
SCP.shutdown()
orthanc.RegisterOnChangeCallback(OnChange)
# MPPS SETUP
managed_instances = {}
pynetdicom.debug_logger()
# Implement the evt.EVT_N_CREATE handler
def handle_create(event):
MPPS’ N-CREATE request must have an Affected SOP Instance UID
print(“In Pynet Dicom, N-CREATE”)
req = event.request
print(req.AffectedSOPInstanceUID)
if req.AffectedSOPInstanceUID is None:
Failed - invalid attribute value
print(“Returning 0x0106, No AffectedSOPInstanceUID”)
return 0x0106, None
Can’t create a duplicate SOP Instance
if req.AffectedSOPInstanceUID in managed_instances:
Failed - duplicate SOP Instance
print(“returning 0x0111, 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
print(“returning 0x0120, PerformedProcedureStepStatus not in Attribute List”)
return 0x0120, None
if attr_list.PerformedProcedureStepStatus.upper() != ‘IN PROGRESS’:
print(“returning 0x0106, Not IN PROGRESS”)
return 0x0106, None
Skip other tests…
Create a Modality Performed Procedure Step SOP Class Instance
DICOM Standard, Part 3, Annex B.17
print(“Getting DataSet”)
ds = pydicom.dataset.Dataset()
print(“Setting SOP UIDs”)
Add the SOP Common module elements (Annex C.12.1)
ds.SOPClassUID = pynetdicom.sop_class.ModalityPerformedProcedureStepSOPClass
ds.SOPInstanceUID = req.AffectedSOPInstanceUID
print(“Updating Attributes”)
Update with the requested attributes
ds.update(attr_list)
Add the dataset to the managed SOP Instances
managed_instances[ds.SOPInstanceUID] = ds
print(“Returned 0x0000 and DataSet”)
print (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