annotate Sphinx/source/plugins/python.rst @ 373:847996394688

Scheduling a task for periodic execution
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 05 Apr 2020 10:25:36 +0200
parents 181b02cea7ab
children f5bbdabb04d9
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
364
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
9 Overview
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
10 --------
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
11
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
12 This plugin can be used to write :ref:`Orthanc plugins
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
13 <creating-plugins>` using the `Python programming language
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
14 <https://en.wikipedia.org/wiki/Python_(programming_language)>`__
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
15 instead of the more complex C/C++ programming languages.
343
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
16
364
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
17 Python plugins have access to more features and a more consistent SDK
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
18 than :ref:`Lua scripts <lua>`. The Python API is automatically
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
19 generated from the `Orthanc plugin SDK in C
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
20 <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
21 using the `Clang <https://en.wikipedia.org/wiki/Clang>`__ compiler
364
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
22 front-end.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
23
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
24 As of initial release 1.0 of the plugin, the coverage of the C SDK is
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
25 about 75% (105 functions are automatically wrapped in Python out of a
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
26 total of 139 functions in the Orthanc SDK 1.5.7).
343
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
27
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
28 Licensing
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
29 ---------
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
30
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
31 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
32 of the `AGPL license
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
33 <https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License>`__.
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
34
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
35 This has an important consequence: If you distribute Orthanc to
364
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
36 clients together with one Python script, or if you put an Orthanc
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
37 server equipped with one Python script on a Web portal, you **must**
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
38 disclose the source code of your Python script to the Orthanc
354
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
39 community under the terms of the AGPL license.
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
40
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
41 We suggest you to put the source code of your Python scripts on the
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
42 dedicated `"OrthancContributed" repository on GitHub
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
43 <https://github.com/jodogne/OrthancContributed/tree/master/Plugins>`__,
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
44 and/or to send it to the `Orthanc Users
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
45 <https://groups.google.com/forum/#!forum/orthanc-users>`__ discussion
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 353
diff changeset
46 group.
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
47
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
48 Check out the :ref:`FAQ about licensing <licensing>` for more context.
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
49
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
50
364
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
51 Usage
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
52 -----
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
53
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
54 Docker
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
55 ......
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
56
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
57 .. highlight:: json
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
58
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
59 The most direct way of starting Orthanc together with the Python
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
60 plugin is through :ref:`Docker <docker>`. Let's create the file
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
61 ``/tmp/hello.py`` that contains the following basic Python script::
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
62
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
63 print('Hello world!')
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
64
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
65 .. highlight:: json
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
66
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
67 Let's also create the file ``/tmp/orthanc.json`` that contains the
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
68 following minimal :ref:`configuration for Orthanc <configuration>`::
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
69
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
70 {
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
71 "StorageDirectory" : "/var/lib/orthanc/db",
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
72 "RemoteAccessAllowed" : true,
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
73 "Plugins" : [
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
74 "/usr/local/share/orthanc/plugins"
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
75 ],
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
76 "PythonScript" : "/etc/orthanc/hello.py"
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
77 }
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
78
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
79 .. highlight:: bash
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
80
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
81 Given these two files, Orthanc can be started as follows::
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
82
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
83 $ docker run -p 4242:4242 -p 8042:8042 --rm \
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
84 -v /tmp/orthanc.json:/etc/orthanc/orthanc.json:ro \
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
85 -v /tmp/hello.py:/etc/orthanc/hello.py:ro \
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
86 jodogne/orthanc-python
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
87
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
88 .. highlight:: text
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
89
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
90 You'll see the following excerpt in the log, which indicates that the Python plugin is properly loaded::
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
91
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
92 W0331 15:48:12.990661 PluginsManager.cpp:269] Registering plugin 'python' (version mainline)
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
93 W0331 15:48:12.990691 PluginsManager.cpp:168] Python plugin is initializing
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
94 W0331 15:48:12.990743 PluginsManager.cpp:168] Using Python script "hello.py" from directory: /etc/orthanc
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
95 W0331 15:48:12.990819 PluginsManager.cpp:168] Program name: /usr/local/sbin/Orthanc
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
96 Hello world!
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
97
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
98
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
99 Compiling from source
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
100 .....................
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
101
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
102 .. highlight:: bash
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
103
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
104 The procedure to compile this plugin from source is similar to that
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
105 for the :ref:`core of Orthanc <compiling>`. The following commands
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
106 should work for most UNIX-like distribution (including GNU/Linux)::
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
107
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
108 $ mkdir Build
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
109 $ cd Build
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
110 $ cmake .. -DPYTHON_VERSION=3.7 -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
111 $ make
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
112
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
113 Before running CMake, make sure that the Python interpreter and its
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
114 associated development library are installed. On Ubuntu 18.04 LTS, you
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
115 would for instance install packages ``libpython3.7-dev`` and
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
116 ``python3.7``.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
117
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
118 The compilation will produce the shared library ``OrthancPython``,
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
119 that can be loaded by properly setting the ``Plugins``
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
120 :ref:`configuration option <configuration>` of Orthanc.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
121
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
122 **Warning:** The shared library is only compatible with the Python
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
123 interpreter whose version corresponds to the value of the
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
124 ``PYTHON_VERSION`` argument that was given to CMake.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
125
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
126
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
127 Microsoft Windows
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
128 .................
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
129
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
130 Pre-compiled binaries for Microsoft Windows `are also available
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
131 <https://www.orthanc-server.com/browse.php?path=/plugin-python>`__.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
132
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
133 Beware that one version of the Python plugin can only be run against
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
134 one version of the Python interpreter. This version is clearly
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
135 indicated in the name of the folder.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
136
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
137 As of release 1.0, the Orthanc project only provides pre-compiled
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
138 binaries for Microsoft Windows 32bit and Python 2.7. Even though this
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
139 version of Python is not supported anymore, it can still run on all
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
140 the versions of Microsoft Windows that have been released for more
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
141 than 10 years.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
142
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
143 .. highlight:: text
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
144
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
145 You are of course free to compile the plugin from sources if you need
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
146 a more recent version. You'll have to explicitly specify the path to
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
147 your Python installation while invoking CMake. For instance::
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
148
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
149 C:\orthanc-python\Build> cmake .. -DPYTHON_VERSION=2.7 -DPYTHON_WINDOWS_ROOT=C:/Python27 \
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
150 -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 15 2017"
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
151
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
152
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
153 Configuration options
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
154 ---------------------
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
155
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
156 The only two configuration options that are available for this plugin
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
157 are the following:
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
158
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
159 * ``PythonScript`` indicates where the Python script is located. If
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
160 this configuration option is not provided, the Python plugin is not
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
161 started.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
162
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
163 * ``PythonVerbose`` is a Boolean value to make the Python interpreter
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
164 verbose.
234de55ed125 usage of the python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 354
diff changeset
165
343
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
166
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
167 Samples
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
168 -------
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
169
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
170 Extending the REST API
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
171 ......................
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
172
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
173 .. highlight:: python
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
174
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
175 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
176 REST API::
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
177
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
178 import orthanc
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
179 import pprint
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
180
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
181 def OnRest(output, uri, **request):
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
182 pprint.pprint(request)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
183 print('Accessing uri: %s' % uri)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
184 output.AnswerBuffer('ok\n', 'text/plain')
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
185
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
186 orthanc.RegisterRestCallback('/(to)(t)o', OnRest)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
187 orthanc.RegisterRestCallback('/tata', OnRest)
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
188
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
189 .. highlight:: json
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
190
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
191 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
192 (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
193
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
194 {
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
195 "Plugins" : [ "." ],
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
196 "PythonScript" : "rest.py",
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
197 "PythonVerbose" : false
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
198 }
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
199
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
200 .. highlight:: bash
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
201
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
202 The route can then be accessed as::
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
203
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
204 $ curl http://localhost:8042/toto
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
205 ok
fff45618262d creating the documentation of the Python plugin
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
diff changeset
206
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
207
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
208 Listening to changes
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
209 ....................
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
210
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
211 .. highlight:: python
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
212
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
213 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
214
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
215 import orthanc
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
216
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
217 def OnChange(changeType, level, resource):
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
218 if changeType == orthanc.ChangeType.ORTHANC_STARTED:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
219 print('Started')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
220
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
221 with open('/tmp/sample.dcm', 'rb') as f:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
222 orthanc.RestApiPost('/instances', f.read())
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
223
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
224 elif changeType == orthanc.ChangeType.ORTHANC_STOPPED:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
225 print('Stopped')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
226
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
227 elif changeType == orthanc.ChangeType.NEW_INSTANCE:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
228 print('A new instance was uploaded: %s' % resource)
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
229
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
230 orthanc.RegisterOnChangeCallback(OnChange)
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
231
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
232
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
233 Accessing the content of a new instance
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
234 .......................................
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
235
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
236 .. highlight:: python
345
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
237
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
238 ::
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
239
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
240 import orthanc
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
241 import json
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
242 import pprint
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
243
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
244 def OnStoredInstance(dicom, instanceId):
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
245 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
246 instanceId, dicom.GetInstanceSize(),
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
247 dicom.GetInstanceMetadata('TransferSyntax'),
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
248 dicom.GetInstanceMetadata('SopClassUid')))
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
249
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
250 # Print the origin information
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
251 if dicom.GetInstanceOrigin() == orthanc.InstanceOrigin.DICOM_PROTOCOL:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
252 print('This instance was received through the DICOM protocol')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
253 elif dicom.GetInstanceOrigin() == orthanc.InstanceOrigin.REST_API:
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
254 print('This instance was received through the REST API')
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
255
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
256 # Print the DICOM tags
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
257 pprint.pprint(json.loads(dicom.GetInstanceSimplifiedJson()))
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
258
f81b533a0fd0 python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 343
diff changeset
259 orthanc.RegisterOnStoredInstanceCallback(OnStoredInstance)
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
260
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
261
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
262 Calling pydicom
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
263 ...............
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
264
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
265 .. highlight:: python
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
266
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
267 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
268 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
269 Orthanc, using `pydicom <https://pydicom.github.io/>`__::
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
270
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
271 import io
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
272 import orthanc
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
273 import pydicom
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
274
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
275 def DecodeInstance(output, uri, **request):
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
276 if request['method'] == 'GET':
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
277 # Retrieve the instance ID from the regular expression (*)
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
278 instanceId = request['groups'][0]
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
279 # Get the content of the DICOM file
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
280 f = orthanc.GetDicomForInstance(instanceId)
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
281 # Parse it using pydicom
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
282 dicom = pydicom.dcmread(io.BytesIO(f))
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
283 # Return a string representation the dataset to the caller
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
284 output.AnswerBuffer(str(dicom), 'text/plain')
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
285 else:
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
286 output.SendMethodNotAllowed('GET')
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
287
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
288 orthanc.RegisterRestCallback('/pydicom/(.*)', DecodeInstance) # (*)
346
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
289
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
290 .. highlight:: bash
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
291
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
292 This can be called as follows::
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
293
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
294 $ curl http://localhost:8042/pydicom/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5
bdf8757449e3 more python samples
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 345
diff changeset
295
347
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
296
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
297 Auto-routing studies
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
298 ....................
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
299
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
300 .. highlight:: python
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
301
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
302 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
303 <lua-callbacks>` to a modality named ``samples`` (as declared in the
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
304 ``DicomModalities`` configuration option)::
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
305
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
306 import orthanc
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
307
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
308 def OnChange(changeType, level, resourceId):
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
309 if changeType == orthanc.ChangeType.STABLE_STUDY:
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
310 print('Stable study: %s' % resourceId)
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
311 orthanc.RestApiPost('/modalities/sample/store', resourceId)
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
312
04fae9d4b65f auto-routing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 346
diff changeset
313 orthanc.RegisterOnChangeCallback(OnChange)
348
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
314
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
315
352
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
316 Rendering a thumbnail using PIL/Pillow
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
317 ......................................
348
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
318
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
319 .. highlight:: python
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
320
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
321 ::
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
322
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
323 from PIL import Image
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
324 import io
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
325 import orthanc
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
326
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
327 def DecodeInstance(output, uri, **request):
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
328 if request['method'] == 'GET':
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
329 # Retrieve the instance ID from the regular expression (*)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
330 instanceId = request['groups'][0]
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
331
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
332 # Render the instance, then open it in Python using PIL/Pillow
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
333 png = orthanc.RestApiGet('/instances/%s/rendered' % instanceId)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
334 image = Image.open(io.BytesIO(png))
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
335
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
336 # Downsize the image as a 64x64 thumbnail
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
337 image.thumbnail((64, 64), Image.ANTIALIAS)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
338
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
339 # 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
340 jpeg = io.BytesIO()
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
341 image.save(jpeg, format = "JPEG", quality = 80)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
342 jpeg.seek(0)
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
343 output.AnswerBuffer(jpeg.read(), 'text/plain')
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
344
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
345 else:
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
346 output.SendMethodNotAllowed('GET')
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
347
d8359cecdc89 pillow sample
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 347
diff changeset
348 orthanc.RegisterRestCallback('/pydicom/(.*)', DecodeInstance) # (*)
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
349
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
350
369
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
351 .. _python-introspection:
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
352
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
353 Inspecting the available API
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
354 ............................
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
355
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
356 .. highlight:: python
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
357
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
358 Thanks to Python's introspection primitives, it is possible to inspect
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
359 the API of the ``orthanc`` module in order to dump all the available
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
360 features::
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
361
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
362 import inspect
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
363 import numbers
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
364 import orthanc
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
365
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
366 # Loop over the members of the "orthanc" module
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
367 for (name, obj) in inspect.getmembers(orthanc):
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
368 if inspect.isroutine(obj):
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
369 print('Function %s():\n Documentation: %s\n' % (name, inspect.getdoc(obj)))
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
370
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
371 elif inspect.isclass(obj):
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
372 print('Class %s:\n Documentation: %s' % (name, inspect.getdoc(obj)))
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
373
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
374 # Loop over the members of the class
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
375 for (subname, subobj) in inspect.getmembers(obj):
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
376 if isinstance(subobj, numbers.Number):
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
377 print(' - Enumeration value %s: %s' % (subname, subobj))
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
378 elif (not subname.startswith('_') and
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
379 inspect.ismethoddescriptor(subobj)):
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
380 print(' - Method %s(): %s' % (subname, inspect.getdoc(subobj)))
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
381 print('')
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
382
373
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
383
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
384 .. _python-scheduler:
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
385
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
386 Scheduling a task for periodic execution
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
387 ........................................
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
388
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
389 .. highlight:: python
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
390
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
391 The following Python script will periodically (every second) run the
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
392 function ``Hello()`` thanks to the ``threading`` module::
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
393
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
394 import orthanc
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
395 import threading
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
396
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
397 TIMER = None
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
398
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
399 def Hello():
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
400 global TIMER
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
401 TIMER = None
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
402 orthanc.LogWarning("In Hello()")
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
403 # Do stuff...
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
404 TIMER = threading.Timer(1, Hello) # Re-schedule after 1 second
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
405 TIMER.start()
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
406
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
407 def OnChange(changeType, level, resource):
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
408 if changeType == orthanc.ChangeType.ORTHANC_STARTED:
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
409 orthanc.LogWarning("Starting the scheduler")
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
410 Hello()
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
411
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
412 elif changeType == orthanc.ChangeType.ORTHANC_STOPPED:
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
413 if TIMER != None:
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
414 orthanc.LogWarning("Stopping the scheduler")
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
415 TIMER.cancel()
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
416
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
417 orthanc.RegisterOnChangeCallback(OnChange)
847996394688 Scheduling a task for periodic execution
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 369
diff changeset
418
369
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
419
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
420
181b02cea7ab inspecting Python API
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 364
diff changeset
421
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
422 Performance and concurrency
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
423 ---------------------------
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
424
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
425 .. highlight:: python
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
426
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
427 Let us consider the following sample Python script that makes a
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
428 CPU-intensive computation on a REST callback::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
429
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
430 import math
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
431 import orthanc
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
432 import time
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
433
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
434 # CPU-intensive computation taking about 4 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
435 def SlowComputation():
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
436 start = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
437 for i in range(1000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
438 for j in range(30000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
439 math.sqrt(float(j))
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
440 end = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
441 duration = (end - start)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
442 return 'computation done in %.03f seconds\n' % duration
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
443
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
444 def OnRest(output, uri, **request):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
445 answer = SlowComputation()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
446 output.AnswerBuffer(answer, 'text/plain')
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
447
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
448 orthanc.RegisterRestCallback('/computation', OnRest)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
449
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
450
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
451 .. highlight:: text
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
452
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
453 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
454 needed to compute 30 million times a squared root on your CPU::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
455
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
456 $ curl http://localhost:8042/computation
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
457 computation done in 4.208 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
458
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
459 Now, let us call this route three times concurrently (we use bash)::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
460
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
461 $ (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
462 computation done in 11.262 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
463 computation done in 12.457 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
464 computation done in 13.360 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
465
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
466 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
467 computations were not distributed across the available CPU cores.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
468 This might seem surprising, as Orthanc is a threaded server (in
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
469 Orthanc, a pool of C++ threads serves concurrent requests).
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
470
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
471 The explanation is that the Python interpreter (`CPython
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
472 <https://en.wikipedia.org/wiki/CPython>`__ actually) is built on the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
473 top of a so-called `Global Interpreter Lock (GIL)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
474 <https://en.wikipedia.org/wiki/Global_interpreter_lock>`__. The GIL is
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
475 basically a mutex that protects all the calls to the Python
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
476 interpreter. If multiple C++ threads from Orthanc call a Python
353
0122c668f4ec python licensing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 352
diff changeset
477 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
478 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
479 Orthanc is not affected by the GIL.
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
480
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
481 .. highlight:: python
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
482
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
483 The solution is to use the `multiprocessing primitives
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
484 <https://docs.python.org/3/library/multiprocessing.html>`__ of Python.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
485 The "master" Python interpreter that is initially started by the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
486 Orthanc plugin, can start several `children processes
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
487 <https://en.wikipedia.org/wiki/Process_(computing)>`__, each of these
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
488 processes running a separate Python interpreter. This allows to
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
489 offload intensive computations from the "master" Python interpreter of
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
490 Orthanc onto those "slave" interpreters. The ``multiprocessing``
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
491 library is actually quite straightforward to use::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
492
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
493 import math
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
494 import multiprocessing
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
495 import orthanc
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
496 import signal
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
497 import time
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
498
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
499 # CPU-intensive computation taking about 4 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
500 # (same code as above)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
501 def SlowComputation():
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
502 start = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
503 for i in range(1000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
504 for j in range(30000):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
505 math.sqrt(float(j))
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
506 end = time.time()
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
507 duration = (end - start)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
508 return 'computation done in %.03f seconds\n' % duration
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
509
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
510 # Ignore CTRL+C in the slave processes
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
511 def Initializer():
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
512 signal.signal(signal.SIGINT, signal.SIG_IGN)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
513
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
514 # Create a pool of 4 slave Python interpreters
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
515 POOL = multiprocessing.Pool(4, initializer = Initializer)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
516
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
517 def OnRest(output, uri, **request):
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
518 # Offload the call to "SlowComputation" onto one slave process.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
519 # The GIL is unlocked until the slave sends its answer back.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
520 answer = POOL.apply(SlowComputation)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
521 output.AnswerBuffer(answer, 'text/plain')
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
522
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
523 orthanc.RegisterRestCallback('/computation', OnRest)
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
524
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
525 .. highlight:: text
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
526
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
527 Here is now the result of calling this route three times concurrently::
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
528
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
529 $ (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
530 computation done in 4.211 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
531 computation done in 4.215 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
532 computation done in 4.225 seconds
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
533
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
534 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
535 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
536 as for one isolated request).
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
537
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
538 Note also how the ``multiprocessing`` library allows to make a fine
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
539 control over the computational resources that are available to the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
540 Python script: The number of "slave" interpreters can be easily
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
541 changed in the constructor of the ``multiprocessing.Pool`` object, and
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
542 are fully independent of the threads used by the Orthanc server.
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
543
352
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
544 .. highlight:: python
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
545
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
546 Very importantly, pay attention to the fact that only the "master"
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
547 Python interpreter has access to the Orthanc SDK. For instance, here
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
548 is how you would parse a DICOM file in a slave process::
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
549
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
550 import pydicom
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
551 import io
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
552
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
553 def OffloadedDicomParsing(dicom):
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
554 # No access to the "orthanc" library here, as we are in the slave process
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
555 dataset = pydicom.dcmread(io.BytesIO(dicom))
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
556 return str(dataset)
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
557
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
558 def OnRest(output, uri, **request):
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
559 # The call to "orthanc.RestApiGet()" is only possible in the master process
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
560 dicom = orthanc.RestApiGet('/instances/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5/file')
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
561 answer = POOL.apply(OffloadedDicomParsing, args = (dicom, ))
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
562 output.AnswerBuffer(answer, 'text/plain')
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
563
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
564 Communication primitives such as ``multiprocessing.Queue`` are
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
565 available to exchange messages from the "slave" Python interpreters to
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
566 the "master" Python interpreter if further calls to the Orthanc SDK
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
567 are required.
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 351
diff changeset
568
351
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
569 Obviously, an in-depth discussion about the ``multiprocessing``
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
570 library is out of the scope of this document. There are many
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
571 references available on Internet. Also, note that ``multithreading``
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
572 is not useful here, as Python multithreading is also limited by the
e2863083fa30 multiprocessing
Sebastien Jodogne <s.jodogne@gmail.com>
parents: 350
diff changeset
573 GIL, and is more targeted at dealing with costly I/O operations.