Mercurial > hg > orthanc-book
diff Sphinx/source/plugins/python.rst @ 704:ba2403ebd4b7
moving python samples in separate files (3)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 11 Jun 2021 10:24:08 +0200 |
parents | a589668768d7 |
children | c62539d00251 |
line wrap: on
line diff
--- a/Sphinx/source/plugins/python.rst Fri Jun 11 10:07:12 2021 +0200 +++ b/Sphinx/source/plugins/python.rst Fri Jun 11 10:24:08 2021 +0200 @@ -351,35 +351,11 @@ Scheduling a task for periodic execution ........................................ -.. highlight:: python - The following Python script will periodically (every second) run the -function ``Hello()`` thanks to the ``threading`` module:: - - import orthanc - import threading - - TIMER = None +function ``Hello()`` thanks to the ``threading`` module: - def Hello(): - global TIMER - TIMER = None - orthanc.LogWarning("In Hello()") - # Do stuff... - TIMER = threading.Timer(1, Hello) # Re-schedule after 1 second - TIMER.start() - - def OnChange(changeType, level, resource): - if changeType == orthanc.ChangeType.ORTHANC_STARTED: - orthanc.LogWarning("Starting the scheduler") - Hello() - - elif changeType == orthanc.ChangeType.ORTHANC_STOPPED: - if TIMER != None: - orthanc.LogWarning("Stopping the scheduler") - TIMER.cancel() - - orthanc.RegisterOnChangeCallback(OnChange) +.. literalinclude:: python/periodic-execution.py + :language: python .. _python-metadata: @@ -399,83 +375,11 @@ tags. Filtering metadata requires a linear search over all the matching resources, which induces a cost in the performance. -.. highlight:: python - Nevertheless, here is a full sample Python script that overwrites the -``/tools/find`` route in order to give access to metadata:: - - import json - import orthanc - import re - - # Get the path in the REST API to the given resource that was returned - # by a call to "/tools/find" - def GetPath(resource): - if resource['Type'] == 'Patient': - return '/patients/%s' % resource['ID'] - elif resource['Type'] == 'Study': - return '/studies/%s' % resource['ID'] - elif resource['Type'] == 'Series': - return '/series/%s' % resource['ID'] - elif resource['Type'] == 'Instance': - return '/instances/%s' % resource['ID'] - else: - raise Exception('Unknown resource level') - - 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 +``/tools/find`` route in order to give access to metadata: - # 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 - - # Return the filtered answers in the JSON format - output.AnswerBuffer(json.dumps(filteredAnswers, indent = 3), 'application/json') - - orthanc.RegisterRestCallback('/tools/find', FindWithMetadata) +.. literalinclude:: python/filtering-metadata.py + :language: python **Warning:** In the sample above, the filtering of the metadata is @@ -498,54 +402,15 @@ Implementing basic paging ......................... -.. highlight:: python - As explained in the FAQ, the :ref:`Orthanc Explorer interface is low-level <improving-interface>`, and is not adapted for end-users. One common need is to implement paging of studies, which calls for server-side sorting of studies. This can be done using the following sample Python plugin that registers a new route -``/sort-studies`` in the REST API of Orthanc:: - - import json - import orthanc - - def GetStudyDate(study): - if 'StudyDate' in study['MainDicomTags']: - return study['MainDicomTags']['StudyDate'] - else: - return '' - - def SortStudiesByDate(output, uri, **request): - if request['method'] == 'GET': - # Retrieve all the studies - studies = json.loads(orthanc.RestApiGet('/studies?expand')) - - # Sort the studies according to the "StudyDate" DICOM tag - studies = sorted(studies, key = GetStudyDate) +``/sort-studies`` in the REST API of Orthanc: - # Read the limit/offset arguments provided by the user - offset = 0 - if 'offset' in request['get']: - offset = int(request['get']['offset']) - - limit = 0 - if 'limit' in request['get']: - limit = int(request['get']['limit']) - - # Truncate the list of studies - if limit == 0: - studies = studies[offset : ] - else: - studies = studies[offset : offset + limit] - - # Return the truncated list of studies - output.AnswerBuffer(json.dumps(studies), 'application/json') - else: - output.SendMethodNotAllowed('GET') - - orthanc.RegisterRestCallback('/sort-studies', SortStudiesByDate) - +.. literalinclude:: python/paging.py + :language: python .. highlight:: bash @@ -573,41 +438,12 @@ Creating a Microsoft Excel report ................................. -.. highlight:: python - As Orthanc plugins have access to any installed Python module, it is very easy to implement a server-side plugin that generates a report in -the Microsoft Excel ``.xls`` format. Here is a working example:: +the Microsoft Excel ``.xls`` format. Here is a working example: - import StringIO - import json - import orthanc - import xlwt - - def CreateExcelReport(output, uri, **request): - if request['method'] != 'GET' : - output.SendMethodNotAllowed('GET') - else: - # Create an Excel writer - excel = xlwt.Workbook() - sheet = excel.add_sheet('Studies') - - # Loop over the studies stored in Orthanc - row = 0 - studies = orthanc.RestApiGet('/studies?expand') - for study in json.loads(studies): - sheet.write(row, 0, study['PatientMainDicomTags'].get('PatientID')) - sheet.write(row, 1, study['PatientMainDicomTags'].get('PatientName')) - sheet.write(row, 2, study['MainDicomTags'].get('StudyDescription')) - row += 1 - - # Serialize the Excel workbook to a string, and return it to the caller - # https://stackoverflow.com/a/15649139/881731 - b = StringIO.StringIO() - excel.save(b) - output.AnswerBuffer(b.getvalue(), 'application/vnd.ms-excel') - - orthanc.RegisterRestCallback('/report.xls', CreateExcelReport) +.. literalinclude:: python/excel.py + :language: python If opening the ``http://localhost:8042/report.xls`` URI, this Python will generate a workbook with one sheet that contains the list of @@ -620,20 +456,12 @@ Forbid or allow access to REST resources (authorization, new in 3.0) .................................................................... -.. highlight:: python - The following Python script installs a callback that is triggered -whenever the HTTP server of Orthanc is accessed:: +whenever the HTTP server of Orthanc is accessed: - import orthanc - import pprint +.. literalinclude:: python/authorization-1.py + :language: python - def Filter(uri, **request): - print('User trying to access URI: %s' % uri) - pprint.pprint(request) - return True # False to forbid access - - orthanc.RegisterIncomingHttpRequestFilter(Filter) If access is not granted, the ``Filter`` callback must return ``False``. As a consequence, the HTTP status code would be set to @@ -646,47 +474,19 @@ callback that is available in :ref:`Lua scripts <lua-filter-rest>`. Thanks to Python, it is extremely easy to call remote Web services for -authorization. Here is an example using the ``requests`` library:: - - import json - import orthanc - import requests +authorization. Here is an example using the ``requests`` library: - def Filter(uri, **request): - body = { - 'uri' : uri, - 'headers' : request['headers'] - } - r = requests.post('http://localhost:8000/authorize', - data = json.dumps(body)) - return r.json() ['granted'] # Must be a Boolean - - orthanc.RegisterIncomingHttpRequestFilter(Filter) +.. literalinclude:: python/authorization-2.py + :language: python .. highlight:: javascript This filter could be used together with the following Web service implemented using `Node.js -<https://en.wikipedia.org/wiki/Node.js>`__:: - - const http = require('http'); +<https://en.wikipedia.org/wiki/Node.js>`__: - const requestListener = function(req, res) { - let body = ''; - req.on('data', function(chunk) { - body += chunk; - }); - req.on('end', function() { - console.log(JSON.parse(body)); - var answer = { - 'granted' : false // Forbid access - }; - res.writeHead(200); - res.end(JSON.stringify(answer)); - }); - } - - http.createServer(requestListener).listen(8000); +.. literalinclude:: python/authorization-node-service.js + :language: javascript .. _python_create_dicom: @@ -694,36 +494,12 @@ Creating DICOM instances (new in 3.2) ..................................... -.. highlight:: python - The following sample Python script will write on the disk a new DICOM instance including the traditional Lena sample image, and will decode -the single frame of this DICOM instance:: +the single frame of this DICOM instance: - import json - import orthanc - - def OnChange(changeType, level, resource): - if changeType == orthanc.ChangeType.ORTHANC_STARTED: - tags = { - 'SOPClassUID' : '1.2.840.10008.5.1.4.1.1.1', - 'PatientID' : 'HELLO', - 'PatientName' : 'WORLD', - } - - with open('Lena.png', 'rb') as f: - img = orthanc.UncompressImage(f.read(), orthanc.ImageFormat.PNG) - - s = orthanc.CreateDicom(json.dumps(tags), img, orthanc.CreateDicomFlags.GENERATE_IDENTIFIERS) - - with open('/tmp/sample.dcm', 'wb') as f: - f.write(s) - - dicom = orthanc.CreateDicomInstance(s) - frame = dicom.GetInstanceDecodedFrame(0) - print('Size of the frame: %dx%d' % (frame.GetImageWidth(), frame.GetImageHeight())) - - orthanc.RegisterOnChangeCallback(OnChange) +.. literalinclude:: python/create-dicom.py + :language: python .. _python_dicom_scp: @@ -731,37 +507,14 @@ Handling DICOM SCP requests (new in 3.2) ........................................ -.. highlight:: python - Starting with release 3.2 of the Python plugin, it is possible to replace the C-FIND SCP and C-MOVE SCP of Orthanc by a Python script. This feature can notably be used to create a custom DICOM -proxy. Here is a minimal example:: +proxy. Here is a minimal example: - import json - import orthanc - import pprint - - def OnFind(answers, query, issuerAet, calledAet): - print('Received incoming C-FIND request from %s:' % issuerAet) - - answer = {} - for i in range(query.GetFindQuerySize()): - print(' %s (%04x,%04x) = [%s]' % (query.GetFindQueryTagName(i), - query.GetFindQueryTagGroup(i), - query.GetFindQueryTagElement(i), - query.GetFindQueryValue(i))) - answer[query.GetFindQueryTagName(i)] = ('HELLO%d-%s' % (i, query.GetFindQueryValue(i))) - - answers.FindAddAnswer(orthanc.CreateDicom( - json.dumps(answer), None, orthanc.CreateDicomFlags.NONE)) - - def OnMove(**request): - orthanc.LogWarning('C-MOVE request to be handled in Python: %s' % - json.dumps(request, indent = 4, sort_keys = True)) - - orthanc.RegisterFindCallback(OnFind) - orthanc.RegisterMoveCallback(OnMove) +.. literalinclude:: python/dicom-find-move-scp.py + :language: python + .. highlight:: text @@ -954,8 +707,6 @@ Slave processes and the "orthanc" module ........................................ -.. highlight:: python - Very importantly, pay attention to the fact that **only the "master" Python interpreter has access to the Orthanc SDK**. The "slave" processes have no access to the ``orthanc`` module.