Mercurial > hg > orthanc-book
comparison Sphinx/source/users/lua.rst @ 0:901e8961f46e
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 22 Apr 2016 12:57:38 +0200 |
parents | |
children | c98317fedf87 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:901e8961f46e |
---|---|
1 .. _lua: | |
2 | |
3 Server-side scripting with Lua | |
4 ============================== | |
5 | |
6 .. contents:: | |
7 | |
8 Since release 0.5.2, Orthanc supports server-side scripting through | |
9 the `Lua <http://en.wikipedia.org/wiki/Lua_(programming_language)>`__ | |
10 scripting language. Thanks to this major feature, Orthanc can be tuned | |
11 to specific medical workflows without being driven by an external | |
12 script. This page summarizes the possibilities of Orthanc server-side | |
13 scripting. | |
14 | |
15 Many other examples are `available in the source distribution | |
16 <https://bitbucket.org/sjodogne/orthanc/src/default/Resources/Samples/Lua/>`__. | |
17 | |
18 | |
19 Installing a Lua Script | |
20 ----------------------- | |
21 | |
22 .. highlight:: bash | |
23 | |
24 A custom Lua script can be installed either by the :ref:`configuration | |
25 file <configuration>`, or by uploading it | |
26 through the :ref:`REST API <rest-samples>`. | |
27 | |
28 To install it by the **configuration file** method, you just have to | |
29 specify the path to the file containing the Lua script in the | |
30 ``LuaScripts`` variable. | |
31 | |
32 To upload a script stored in the file "``script.lua``" through the | |
33 **REST API**, use the following command:: | |
34 | |
35 $ curl -X POST http://localhost:8042/tools/execute-script --data-binary @script.lua | |
36 | |
37 Pay attention to the fact that, contrarily to the scripts installed | |
38 from the configuration file, the scripts installed through the REST | |
39 API are non-persistent: They are discarded after a restart of Orthanc, | |
40 which makes them useful for script prototyping. You can also interpret | |
41 a single Lua command through the REST API:: | |
42 | |
43 $ curl -X POST http://localhost:8042/tools/execute-script --data-binary "print(42)" | |
44 | |
45 *Note:* The ``--data-binary`` cURL option is used instead of | |
46 ``--data`` to prevent the interpretation of newlines by cURL, which is | |
47 `mandatory for the proper evaluation | |
48 <http://stackoverflow.com/q/3872427/881731>`__ of the possible | |
49 comments inside the Lua script. | |
50 | |
51 | |
52 Lua API | |
53 ------- | |
54 | |
55 | |
56 .. _lua-callbacks: | |
57 | |
58 Callbacks to react to events | |
59 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
60 | |
61 The Lua engine of Orthanc comes invokes the following callbacks that | |
62 are triggered on various events. Here are the **generic events**: | |
63 | |
64 * ``function Initialize()``: Invoked as soon as the Orthanc server is started. | |
65 * ``function Finalize()``: Invoked just before the Orthanc server is stopped. | |
66 | |
67 Some **permission-related events** allow to filter incoming requests: | |
68 | |
69 * ``function ReceivedInstanceFilter(dicom, origin)``: | |
70 Invoked to known whether an incoming DICOM instance should be | |
71 accepted. :ref:`See this section <lua-filter-dicom>`. The ``origin`` | |
72 parameter is :ref:`documented separately <lua-origin>`. | |
73 * ``function IncomingHttpRequestFilter(method, uri, ip, username, | |
74 httpHeaders)``: Invoked to known whether a REST request should be | |
75 accepted. :ref:`See this section <lua-filter-rest>`. | |
76 | |
77 Some **DICOM-related events** allow to react to the reception of | |
78 new medical images: | |
79 | |
80 * ``function OnStoredInstance(instanceId, tags, metadata, origin)``: | |
81 Invoked whenever a new instance has been stored into Orthanc. | |
82 This is especially useful for :ref:`lua-auto-routing`. The ``origin`` | |
83 parameter is :ref:`documented separately <lua-origin>`. | |
84 * ``function OnStablePatient(patientId, tags, metadata)``: Invoked | |
85 whenever a patient has not received any new instance for a certain | |
86 amount of time (cf. the option ``StableAge`` in the | |
87 :ref:`configuration file <configuration>`). The :ref:`identifier | |
88 <orthanc-ids>` of the patient is provided, together with her DICOM | |
89 tags and her metadata. | |
90 * ``function OnStableSeries(seriesId, tags, metadata)``: Invoked | |
91 whenever a series has not received any new instance for a certain | |
92 amount of time. | |
93 * ``function OnStableStudy(studyId, tags, metadata)``: Invoked | |
94 whenever a study has not received any new instance for a certain | |
95 amount of time. | |
96 * ``function IncomingFindRequestFilter(source, origin)``: Invoked | |
97 whenever Orthanc receives an incoming C-Find query through the DICOM | |
98 protocol. This allows to inspect the content of the C-Find query, | |
99 and possibly modify it if a patch is needed for some manufacturer. A | |
100 `sample script is available | |
101 <https://bitbucket.org/sjodogne/orthanc/src/default/Resources/Samples/Lua/IncomingFindRequestFilter.lua>`__. | |
102 | |
103 Furthermore, whenever a DICOM association is negociated for C-Store | |
104 SCP, several callbacks are successively invoked to specify which | |
105 **transfer syntaxes** are accepted for the association. These | |
106 callbacks are listed in `this sample script | |
107 <https://bitbucket.org/sjodogne/orthanc/src/default/Resources/Samples/Lua/TransferSyntaxEnable.lua>`__. | |
108 | |
109 *Note:* All of these callbacks are guaranteed to be **invoked in | |
110 mutual exclusion**. This implies that Lua scripting in Orthanc does | |
111 not support any kind of concurrency. | |
112 | |
113 | |
114 .. _lua-rest: | |
115 | |
116 Calling the REST API of Orthanc | |
117 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
118 | |
119 Lua scripts have :ref:`full access to the REST API <rest>` of Orthanc | |
120 through the following functions: | |
121 | |
122 * ``RestApiGet(uri, builtin)`` | |
123 * ``RestApiPost(uri, body, builtin)`` | |
124 * ``RestApiPut(uri, body, builtin)`` | |
125 * ``RestApiDelete(uri, builtin)`` | |
126 | |
127 The ``uri`` arguments specifies the URI against which to make the | |
128 request, and ``body`` is a string containing the body of POST/PUT | |
129 request. The ``builtin`` parameter is an optional Boolean that | |
130 specifies whether the request targets only the built-in REST API of | |
131 Orthanc (if set to ``true``), or the full the REST API after being | |
132 tainted by the plugins (if set to ``false``). | |
133 | |
134 | |
135 General-purpose functions | |
136 ^^^^^^^^^^^^^^^^^^^^^^^^^ | |
137 | |
138 The Lua engine of Orthanc contain several general-purpose ancillary | |
139 functions: | |
140 | |
141 * ``PrintRecursive(v)`` recursively prints the content of a `Lua table | |
142 <http://www.lua.org/pil/2.5.html>`__ to the log file of Orthanc. | |
143 * ``ParseJson(s)`` converts a string encoded in the `JSON format | |
144 <https://en.wikipedia.org/wiki/JSON>`__ to a Lua table. | |
145 * ``DumpJson(v, keepStrings)`` encodes a Lua table as a JSON string. | |
146 Setting the optional argument ``keepStrings`` (available from | |
147 release 0.9.5) to ``true`` prevents the automatic conversion of | |
148 strings to integers. | |
149 * ``GetOrthancConfiguration()`` returns a Lua table containing the | |
150 content of the :ref:`configuration files <configuration>` of | |
151 Orthanc. | |
152 | |
153 | |
154 Similarly to the functions to :ref:`call the REST API of Orthanc | |
155 <lua-rest>`, several functions are available to make generic HTTP | |
156 requests to Web services: | |
157 | |
158 * ``HttpGet(url)`` | |
159 * ``HttpPost(url, body)`` | |
160 * ``HttpPut(url, body)`` | |
161 * ``HttpDelete(url)`` | |
162 * ``SetHttpCredentials(username, password)`` can be used to setup the | |
163 HTTP credentials. | |
164 | |
165 | |
166 .. _lua-origin: | |
167 | |
168 Origin of the instances | |
169 ^^^^^^^^^^^^^^^^^^^^^^^ | |
170 | |
171 Whenever Orthanc decides whether it should should store a new instance | |
172 (cf. the ``ReceivedInstanceFilter()`` callback), or whenever it has | |
173 actually stored a new instance (cf. the ``OnStoredInstance`` | |
174 callback), an ``origin`` parameter is provided. This parameter is a | |
175 `Lua table <http://www.lua.org/pil/2.5.html>`__ that describes from | |
176 which Orthanc subsystem the new instance comes from. | |
177 | |
178 There are 4 possible subsystems, that can be distinguished according | |
179 to the value of ``origin["RequestOrigin"]``: | |
180 | |
181 * ``RestApi``: The instance originates from some HTTP request to the REST | |
182 API. In this case, the ``RemoteIp`` and ``Username`` fields are | |
183 available in ``origin``. They respectively describe the IP address | |
184 of the HTTP client, and the username that was used for HTTP | |
185 authentication (as defined in the ``RegisteredUsers`` | |
186 :ref:`configuration variable <configuration>`). | |
187 * ``DicomProtocol``: The instance originates from a DICOM C-Store. | |
188 The fields ``RemoteIp``, ``RemoteAet`` and ``CalledAet`` | |
189 respectively provide the IP address of the DICOM SCU (client), the | |
190 application entity title of the DICOM SCU client, and the | |
191 application entity title of the Orthanc SCP server. The | |
192 ``CalledAet`` can be used for :ref:`advanced auto-routing scenarios | |
193 <lua-auto-routing>`, when a single instance of Orthanc acts as a | |
194 proxy for several DICOM SCU clients. | |
195 * ``Lua``: The instance originates from a Lua script. | |
196 * ``Plugins``: The instance originates from a plugin. | |
197 | |
198 | |
199 .. _lua-filter-dicom: | |
200 | |
201 Filtering Incoming DICOM Instances | |
202 ---------------------------------- | |
203 | |
204 .. highlight:: lua | |
205 | |
206 Each time a DICOM instance is received by Orthanc (either through the | |
207 DICOM protocol or through the REST API), the | |
208 ``ReceivedInstanceFilter()`` Lua function is invoked. If this callback | |
209 returns ``true``, the instance is accepted for storage. If it returns | |
210 ``false``, the instance is discarded. This mechanism can be used to | |
211 filter the incoming DICOM instances. Here is an example of a Lua | |
212 filter that only allows incoming instances of MR modality:: | |
213 | |
214 function ReceivedInstanceFilter(dicom, origin) | |
215 -- Only allow incoming MR images | |
216 if dicom.Modality == 'MR' then | |
217 return true | |
218 else | |
219 return false | |
220 end | |
221 end | |
222 | |
223 The argument dicom corresponds to a `Lua table | |
224 <http://www.lua.org/pil/2.5.html>`__ (i.e. an associative array) that | |
225 contains the DICOM tags of the incoming instance. For debugging | |
226 purpose, you can print this structure as follows:: | |
227 | |
228 function ReceivedInstanceFilter(dicom, origin) | |
229 PrintRecursive(dicom) | |
230 -- Accept all incoming instances (default behavior) | |
231 return true | |
232 end | |
233 | |
234 The argument ``origin`` is :ref:`documented separately <lua-origin>`. | |
235 | |
236 | |
237 .. _lua-filter-rest: | |
238 | |
239 Filtering Incoming REST Requests | |
240 -------------------------------- | |
241 | |
242 .. highlight:: lua | |
243 | |
244 Lua scripting can be used to control the access to the various URI of | |
245 the REST API. Each time an incoming HTTP request is received, the | |
246 ``IncomingHttpRequestFilter()`` Lua function is called. The access to | |
247 the resource is granted if and only if this callback script returns | |
248 ``true``. | |
249 | |
250 This mechanism can be used to implement fine-grained `access control | |
251 lists <http://en.wikipedia.org/wiki/Access_control_list>`__. Here is | |
252 an example of a Lua script that limits POST, PUT and DELETE requests | |
253 to an user that is called "admin":: | |
254 | |
255 function IncomingHttpRequestFilter(method, uri, ip, username, httpHeaders) | |
256 -- Only allow GET requests for non-admin users | |
257 | |
258 if method == 'GET' then | |
259 return true | |
260 elseif username == 'admin' then | |
261 return true | |
262 else | |
263 return false | |
264 end | |
265 end | |
266 | |
267 Here is a description of the arguments of this Lua callback: | |
268 | |
269 * ``method``: The HTTP method (GET, POST, PUT or DELETE). | |
270 * ``uri``: The path to the resource (e.g. ``/tools/generate-uid``). | |
271 * ``ip``: The IP address of the host that has issued the HTTP request (e.g. ``127.0.0.1``). | |
272 * ``username``: If HTTP Basic Authentication is enabled in the | |
273 :ref:`configuration file <configuration>`, the name of the user that | |
274 has issued the HTTP request (as defined in the ``RegisteredUsers`` | |
275 configuration variable). If the authentication is disabled, this | |
276 argument is set to the empty string. | |
277 * ``httpHeaders``: The HTTP headers of the incoming request. This | |
278 argument is available since Orthanc 1.0.1. It is useful if the | |
279 authentication should be achieved through tokens, for instance | |
280 against a `LDAP | |
281 <https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol>`__ | |
282 or `OAuth2 <https://en.wikipedia.org/wiki/OAuth>`__ server. | |
283 | |
284 | |
285 .. _lua-auto-routing: | |
286 | |
287 Auto-Routing of DICOM Images | |
288 ---------------------------- | |
289 | |
290 .. highlight:: lua | |
291 | |
292 Since release 0.8.0, the routing of DICOM flows can be very easily | |
293 automated with Orthanc. All you have to do is to declare your | |
294 destination modality in the :ref:`configuration file <configuration>` | |
295 (section ``DicomModalities``), then to create and install a Lua | |
296 script. For instance, here is a sample script:: | |
297 | |
298 function OnStoredInstance(instanceId, tags, metadata) | |
299 Delete(SendToModality(instanceId, 'sample')) | |
300 end | |
301 | |
302 If this script is loaded into Orthanc, whenever a new DICOM instance | |
303 is received by Orthanc, it will be routed to the modality whose | |
304 symbolic name is ``sample`` (through a Store-SCU command), then it | |
305 will be removed from Orthanc. In other words, this is a **one-liner | |
306 script to implement DICOM auto-routing**. | |
307 | |
308 Very importantly, thanks to this feature, you do not have to use the | |
309 REST API or to create external scripts in order to automate simple | |
310 imaging flows. The scripting engine is entirely contained inside the | |
311 Orthanc core system. | |
312 | |
313 Thanks to Lua expressiveness, you can also implement conditional | |
314 auto-routing. For instance, if you wish to route only patients whose | |
315 name contains "David", you would simply write:: | |
316 | |
317 function OnStoredInstance(instanceId, tags, metadata) | |
318 -- Extract the value of the "PatientName" DICOM tag | |
319 local patientName = string.lower(tags['PatientName']) | |
320 | |
321 if string.find(patientName, 'david') ~= nil then | |
322 -- Only route patients whose name contains "David" | |
323 Delete(SendToModality(instanceId, 'sample')) | |
324 | |
325 else | |
326 -- Delete the patients that are not called "David" | |
327 Delete(instanceId) | |
328 end | |
329 end | |
330 | |
331 Besides ``SendToModality()``, a mostly identical function with the | |
332 same arguments called ``SendToPeer()`` can be used to route instances | |
333 to :ref:`Orthanc peers <peers>`. It is also possible to modify the | |
334 received instances before routing them. For instance, here is how you | |
335 would replace the ``StationName`` DICOM tag:: | |
336 | |
337 function OnStoredInstance(instanceId, tags, metadata) | |
338 -- Ignore the instances that result from a modification to avoid | |
339 -- infinite loops | |
340 if (metadata['ModifiedFrom'] == nil and | |
341 metadata['AnonymizedFrom'] == nil) then | |
342 | |
343 -- The tags to be replaced | |
344 local replace = {} | |
345 replace['StationName'] = 'My Medical Device' | |
346 | |
347 -- The tags to be removed | |
348 local remove = { 'MilitaryRank' } | |
349 | |
350 -- Modify the instance, send it, then delete the modified instance | |
351 Delete(SendToModality(ModifyInstance(instanceId, replace, remove, true), 'sample')) | |
352 | |
353 -- Delete the original instance | |
354 Delete(instanceId) | |
355 end | |
356 end | |
357 | |
358 | |
359 **Important remark:** The ``SendToModality()``, ``SendToPeer()``, | |
360 ``ModifyInstance()`` and ``Delete()`` functions are for the most | |
361 common cases of auto-routing (implying a single DICOM instance, and | |
362 possibly a basic modification of this instance). For more evolved | |
363 auto-routing scenarios, remember that Lua scripts :ref:`have full to | |
364 the REST API of Orthanc <lua-rest>`, and that :ref:`other callbacks | |
365 are available <lua-callbacks>` to react to other events than the | |
366 reception of a single instance (notably ``OnStablePatient()``, | |
367 ``OnStableStudy()`` and ``OnStableSeries()``). |