Custom Python Script to perform tools/find with pagination and sorting by params

Thanks to Sébastien, I am now able to play around with the Python scripts on OS X. Still a relative novice with Python.

I sort of combined some scripts on this page: https://book.orthanc-server.com/plugins/python.html

to perform a tools/find as well as a pagination and sorting by a tag. I haven’t fulling implemented a front end, but the REST call I think return enough data to implement display in a pagination manner on a front end since it return a subset of the search results using an offset and a limit, filtered by the tools/find params and sorted by the specified sort param. I have not fully tested, but it does seem to work from the command line. It only works for a Studies query currently since that is what I need, but it could probably be modified.

An example curl call would be:

curl http://localhost:8042/studies/page -d ‘{“Level”:“Study”,“Query”:{“PatientID”: “DEV0000001”},“Expand”:true,“Metadata”:{},“offset”:2,“limit”:2,“sortbytaggroup”:“MainDicomTags”,“sortparam”:“AccessionNumber”,“reverse”:0}’

See the Orthanc Book reference regarding the Metadata. That feature could actually just be removed, bypassed or disabled if that is not needed.

where the first part is like the tools/find and the MetaData is adapted from the script on the Wiki. offset, limit, sortbytaggroup, sortparam and reverse are additional parameters for the pagaination and sorting.

The script is below and there are quite a few comments, but I’ve also attached the script.

You would need to add:

“PythonScript” : “studiesfindpage.py”,
“PythonVerbose” : false,

to the config.json file with the path to the python script.

The script is below, but also attached.

If anyone is interested in working on that further or developing the front end I would be interested in collaborating, and if you can improve / modify the script you are welcome.

`
import json
import orthanc
import re

Example items to search and sort on, PatientMainDicomTags or MainDicomTags

#“PatientMainDicomTags”: {

“PatientBirthDate”: “YYYYMMDD”,

“PatientSex”: “M”,

“PatientID”: “DEV0000001”,

“PatientName”: “Patient^Test”

#},
#“Series”: [

“e46bfef4-2b166666-468cc957-4b942aa8-3a5c6ef8”

#],
#“ParentPatient”: “fa21ff2d-33e9b60a-daedf6a0-64d018da-682fd0a4”,
#“MainDicomTags”: {

“AccessionNumber”: “DEVACC00000006”,

“StudyDate”: “YYYYMMDD”,

“StudyDescription”: “StudyDescription”,

“InstitutionName”: “InstitutionName”,

“ReferringPhysicianName”: “Last^First^Middle^Suffix”,

“RequestingPhysician”: “Last^First^Middle^Suffix”,

“StudyTime”: “090425”,

“StudyID”: “DEVACC00000006”,

“StudyInstanceUID”: “2.16.840.1.114151.1052214956401694179114379854103077382390190829”

#}

Example CURL:

curl http://localhost:8042/studies/page -d ‘{“Level”:“Study”,“Query”:{“PatientID”: “DEV0000001”},“Expand”:true,“Metadata”:{}, “offset”:2,“limit”:2,“sortbytaggroup”:“MainDicomTags”,“sortparam”:“AccessionNumber”,“reverse”:0}’

Takes the standard tools find format, with added “Metadata”:{}, “offset”:2,“limit”:2,“sortbytaggroup”:“MainDicomTags”,“sortparam”:“AccessionNumber”,“reverse”:0 as additional params

offset and limit are for paging, offset from the beginning, and number of results

sortbytaggroup is PatientMainDicomTags or MainDicomTags

sortparam is the tag within the group to sort on.

reverse is the order for the sort, 0 or 1

Get the path in the REST API to the given resource that was returned

by a call to “/studies/page” as the new REST callback

#only accept queries on the Study, although others could be added.

def GetPath(resource):
if resource[‘Type’] == ‘Study’:
return ‘/studies/%s’ % resource[‘ID’]
else:
raise Exception(‘Can only Query Studies’)

main function

def FindWithMetadata(output, uri, **request):

The “/tools/find” route expects a POST method

if request[‘method’] != ‘POST’:
output.SendMethodNotAllowed(‘POST’)
else:

Parse the query provided by the user, and backup the “Expand” field

query = json.loads(request[‘body’])

if ‘Expand’ in query:
originalExpand = query[‘Expand’]
else:
originalExpand = False
offset = 0
if ‘offset’ in query:
offset = query[‘offset’]
limit = 0
if ‘limit’ in query:
limit = query[‘limit’]

The globals are used in the GetSortParam function for the taggroup, sortparam and sortorder

global param
param = query[‘sortparam’]
global taggroup
taggroup = query[‘sortbytaggroup’]
global reverse
reverse = query[‘reverse’]

Call the core “/tools/find” route

query[‘Expand’] = True
answers = orthanc.RestApiPost(‘/tools/find’, json.dumps(query))

Loop over the matching resources

filteredAnswers = []
for answer in json.loads(answers):
try:

Read the metadata that is associated with the resource

metadata = json.loads(orthanc.RestApiGet(‘%s/metadata?expand’ % GetPath(answer)))

Check whether the metadata matches the regular expressions

that were provided in the “Metadata” field of the user request

isMetadataMatch = True
if ‘Metadata’ in query:
for (name, pattern) in query[‘Metadata’].items():
if name in metadata:
value = metadata[name]
else:
value = ‘’

if re.match(pattern, value) == None:
isMetadataMatch = False
break

If all the metadata matches the provided regular

expressions, add the resource to the filtered answers

if isMetadataMatch:
if originalExpand:
answer[‘Metadata’] = metadata
filteredAnswers.append(answer)
else:
filteredAnswers.append(answer[‘ID’])
except:

The resource was deleted since the call to “/tools/find”

pass

Sort the studies according to the “StudyDate” DICOM tag

studies = sorted(filteredAnswers, key = GetSortParam, reverse=reverse)
count = len(studies)

Truncate the list of studies

if limit == 0:
studies = studies[offset : ]
else:
studies = studies[offset : offset + limit]

Return the truncated list of studies

studies.append({“count”:count})
studies.append({“limit”:limit})
studies.append({“offset”:offset})

Return the filtered answers in the JSON format

output.AnswerBuffer(json.dumps(studies, indent = 3), ‘application/json’)

#param is the tag to sortby
#taggroup is the taggroup for the param
#defined as globals in FindWithMetadata
def GetSortParam(study):
if param in study[taggroup]:
return study[taggroup][param]
else:
return ‘’

orthanc.RegisterRestCallback(‘/studies/page’, FindWithMetadata)
`

