annotate Sphinx/source/plugins/python.rst @ 354:1ba75bac55fd

cont
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 28 Mar 2020 14:48:39 +0100
parents 0122c668f4ec
children 234de55ed125
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
343
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
1 .. _python-plugin:
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
2
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
3
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
4 Python plugin for Orthanc
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
5 =========================
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
6
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
7 .. contents::
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
8
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
9 Work-in-progress.
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
10
349
60080d792f25 rephrasing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 348
diff changeset
11 Being a plugin, the Python API has access to more features than
350
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 349
diff changeset
12 :ref:`Lua scripts <lua>`.
349
60080d792f25 rephrasing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 348
diff changeset
13
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
14 The Python API is automatically generated from the `Orthanc plugin SDK
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
15 in C
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
16 <https://hg.orthanc-server.com/orthanc/file/Orthanc-1.5.7/Plugins/Include/orthanc/OrthancCPlugin.h>`__
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
17 using the `Clang <https://en.wikipedia.org/wiki/Clang>`__ compiler
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
18 front-end. The coverage of the C SDK is about 75% (105 functions are
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
19 automatically wrapped in Python out of a total of 139 functions in C).
343
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
20
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
21 Licensing
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
22 ---------
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
23
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
24 Pay attention to the fact that this plugin is licensed under the terms
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
25 of the `AGPL license
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
26 <https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License>`__.
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
27
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
28 This has an important consequence: If you distribute Orthanc to
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
29 clients together with one Python script, or if you make an Orthanc
354
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
30 server equipped with one Python script available on a Web portal, you
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
31 **must** disclose the source code of your Python script to the Orthanc
354
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
32 community under the terms of the AGPL license.
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
33
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
34 We suggest you to put the source code of your Python scripts on the
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
35 dedicated `"OrthancContributed" repository on GitHub
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
36 <https://github.com/jodogne/OrthancContributed/tree/master/Plugins>`__,
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
37 and/or to send it to the `Orthanc Users
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
38 <https://groups.google.com/forum/#!forum/orthanc-users>`__ discussion
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
39 group.
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
40
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
41 Check out the :ref:`FAQ about licensing <licensing>` for more context.
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
42
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
43
343
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
44
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
45 Samples
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
46 -------
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
47
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
48 Extending the REST API
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
49 ......................
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
50
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
51 .. highlight:: python
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
52
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
53 Here is a basic Python script that registers two new routes in the
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
54 REST API::
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
55
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
56 import orthanc
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
57 import pprint
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
58
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
59 def OnRest(output, uri, **request):
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
60 pprint.pprint(request)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
61 print('Accessing uri: %s' % uri)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
62 output.AnswerBuffer('ok\n', 'text/plain')
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
63
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
64 orthanc.RegisterRestCallback('/(to)(t)o', OnRest)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
65 orthanc.RegisterRestCallback('/tata', OnRest)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
66
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
67 .. highlight:: json
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
68
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
69 Here is the associated minimal configuration file for Orthanc
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
70 (provided the Python script is saved as ``rest.py``)::
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
71
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
72 {
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
73 "Plugins" : [ "." ],
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
74 "PythonScript" : "rest.py",
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
75 "PythonVerbose" : false
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
76 }
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
77
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
78 .. highlight:: bash
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
79
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
80 The route can then be accessed as::
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
81
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
82 $ curl http://localhost:8042/toto
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
83 ok
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
84
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
85
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
86 Listening to changes
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
87 ....................
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
88
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
89 .. highlight:: python
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
90
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
91 This sample uploads a DICOM file as soon as Orthanc is started::
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
92
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
93 import orthanc
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
94
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
95 def OnChange(changeType, level, resource):
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
96 if changeType == orthanc.ChangeType.ORTHANC_STARTED:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
97 print('Started')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
98
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
99 with open('/tmp/sample.dcm', 'rb') as f:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
100 orthanc.RestApiPost('/instances', f.read())
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
101
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
102 elif changeType == orthanc.ChangeType.ORTHANC_STOPPED:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
103 print('Stopped')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
104
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
105 elif changeType == orthanc.ChangeType.NEW_INSTANCE:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
106 print('A new instance was uploaded: %s' % resource)
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
107
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
108 orthanc.RegisterOnChangeCallback(OnChange)
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
109
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
110
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
111 Accessing the content of a new instance
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
112 .......................................
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
113
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
114 .. highlight:: python
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
115
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
116 ::
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
117
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
118 import orthanc
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
119 import json
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
120 import pprint
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
121
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
122 def OnStoredInstance(dicom, instanceId):
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
123 print('Received instance %s of size %d (transfer syntax %s, SOP class UID %s)' % (
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
124 instanceId, dicom.GetInstanceSize(),
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
125 dicom.GetInstanceMetadata('TransferSyntax'),
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
126 dicom.GetInstanceMetadata('SopClassUid')))
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
127
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
128 # Print the origin information
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
129 if dicom.GetInstanceOrigin() == orthanc.InstanceOrigin.DICOM_PROTOCOL:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
130 print('This instance was received through the DICOM protocol')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
131 elif dicom.GetInstanceOrigin() == orthanc.InstanceOrigin.REST_API:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
132 print('This instance was received through the REST API')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
133
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
134 # Print the DICOM tags
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
135 pprint.pprint(json.loads(dicom.GetInstanceSimplifiedJson()))
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
136
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
137 orthanc.RegisterOnStoredInstanceCallback(OnStoredInstance)
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
138
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
139
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
140 Calling pydicom
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
141 ...............
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
142
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
143 .. highlight:: python
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
144
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
145 Here is a sample Python plugin that registers a REST callback to dump
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
146 the content of the dataset of one given DICOM instance stored in
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
147 Orthanc, using `pydicom <https://pydicom.github.io/>`__::
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
148
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
149 import io
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
150 import orthanc
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
151 import pydicom
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
152
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
153 def DecodeInstance(output, uri, **request):
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
154 if request['method'] == 'GET':
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
155 # Retrieve the instance ID from the regular expression (*)
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
156 instanceId = request['groups'][0]
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
157 # Get the content of the DICOM file
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
158 f = orthanc.GetDicomForInstance(instanceId)
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
159 # Parse it using pydicom
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
160 dicom = pydicom.dcmread(io.BytesIO(f))
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
161 # Return a string representation the dataset to the caller
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
162 output.AnswerBuffer(str(dicom), 'text/plain')
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
163 else:
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
164 output.SendMethodNotAllowed('GET')
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
165
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
166 orthanc.RegisterRestCallback('/pydicom/(.*)', DecodeInstance) # (*)
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
167
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
168 .. highlight:: bash
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
169
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
170 This can be called as follows::
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
171
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
172 $ curl http://localhost:8042/pydicom/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
173
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
174
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
175 Auto-routing studies
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
176 ....................
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
177
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
178 .. highlight:: python
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
179
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
180 Here is a sample Python plugin that routes any :ref:`stable study
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
181 <lua-callbacks>` to a modality named ``samples`` (as declared in the
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
182 ``DicomModalities`` configuration option)::
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
183
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
184 import orthanc
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
185
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
186 def OnChange(changeType, level, resourceId):
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
187 if changeType == orthanc.ChangeType.STABLE_STUDY:
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
188 print('Stable study: %s' % resourceId)
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
189 orthanc.RestApiPost('/modalities/sample/store', resourceId)
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
190
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
191 orthanc.RegisterOnChangeCallback(OnChange)
348
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
192
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
193
352
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
194 Rendering a thumbnail using PIL/Pillow
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
195 ......................................
348
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
196
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
197 .. highlight:: python
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
198
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
199 ::
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
200
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
201 from PIL import Image
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
202 import io
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
203 import orthanc
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
204
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
205 def DecodeInstance(output, uri, **request):
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
206 if request['method'] == 'GET':
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
207 # Retrieve the instance ID from the regular expression (*)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
208 instanceId = request['groups'][0]
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
209
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
210 # Render the instance, then open it in Python using PIL/Pillow
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
211 png = orthanc.RestApiGet('/instances/%s/rendered' % instanceId)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
212 image = Image.open(io.BytesIO(png))
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
213
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
214 # Downsize the image as a 64x64 thumbnail
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
215 image.thumbnail((64, 64), Image.ANTIALIAS)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
216
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
217 # Save the thumbnail as JPEG, then send the buffer to the caller
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
218 jpeg = io.BytesIO()
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
219 image.save(jpeg, format = "JPEG", quality = 80)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
220 jpeg.seek(0)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
221 output.AnswerBuffer(jpeg.read(), 'text/plain')
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
222
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
223 else:
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
224 output.SendMethodNotAllowed('GET')
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
225
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
226 orthanc.RegisterRestCallback('/pydicom/(.*)', DecodeInstance) # (*)
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
227
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
228
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
229 Performance and concurrency
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
230 ---------------------------
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
231
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
232 .. highlight:: python
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
233
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
234 Let us consider the following sample Python script that makes a
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
235 CPU-intensive computation on a REST callback::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
236
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
237 import math
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
238 import orthanc
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
239 import time
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
240
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
241 # CPU-intensive computation taking about 4 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
242 def SlowComputation():
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
243 start = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
244 for i in range(1000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
245 for j in range(30000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
246 math.sqrt(float(j))
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
247 end = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
248 duration = (end - start)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
249 return 'computation done in %.03f seconds\n' % duration
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
250
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
251 def OnRest(output, uri, **request):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
252 answer = SlowComputation()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
253 output.AnswerBuffer(answer, 'text/plain')
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
254
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
255 orthanc.RegisterRestCallback('/computation', OnRest)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
256
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
257
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
258 .. highlight:: text
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
259
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
260 Calling this REST route from the command-line returns the time that is
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
261 needed to compute 30 million times a squared root on your CPU::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
262
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
263 $ curl http://localhost:8042/computation
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
264 computation done in 4.208 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
265
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
266 Now, let us call this route three times concurrently (we use bash)::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
267
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
268 $ (curl http://localhost:8042/computation & curl http://localhost:8042/computation & curl http://localhost:8042/computation )
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
269 computation done in 11.262 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
270 computation done in 12.457 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
271 computation done in 13.360 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
272
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
273 As can be seen, the computation time has tripled. This means that the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
274 computations were not distributed across the available CPU cores.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
275 This might seem surprising, as Orthanc is a threaded server (in
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
276 Orthanc, a pool of C++ threads serves concurrent requests).
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
277
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
278 The explanation is that the Python interpreter (`CPython
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
279 <https://en.wikipedia.org/wiki/CPython>`__ actually) is built on the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
280 top of a so-called `Global Interpreter Lock (GIL)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
281 <https://en.wikipedia.org/wiki/Global_interpreter_lock>`__. The GIL is
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
282 basically a mutex that protects all the calls to the Python
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
283 interpreter. If multiple C++ threads from Orthanc call a Python
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
284 callback, only one can proceed at any given time. Note however that
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
285 the GIL only applies to the Python script: The baseline REST API of
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
286 Orthanc is not affected by the GIL.
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
287
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
288 .. highlight:: python
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
289
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
290 The solution is to use the `multiprocessing primitives
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
291 <https://docs.python.org/3/library/multiprocessing.html>`__ of Python.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
292 The "master" Python interpreter that is initially started by the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
293 Orthanc plugin, can start several `children processes
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
294 <https://en.wikipedia.org/wiki/Process_(computing)>`__, each of these
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
295 processes running a separate Python interpreter. This allows to
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
296 offload intensive computations from the "master" Python interpreter of
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
297 Orthanc onto those "slave" interpreters. The ``multiprocessing``
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
298 library is actually quite straightforward to use::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
299
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
300 import math
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
301 import multiprocessing
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
302 import orthanc
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
303 import signal
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
304 import time
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
305
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
306 # CPU-intensive computation taking about 4 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
307 # (same code as above)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
308 def SlowComputation():
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
309 start = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
310 for i in range(1000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
311 for j in range(30000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
312 math.sqrt(float(j))
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
313 end = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
314 duration = (end - start)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
315 return 'computation done in %.03f seconds\n' % duration
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
316
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
317 # Ignore CTRL+C in the slave processes
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
318 def Initializer():
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
319 signal.signal(signal.SIGINT, signal.SIG_IGN)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
320
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
321 # Create a pool of 4 slave Python interpreters
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
322 POOL = multiprocessing.Pool(4, initializer = Initializer)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
323
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
324 def OnRest(output, uri, **request):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
325 # Offload the call to "SlowComputation" onto one slave process.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
326 # The GIL is unlocked until the slave sends its answer back.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
327 answer = POOL.apply(SlowComputation)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
328 output.AnswerBuffer(answer, 'text/plain')
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
329
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
330 orthanc.RegisterRestCallback('/computation', OnRest)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
331
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
332 .. highlight:: text
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
333
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
334 Here is now the result of calling this route three times concurrently::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
335
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
336 $ (curl http://localhost:8042/computation & curl http://localhost:8042/computation & curl http://localhost:8042/computation )
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
337 computation done in 4.211 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
338 computation done in 4.215 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
339 computation done in 4.225 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
340
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
341 As can be seen, the calls to the Python computation now fully run in
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
342 parallel (the time is cut down from 12 seconds to 4 seconds, the same
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
343 as for one isolated request).
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
344
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
345 Note also how the ``multiprocessing`` library allows to make a fine
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
346 control over the computational resources that are available to the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
347 Python script: The number of "slave" interpreters can be easily
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
348 changed in the constructor of the ``multiprocessing.Pool`` object, and
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
349 are fully independent of the threads used by the Orthanc server.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
350
352
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
351 .. highlight:: python
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
352
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
353 Very importantly, pay attention to the fact that only the "master"
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
354 Python interpreter has access to the Orthanc SDK. For instance, here
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
355 is how you would parse a DICOM file in a slave process::
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
356
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
357 import pydicom
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
358 import io
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
359
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
360 def OffloadedDicomParsing(dicom):
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
361 # No access to the "orthanc" library here, as we are in the slave process
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
362 dataset = pydicom.dcmread(io.BytesIO(dicom))
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
363 return str(dataset)
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
364
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
365 def OnRest(output, uri, **request):
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
366 # The call to "orthanc.RestApiGet()" is only possible in the master process
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
367 dicom = orthanc.RestApiGet('/instances/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5/file')
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
368 answer = POOL.apply(OffloadedDicomParsing, args = (dicom, ))
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
369 output.AnswerBuffer(answer, 'text/plain')
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
370
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
371 Communication primitives such as ``multiprocessing.Queue`` are
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
372 available to exchange messages from the "slave" Python interpreters to
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
373 the "master" Python interpreter if further calls to the Orthanc SDK
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
374 are required.
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
375
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
376 Obviously, an in-depth discussion about the ``multiprocessing``
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
377 library is out of the scope of this document. There are many
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
378 references available on Internet. Also, note that ``multithreading``
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
379 is not useful here, as Python multithreading is also limited by the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
380 GIL, and is more targeted at dealing with costly I/O operations.