Mercurial > hg > orthanc-book
comparison Sphinx/source/plugins/python.rst @ 702:6e02cd89eb6a
moving python samples in separate files
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 11 Jun 2021 09:38:15 +0200 |
parents | f093160dd7f4 |
children | a589668768d7 |
comparison
equal
deleted
inserted
replaced
701:f093160dd7f4 | 702:6e02cd89eb6a |
---|---|
1016 <https://en.wikipedia.org/wiki/Fork_(system_call)>`__. | 1016 <https://en.wikipedia.org/wiki/Fork_(system_call)>`__. |
1017 | 1017 |
1018 Using slave processes | 1018 Using slave processes |
1019 ..................... | 1019 ..................... |
1020 | 1020 |
1021 .. highlight:: python | |
1022 | |
1023 Let us consider the following sample Python script that makes a | 1021 Let us consider the following sample Python script that makes a |
1024 CPU-intensive computation on a REST callback:: | 1022 CPU-intensive computation on a REST callback: |
1025 | 1023 |
1026 import math | 1024 .. literalinclude:: python/multiprocessing-1.py |
1027 import orthanc | 1025 :language: python |
1028 import time | |
1029 | |
1030 # CPU-intensive computation taking about 4 seconds | |
1031 def SlowComputation(): | |
1032 start = time.time() | |
1033 for i in range(1000): | |
1034 for j in range(30000): | |
1035 math.sqrt(float(j)) | |
1036 end = time.time() | |
1037 duration = (end - start) | |
1038 return 'computation done in %.03f seconds\n' % duration | |
1039 | |
1040 def OnRest(output, uri, **request): | |
1041 answer = SlowComputation() | |
1042 output.AnswerBuffer(answer, 'text/plain') | |
1043 | |
1044 orthanc.RegisterRestCallback('/computation', OnRest) | |
1045 | |
1046 | 1026 |
1047 .. highlight:: text | 1027 .. highlight:: text |
1048 | 1028 |
1049 Calling this REST route from the command-line returns the time that is | 1029 Calling this REST route from the command-line returns the time that is |
1050 needed to compute 30 million times a squared root on your CPU:: | 1030 needed to compute 30 million times a squared root on your CPU:: |
1072 interpreter. If multiple C++ threads from Orthanc call a Python | 1052 interpreter. If multiple C++ threads from Orthanc call a Python |
1073 callback, only one can proceed at any given time. Note however that | 1053 callback, only one can proceed at any given time. Note however that |
1074 the GIL only applies to the Python script: The baseline REST API of | 1054 the GIL only applies to the Python script: The baseline REST API of |
1075 Orthanc is not affected by the GIL. | 1055 Orthanc is not affected by the GIL. |
1076 | 1056 |
1077 .. highlight:: python | |
1078 | |
1079 The solution is to use the `multiprocessing primitives | 1057 The solution is to use the `multiprocessing primitives |
1080 <https://docs.python.org/3/library/multiprocessing.html>`__ of Python. | 1058 <https://docs.python.org/3/library/multiprocessing.html>`__ of Python. |
1081 The "master" Python interpreter that is initially started by the | 1059 The "master" Python interpreter that is initially started by the |
1082 Orthanc plugin, can start several `children processes | 1060 Orthanc plugin, can start several `children processes |
1083 <https://en.wikipedia.org/wiki/Process_(computing)>`__, each of these | 1061 <https://en.wikipedia.org/wiki/Process_(computing)>`__, each of these |
1084 processes running a separate Python interpreter. This allows to | 1062 processes running a separate Python interpreter. This allows to |
1085 offload intensive computations from the "master" Python interpreter of | 1063 offload intensive computations from the "master" Python interpreter of |
1086 Orthanc onto those "slave" interpreters. The ``multiprocessing`` | 1064 Orthanc onto those "slave" interpreters. The ``multiprocessing`` |
1087 library is actually quite straightforward to use:: | 1065 library is actually quite straightforward to use: |
1088 | 1066 |
1089 import math | 1067 .. literalinclude:: python/multiprocessing-2.py |
1090 import multiprocessing | 1068 :language: python |
1091 import orthanc | |
1092 import signal | |
1093 import time | |
1094 | |
1095 # CPU-intensive computation taking about 4 seconds | |
1096 # (same code as above) | |
1097 def SlowComputation(): | |
1098 start = time.time() | |
1099 for i in range(1000): | |
1100 for j in range(30000): | |
1101 math.sqrt(float(j)) | |
1102 end = time.time() | |
1103 duration = (end - start) | |
1104 return 'computation done in %.03f seconds\n' % duration | |
1105 | |
1106 # Ignore CTRL+C in the slave processes | |
1107 def Initializer(): | |
1108 signal.signal(signal.SIGINT, signal.SIG_IGN) | |
1109 | |
1110 # Create a pool of 4 slave Python interpreters | |
1111 POOL = multiprocessing.Pool(4, initializer = Initializer) | |
1112 | |
1113 def OnRest(output, uri, **request): | |
1114 # Offload the call to "SlowComputation" onto one slave process. | |
1115 # The GIL is unlocked until the slave sends its answer back. | |
1116 answer = POOL.apply(SlowComputation) | |
1117 output.AnswerBuffer(answer, 'text/plain') | |
1118 | |
1119 orthanc.RegisterRestCallback('/computation', OnRest) | |
1120 | 1069 |
1121 .. highlight:: text | 1070 .. highlight:: text |
1122 | 1071 |
1123 Here is now the result of calling this route three times concurrently:: | 1072 Here is now the result of calling this route three times concurrently:: |
1124 | 1073 |
1155 processes have no access to the ``orthanc`` module. | 1104 processes have no access to the ``orthanc`` module. |
1156 | 1105 |
1157 You must write your Python plugin so as that all the calls to | 1106 You must write your Python plugin so as that all the calls to |
1158 ``orthanc`` are moved from the slaves process to the master | 1107 ``orthanc`` are moved from the slaves process to the master |
1159 process. For instance, here is how you would parse a DICOM file in a | 1108 process. For instance, here is how you would parse a DICOM file in a |
1160 slave process:: | 1109 slave process: |
1161 | 1110 |
1162 import pydicom | 1111 .. literalinclude:: python/multiprocessing-3.py |
1163 import io | 1112 :language: python |
1164 | 1113 |
1165 def OffloadedDicomParsing(dicom): | |
1166 # No access to the "orthanc" library here, as we are in the slave process | |
1167 dataset = pydicom.dcmread(io.BytesIO(dicom)) | |
1168 return str(dataset) | |
1169 | |
1170 def OnRest(output, uri, **request): | |
1171 # The call to "orthanc.RestApiGet()" is only possible in the master process | |
1172 dicom = orthanc.RestApiGet('/instances/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5/file') | |
1173 answer = POOL.apply(OffloadedDicomParsing, args = (dicom, )) | |
1174 output.AnswerBuffer(answer, 'text/plain') | |
1175 | |
1176 Communication primitives such as ``multiprocessing.Queue`` are | 1114 Communication primitives such as ``multiprocessing.Queue`` are |
1177 available to exchange messages from the "slave" Python interpreters to | 1115 available to exchange messages from the "slave" Python interpreters to |
1178 the "master" Python interpreter for more advanced scenarios. | 1116 the "master" Python interpreter for more advanced scenarios. |
1179 | 1117 |
1180 NB: Starting with release 3.0 of the Python plugin, it is possible to | 1118 NB: Starting with release 3.0 of the Python plugin, it is possible to |
1182 way. The function ``orthanc.GenerateRestApiAuthorizationToken()`` can | 1120 way. The function ``orthanc.GenerateRestApiAuthorizationToken()`` can |
1183 be used to create an authorization token that provides full access to | 1121 be used to create an authorization token that provides full access to |
1184 the REST API of Orthanc (without have to set credentials in your | 1122 the REST API of Orthanc (without have to set credentials in your |
1185 plugin). Any HTTP client library for Python, such as `requests | 1123 plugin). Any HTTP client library for Python, such as `requests |
1186 <https://requests.readthedocs.io/en/master/>`__, can then be used to | 1124 <https://requests.readthedocs.io/en/master/>`__, can then be used to |
1187 access the REST API of Orthanc. Here is a minimal example:: | 1125 access the REST API of Orthanc. Here is a minimal example: |
1188 | 1126 |
1189 import json | 1127 .. literalinclude:: python/multiprocessing-4.py |
1190 import multiprocessing | 1128 :language: python |
1191 import orthanc | |
1192 import requests | |
1193 import signal | |
1194 | |
1195 TOKEN = orthanc.GenerateRestApiAuthorizationToken() | |
1196 | |
1197 def SlaveProcess(): | |
1198 r = requests.get('http://localhost:8042/instances', | |
1199 headers = { 'Authorization' : TOKEN }) | |
1200 return json.dumps(r.json()) | |
1201 | |
1202 def Initializer(): | |
1203 signal.signal(signal.SIGINT, signal.SIG_IGN) | |
1204 | |
1205 POOL = multiprocessing.Pool(4, initializer = Initializer) | |
1206 | |
1207 def OnRest(output, uri, **request): | |
1208 answer = POOL.apply(SlaveProcess) | |
1209 output.AnswerBuffer(answer, 'text/plain') | |
1210 | |
1211 orthanc.RegisterRestCallback('/computation', OnRest) |