studiesfindpage.py (5.24 KB)

Adding an updated version of the script. There are comments throughout, so it should be mostly self-explanatory.

/sds

studiesfindpage.py (5.99 KB)

I modified yet a bit further so that the search params are at the beginning of the results in fixed locations. Makes it easier to extract those and the actual studies from the result. Script attached.

Sample results below:

[ { "count": 4 }, { "pagenumber": 2 }, { "offset": 2 }, { "limit": 2 }, { "results": 2 }, { "IsStable": true, "LastUpdate": "20200627T183247", "PatientMainDicomTags": { "PatientBirthDate": "19571116", "PatientSex": "M", "PatientID": "DEV0000001", "PatientName": "SCOTTI^STEPHEN^D^^" }, "Series": [ "e46bfef4-2b166666-468cc957-4b942aa8-3a5c6ef8" ], "ParentPatient": "fa21ff2d-33e9b60a-daedf6a0-64d018da-682fd0a4", "MainDicomTags": { "AccessionNumber": "DEVACC00000006", "StudyDate": "20190829", "StudyDescription": "XR HIP LT 1 VW", "InstitutionName": "MHealth CSC", "ReferringPhysicianName": "2VASKE^SHANNON^M^^", "RequestingPhysician": "2VASKE^SHANNON^M^^", "StudyTime": "090425", "StudyID": "UC4839619", "StudyInstanceUID": "2.16.840.1.114151.1052214956401694179114379854103077382390190829" }, "Type": "Study", "ID": "e8263ed6-56adfc56-a9951260-db8c21f3-c78d7103" }, { "IsStable": true, "LastUpdate": "20200627T183258", "PatientMainDicomTags": { "PatientBirthDate": "19571116", "PatientSex": "M", "PatientID": "DEV0000001", "PatientName": "SCOTTI^STEPHEN^D^^" }, "Series": [ "724c74e6-05cc6cc4-62c9c830-1bfda76c-3a435438", "ab78c679-ef037b0a-c267a1cc-dac2dfe7-456d1a3c", "8bccacc7-d470b154-446c649d-2f08a482-b18cd25d" ], "ParentPatient": "fa21ff2d-33e9b60a-daedf6a0-64d018da-682fd0a4", "MainDicomTags": { "AccessionNumber": "DEVACC00000007", "StudyDate": "20190829", "StudyDescription": "XR KNEE RT 3 VW", "InstitutionName": "MHealth CSC", "ReferringPhysicianName": "2VASKE^SHANNON^M^^", "RequestingPhysician": "2VASKE^SHANNON^M^^", "StudyTime": "083425", "StudyID": "UC4839464", "StudyInstanceUID": "2.16.840.1.114151.2411984198229487379168635092719400577270190829" }, "Type": "Study", "ID": "86e1a292-07ffb90a-50b2d752-3074f6bd-6895e89a" } ]

In PHP you can extract all of that using something like:

$numberofresults = $studiesarray[4]->results; $totalforquery = $studiesarray[0]->count; $pagenumber = $studiesarray[1]->pagenumber; $limit = $studiesarray[3]->limit; $offset = $studiesarray[2]->offset; $studiesarray = array_slice((array)$studiesarray, count($studiesarray)-$totalforquery-1, count($studiesarray));

With that information it is not too diffucult to implement pagination on the front-end, although you’ll have to save the query it the session or something so that you can just query again using the same parameters, but with a different pagenumber.

studiesfindpage.py (6 KB)