I developed a plugin that parses the ImageComments tage of an incoming instance and modifies the incoming dcm BodyPartExamined Tag depending on the comment and then uploads the modified dcm instance. Lastly, if the modified instance is uploaded correctly the original DICOM instance is deleted.
import orthanc
import sys
# print("sys.path before modification: " + ", ".join(sys.path))
# Print contents of /tmp/.venv/
import os
sys.path = ["/etc/orthanc", "/usr/lib/python3.7", "/usr/lib/python3.7/lib-dynload", "/usr/local/lib/python3.7/dist-packages", '/usr/lib/python3/dist-packages', "/tmp/.venv/lib64/python3.7/site-packages"]
# print("sys.path after modification: " + ", ".join(sys.path))
import pydicom
import json
import io
def get_location(comment):
if len(comment) == 1:
comment = comment + " "
if comment.startswith("l "):
return "Left Ovary"
elif comment.startswith("r "):
return "Right Ovary"
elif comment.startswith("u "):
return "Uterus"
else:
return "Unknown"
def set_instance_location(instance_id, location):
if location in ["Left Ovary", "Right Ovary"]:
if location == "Right Ovary":
anatomic_region = {
"CodeValue": "280123002",
"CodingSchemeDesignator": "SCT",
"CodeMeaning": "Right Ovary",
}
else:
anatomic_region = {
"CodeValue": "280124008",
"CodingSchemeDesignator": "SCT",
"CodeMeaning": "Left Ovary",
}
elif location == "Uterus":
anatomic_region = {
"CodeValue": "181452004",
"CodingSchemeDesignator": "SCT",
"CodeMeaning": "Uterus",
}
else:
return # Don't modify if location is Unknown
replace_tag_dict = {
"BodyPartExamined": location,
"AnatomicRegionSequence": [anatomic_region],
}
set_instance_tags(instance_id, replace_tag_dict)
def set_instance_tags(instance_id, replace_tag_dict):
# Modify the instance
modified_instance = orthanc.RestApiPost(f'/instances/{instance_id}/modify', json.dumps({"Replace": replace_tag_dict}))
# Get the modified instance
dcm = pydicom.dcmread(io.BytesIO(modified_instance))
print(f"Instance {instance_id} modified with new metadata: {dcm.BodyPartExamined}")
# Upload the modified instance
new_instance_id = upload_instance(dcm)
if new_instance_id:
delete_instance(instance_id)
def upload_instance(dcm):
dcm_bytes = io.BytesIO()
dcm.save_as(dcm_bytes)
new_instance = orthanc.RestApiPost('/instances', dcm_bytes.getvalue())
new_instance_id = json.loads(new_instance)['ID']
print(f"Instance successfully uploaded: /instances/{new_instance_id}")
return new_instance_id
def delete_instance(instance_id):
orthanc.RestApiDelete(f'/instances/{instance_id}')
print(f"Instance with orthanc ID: {instance_id} at /instances/{instance_id} successfully deleted.")
def OnStoredInstance(dicom, instanceId):
print(f'New instance received: {instanceId}')
#print(dicom.GetInstanceOrigin())
print("Number of frames: ", dicom.GetInstanceFramesCount())
simplified_dcm_json = dicom.GetInstanceSimplifiedJson()
simplified_dcm_dict = json.loads(simplified_dcm_json)
# orthanc.OrthancPluginFreeString(simplified_dcm_json)
advanced_dcm_json = dicom.GetInstanceAdvancedJson(3,0,20)
advanced_dcm_dict = json.loads(advanced_dcm_json)
# orthanc.OrthancPluginFreeString(advanced_dcm_json)
# Check if BodyPartExamined is present in simplified and advanced JSON
print(f"BodyPartExamined - Simplified: {simplified_dcm_dict.get('BodyPartExamined', '')}, Advanced: {advanced_dcm_dict.get('BodyPartExamined', '')}")
# Check if ImageComments is present in simplified and advanced JSON
print(f"ImageComments - Simplified: {simplified_dcm_dict.get('ImageComments', '')}, Advanced: {advanced_dcm_dict.get('ImageComments', '')}")
# orthanc.OrthancPluginFreeString(new_uuid)
# Generate new UUID
new_uuid = orthanc.GenerateUuid()
print("New UUID: ", new_uuid)
# orthanc.OrthancPluginFreeString(new_uuid)
# Check BodyPartExamined field
body_part_examined = advanced_dcm_dict.get('BodyPartExamined', '')
print(f"BodyPartExamined: {body_part_examined}")
if not body_part_examined:
# If BodyPartExamined is empty or doesn't exist, check ImageComments
image_comments = advanced_dcm_dict.get('ImageComments', '')
print(f"ImageComments: {image_comments}")
if image_comments:
location = get_location(image_comments)
print(f"Location: {location}")
if location != "Unknown":
set_instance_location(instanceId, location)
orthanc.RegisterOnStoredInstanceCallback(OnStoredInstance)
The parsing is done from the orthanc plugin functions while the modification, upload and deletion is done using the Orthanc plugin REST API functions. Is there a easier or more efficient way to do this without having to reupload, check and delete?
I was also not able to use the orthanc.OrthancPluginFreeString(new_uuid) function correctly (it didnt find the fucntion) Im not sure what I am doing wrong for this.
I also noticed the BodyPartExamined Tag is on the series level in the Orthanc Explorer 2. The BodyPartExamined tag is however normally a instance level tag. When I upload a new instance with a new series (and the tag is added using my plugin). The Series level BodyPartExamined Tag in the explorer stays empty, only the instance level view shows the correct tag value.
What was the intention here?
For my research project im receiving images from 3 different body parts and was debating to split them into separate series when they are received according to their BodyPartExamined and therefore indirectly depending on the ImageComments.
I would then like to automatically segment them using a python segmentation pipeline I have developed. The pipeline requires python 3.10 and the system level python of the orthanc docker image is only 3.7. Would you recommend that I set up a separate python webservice listening on another port that receives the new dicom images and returns the segmentation instance to the orthanc docker image?