Update Study Tags from MWL file on 'STABLE_STUDY'

172 views
Skip to first unread message

Stephen Douglas Scotti

unread,
May 13, 2021, 11:01:07 AM5/13/21
to Orthanc Users
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)

Stephen Douglas Scotti

unread,
May 13, 2021, 4:11:03 PM5/13/21
to Orthanc Users
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)

Stephen Douglas Scotti

unread,
May 15, 2021, 2:12:47 PM5/15/21
to Orthanc Users
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


Reply all
Reply to author
Forward
0 new messages