Update Study Tags from MWL file on 'STABLE_STUDY'

Items in Bold are the short version:

I sort of abandoned using a Lua script with OnStoredInstance as I was looking into earlier as it does not seem as flexible as using a Python script, plus no feedback about that. The issue is that some dicom tags are not populated by the Modality through the MWL (e.g. StudyDescription, ContentCreatorName, among others), so I want to add / edit those tags either as the instances are sent to Orthanc (preferably before they are even stored), or after they are stored (less desired). I don’t see how to modify tags before an instance is committed to storage using a Lua script, which is why I’m exploring the following approach. Python script that is called when a study becomes Stable.

def OnChange(changeType, level, resourceId):

if changeType == orthanc.ChangeType.STABLE_STUDY:

print(‘Stable study: %s’ % resourceId)
study = json.loads(orthanc.RestApiGet(‘/studies/’ + resourceId))
print (study)
updatetags = dict();
updatetags[‘ContentCreatorName’] = “”
updatetags[‘StudyDescription’] = “”
updatetags[‘PatientAddress’] = “”
updatetags[‘RequestedProcedureDescription’] = “”
updatetags[‘StudyInstanceUID’] = “1.3.6.1.4.1.56016.1.1.1.103.1620244246” # study[‘MainDicomTags’][‘StudyInstanceUID’]
worklistmatches = json.loads(orthanc.RestApiPost(‘/modalities/PACS1/find-worklist’, json.dumps(updatetags)))
if len(worklistmatches) == 1:
print(“1 Match in the MWL folder”)
worklistmatches = worklistmatches[0]
print(worklistmatches)
print(“Insert Call To Function to Modify Tags from MWL response, then delete MWL”)
elif len(worklistmatches) == 0:
print(“StudyInstanceUID not in MWL”)
else:
pinrt(“More than one match for StudyInstanceUID in MWL”)

orthanc.RegisterOnChangeCallback(OnChange)

This does get data from a MWL file if there is one matching the StudyInstanceUID for the study, e.g.:

{
“PatientAddress”: “PatientAddress”,
“RequestedProcedureDescription”: “MRI INTERNAL AUDITORY CANAL - NO CONTRAST”,
“SpecificCharacterSet”: “ISO_IR 192”,
“StudyDescription”: “MRI INTERNAL AUDITORY CANAL - NO CONTRAST”,
“StudyInstanceUID”: “1.3.6.1.4.1.56016.1.1.1.103.1620244246”
}

and then what I’d like to do is modify / update the study with those new tags values. I know that the ‘modify’ feature can do that, but it looks like you have to make a copy and then delete the original. I would prefer to just edit or add some tags, not the PatientID or StudyInstanceUID. I would prefer to keep the original StudyInstanceUID instead of Orthanc creating a new Study with a newly generated StudyInstanceUID.

The is script actually does / will do what I’m looking for though since I already have MWL’s somewhat setup and working with the modality with a few issues related to the StudyDescription and other tags. Also quite easy here to delete an MWL since I have a script to do that already. Not that I have a modality to actually work with, starting to explore using MPPS, but this method is actually is somewhat modality independent.

BEGINNING OF /mwl/file/delete

curl -k -X POST -d ‘[“AccessionNumber”]’ http://localhost:8042/mwl/file/delete

def DeleteMWLByAccession(output, uri, **request):
if request[‘method’] != ‘POST’:
output.SendMethodNotAllowed(‘POST’)
else:
response = dict();
try:
data = json.loads(request[‘body’])

the accession_number to delete, for the filename

accession = data[0]
pathtoworklist = json.loads(orthanc.GetConfiguration())[‘Worklists’][‘Database’] + ‘/’
filenametxt = pathtoworklist + accession + ‘.txt’
filenamewl = pathtoworklist + accession + ‘.wl’
if os.path.exists(filenametxt):
os.remove(filenametxt)
response[‘filenametxt’] = “true”
else:
response[‘filenametxt’] = “false”
if os.path.exists(filenamewl):
os.remove(filenamewl)
response[‘filenamewl’] = “true”
else:
response[‘filenamewl’] = “false”
output.AnswerBuffer(json.dumps(response, indent = 3), ‘application/json’)

except Exception as e:

response[‘error’] = str(e)
output.AnswerBuffer(json.dumps(response, indent = 3), ‘application/json’)

