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)