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()``).