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