orthanc.RegisterRestCallback(‘/mwl/file/delete(.*)’, DeleteMWLByAccession)

Partially working python script version of what I am looking at, although could set it up to be invoked by the RESTAPI when a study is QA’ed by the Tech, instead of using Study Stable:

response[‘modify’] = orthanc.RestApiPost(‘/studies/’ + study[‘ID’] + ‘/modify’, ‘{“Replace”:’ + json.dumps(worklistmatches) + ‘}’)

creates another copy and leaves the original. I was looking to just modify certain tags in the original without creating a new study and StudyInstanceUID. I could just delete the original after making a modified copy, but that seems a little resource intensive.

def OnChange(changeType, level, resourceId):

if changeType == orthanc.ChangeType.STABLE_STUDY:
response = dict();
print(‘Stable study: %s’ % resourceId)
study = json.loads(orthanc.RestApiGet(‘/studies/’ + resourceId))
print (study)
updatetags = dict();
updatetags[‘ContentCreatorName’] = “”
updatetags[‘StudyDescription’] = “”
updatetags[‘PatientAddress’] = “”
updatetags[‘RequestedProcedureDescription’] = “”
updatetags[‘StudyInstanceUID’] = study[‘MainDicomTags’][‘StudyInstanceUID’] # study[‘MainDicomTags’][‘StudyInstanceUID’]
worklistmatches = json.loads(orthanc.RestApiPost(‘/modalities/PACS1/find-worklist’, json.dumps(updatetags)))
if len(worklistmatches) == 1:
print(“1 Match in the MWL folder”)
worklistmatches = worklistmatches[0]
del worklistmatches[‘StudyInstanceUID’]
del worklistmatches[‘SpecificCharacterSet’]
try:
print (‘{“Replace”:’ + json.dumps(worklistmatches) + ‘}’)
response[‘modify’] = orthanc.RestApiPost(‘/studies/’ + study[‘ID’] + ‘/modify’, ‘{“Replace”:’ + json.dumps(worklistmatches) + ‘}’)
pathtoworklist = json.loads(orthanc.GetConfiguration())[‘Worklists’][‘Database’] + ‘/’
filenametxt = pathtoworklist + study[‘MainDicomTags’][‘AccessionNumber’] + ‘.txt’
filenamewl = pathtoworklist + study[‘MainDicomTags’][‘AccessionNumber’] + ‘.wl’
if os.path.exists(filenametxt):
os.remove(filenametxt)
response[‘txt_deleted’] = filenametxt
if os.path.exists(filenamewl):
os.remove(filenamewl)
response[‘wl_deleted’] = filenamewl

print(response)

output.AnswerBuffer(json.dumps(response, indent = 3), ‘application/json’) # Only works if used with callback.

except Exception as e:
response[‘error’] = str(e)
print(response)

elif len(worklistmatches) == 0:
print(“StudyInstanceUID not in MWL”)
else:
print(“More than one match for StudyInstanceUID in MWL. Should not happen.”)

orthanc.RegisterOnChangeCallback(OnChange)

Never Mind. The method above is kind of interesting really, but the easier solution is to use a Lua Script and then reconstruct the tags when the Study is Stable or when the Tech “Completes” the study.

function OnStoredInstance(instanceId, tags, metadata, origin)

if (origin[‘RequestOrigin’] ~= ‘Lua’) then – Could add a check for origin[‘RemoteAet’] = “ModalityAET” & origin[‘DicomProtocol’] = “DicomProtocol”

– The tags to be replaced, can be customized based on the AET Origin if necessary
local replace = {}
replace[‘0070,0084’] = ‘ContentCreatorName’
replace[‘0008,1070’] = ‘OperatorsName’
replace[‘StudyDescription’] = tags[‘RequestAttributesSequence’][1][‘ScheduledProcedureStepDescription’] – The Modality sends these

– The tags to be removed
– local remove = { ‘MilitaryRank’ }

– Modify the instance, send it, then delete the modified instance
ModifyInstance(instanceId, replace, remove, true)

– Delete the original instance
Delete(instanceId)
– call RESTAPI via REST or when study Stable to reconstruct tags curl -X POST -k /studies/2a46e524-a342a4ee-bf8da2bb-911a3a7f-36d9ec85/reconstruct on the study after the study is stable
end
end