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)