Mercurial > hg > orthanc-book
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sphinx/source/users/lua.rst Fri Apr 22 12:57:38 2016 +0200 @@ -0,0 +1,367 @@ +.. _lua: + +Server-side scripting with Lua +============================== + +.. contents:: + +Since release 0.5.2, Orthanc supports server-side scripting through +the `Lua <http://en.wikipedia.org/wiki/Lua_(programming_language)>`__ +scripting language. Thanks to this major feature, Orthanc can be tuned +to specific medical workflows without being driven by an external +script. This page summarizes the possibilities of Orthanc server-side +scripting. + +Many other examples are `available in the source distribution +<https://bitbucket.org/sjodogne/orthanc/src/default/Resources/Samples/Lua/>`__. + + +Installing a Lua Script +----------------------- + +.. highlight:: bash + +A custom Lua script can be installed either by the :ref:`configuration +file <configuration>`, or by uploading it +through the :ref:`REST API <rest-samples>`. + +To install it by the **configuration file** method, you just have to +specify the path to the file containing the Lua script in the +``LuaScripts`` variable. + +To upload a script stored in the file "``script.lua``" through the +**REST API**, use the following command:: + + $ curl -X POST http://localhost:8042/tools/execute-script --data-binary @script.lua + +Pay attention to the fact that, contrarily to the scripts installed +from the configuration file, the scripts installed through the REST +API are non-persistent: They are discarded after a restart of Orthanc, +which makes them useful for script prototyping. You can also interpret +a single Lua command through the REST API:: + + $ curl -X POST http://localhost:8042/tools/execute-script --data-binary "print(42)" + +*Note:* The ``--data-binary`` cURL option is used instead of +``--data`` to prevent the interpretation of newlines by cURL, which is +`mandatory for the proper evaluation +<http://stackoverflow.com/q/3872427/881731>`__ of the possible +comments inside the Lua script. + + +Lua API +------- + + +.. _lua-callbacks: + +Callbacks to react to events +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Lua engine of Orthanc comes invokes the following callbacks that +are triggered on various events. Here are the **generic events**: + +* ``function Initialize()``: Invoked as soon as the Orthanc server is started. +* ``function Finalize()``: Invoked just before the Orthanc server is stopped. + +Some **permission-related events** allow to filter incoming requests: + +* ``function ReceivedInstanceFilter(dicom, origin)``: + Invoked to known whether an incoming DICOM instance should be + accepted. :ref:`See this section <lua-filter-dicom>`. The ``origin`` + parameter is :ref:`documented separately <lua-origin>`. +* ``function IncomingHttpRequestFilter(method, uri, ip, username, + httpHeaders)``: Invoked to known whether a REST request should be + accepted. :ref:`See this section <lua-filter-rest>`. + +Some **DICOM-related events** allow to react to the reception of +new medical images: + +* ``function OnStoredInstance(instanceId, tags, metadata, origin)``: + Invoked whenever a new instance has been stored into Orthanc. + This is especially useful for :ref:`lua-auto-routing`. The ``origin`` + parameter is :ref:`documented separately <lua-origin>`. +* ``function OnStablePatient(patientId, tags, metadata)``: Invoked + whenever a patient has not received any new instance for a certain + amount of time (cf. the option ``StableAge`` in the + :ref:`configuration file <configuration>`). The :ref:`identifier + <orthanc-ids>` of the patient is provided, together with her DICOM + tags and her metadata. +* ``function OnStableSeries(seriesId, tags, metadata)``: Invoked + whenever a series has not received any new instance for a certain + amount of time. +* ``function OnStableStudy(studyId, tags, metadata)``: Invoked + whenever a study has not received any new instance for a certain + amount of time. +* ``function IncomingFindRequestFilter(source, origin)``: Invoked + whenever Orthanc receives an incoming C-Find query through the DICOM + protocol. This allows to inspect the content of the C-Find query, + and possibly modify it if a patch is needed for some manufacturer. A + `sample script is available + <https://bitbucket.org/sjodogne/orthanc/src/default/Resources/Samples/Lua/IncomingFindRequestFilter.lua>`__. + +Furthermore, whenever a DICOM association is negociated for C-Store +SCP, several callbacks are successively invoked to specify which +**transfer syntaxes** are accepted for the association. These +callbacks are listed in `this sample script +<https://bitbucket.org/sjodogne/orthanc/src/default/Resources/Samples/Lua/TransferSyntaxEnable.lua>`__. + +*Note:* All of these callbacks are guaranteed to be **invoked in +mutual exclusion**. This implies that Lua scripting in Orthanc does +not support any kind of concurrency. + + +.. _lua-rest: + +Calling the REST API of Orthanc +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Lua scripts have :ref:`full access to the REST API <rest>` of Orthanc +through the following functions: + +* ``RestApiGet(uri, builtin)`` +* ``RestApiPost(uri, body, builtin)`` +* ``RestApiPut(uri, body, builtin)`` +* ``RestApiDelete(uri, builtin)`` + +The ``uri`` arguments specifies the URI against which to make the +request, and ``body`` is a string containing the body of POST/PUT +request. The ``builtin`` parameter is an optional Boolean that +specifies whether the request targets only the built-in REST API of +Orthanc (if set to ``true``), or the full the REST API after being +tainted by the plugins (if set to ``false``). + + +General-purpose functions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Lua engine of Orthanc contain several general-purpose ancillary +functions: + +* ``PrintRecursive(v)`` recursively prints the content of a `Lua table + <http://www.lua.org/pil/2.5.html>`__ to the log file of Orthanc. +* ``ParseJson(s)`` converts a string encoded in the `JSON format + <https://en.wikipedia.org/wiki/JSON>`__ to a Lua table. +* ``DumpJson(v, keepStrings)`` encodes a Lua table as a JSON string. + Setting the optional argument ``keepStrings`` (available from + release 0.9.5) to ``true`` prevents the automatic conversion of + strings to integers. +* ``GetOrthancConfiguration()`` returns a Lua table containing the + content of the :ref:`configuration files <configuration>` of + Orthanc. + + +Similarly to the functions to :ref:`call the REST API of Orthanc +<lua-rest>`, several functions are available to make generic HTTP +requests to Web services: + +* ``HttpGet(url)`` +* ``HttpPost(url, body)`` +* ``HttpPut(url, body)`` +* ``HttpDelete(url)`` +* ``SetHttpCredentials(username, password)`` can be used to setup the + HTTP credentials. + + +.. _lua-origin: + +Origin of the instances +^^^^^^^^^^^^^^^^^^^^^^^ + +Whenever Orthanc decides whether it should should store a new instance +(cf. the ``ReceivedInstanceFilter()`` callback), or whenever it has +actually stored a new instance (cf. the ``OnStoredInstance`` +callback), an ``origin`` parameter is provided. This parameter is a +`Lua table <http://www.lua.org/pil/2.5.html>`__ that describes from +which Orthanc subsystem the new instance comes from. + +There are 4 possible subsystems, that can be distinguished according +to the value of ``origin["RequestOrigin"]``: + +* ``RestApi``: The instance originates from some HTTP request to the REST + API. In this case, the ``RemoteIp`` and ``Username`` fields are + available in ``origin``. They respectively describe the IP address + of the HTTP client, and the username that was used for HTTP + authentication (as defined in the ``RegisteredUsers`` + :ref:`configuration variable <configuration>`). +* ``DicomProtocol``: The instance originates from a DICOM C-Store. + The fields ``RemoteIp``, ``RemoteAet`` and ``CalledAet`` + respectively provide the IP address of the DICOM SCU (client), the + application entity title of the DICOM SCU client, and the + application entity title of the Orthanc SCP server. The + ``CalledAet`` can be used for :ref:`advanced auto-routing scenarios + <lua-auto-routing>`, when a single instance of Orthanc acts as a + proxy for several DICOM SCU clients. +* ``Lua``: The instance originates from a Lua script. +* ``Plugins``: The instance originates from a plugin. + + +.. _lua-filter-dicom: + +Filtering Incoming DICOM Instances +---------------------------------- + +.. highlight:: lua + +Each time a DICOM instance is received by Orthanc (either through the +DICOM protocol or through the REST API), the +``ReceivedInstanceFilter()`` Lua function is invoked. If this callback +returns ``true``, the instance is accepted for storage. If it returns +``false``, the instance is discarded. This mechanism can be used to +filter the incoming DICOM instances. Here is an example of a Lua +filter that only allows incoming instances of MR modality:: + + function ReceivedInstanceFilter(dicom, origin) + -- Only allow incoming MR images + if dicom.Modality == 'MR' then + return true + else + return false + end + end + +The argument dicom corresponds to a `Lua table +<http://www.lua.org/pil/2.5.html>`__ (i.e. an associative array) that +contains the DICOM tags of the incoming instance. For debugging +purpose, you can print this structure as follows:: + + function ReceivedInstanceFilter(dicom, origin) + PrintRecursive(dicom) + -- Accept all incoming instances (default behavior) + return true + end + +The argument ``origin`` is :ref:`documented separately <lua-origin>`. + + +.. _lua-filter-rest: + +Filtering Incoming REST Requests +-------------------------------- + +.. highlight:: lua + +Lua scripting can be used to control the access to the various URI of +the REST API. Each time an incoming HTTP request is received, the +``IncomingHttpRequestFilter()`` Lua function is called. The access to +the resource is granted if and only if this callback script returns +``true``. + +This mechanism can be used to implement fine-grained `access control +lists <http://en.wikipedia.org/wiki/Access_control_list>`__. Here is +an example of a Lua script that limits POST, PUT and DELETE requests +to an user that is called "admin":: + + function IncomingHttpRequestFilter(method, uri, ip, username, httpHeaders) + -- Only allow GET requests for non-admin users + + if method == 'GET' then + return true + elseif username == 'admin' then + return true + else + return false + end + end + +Here is a description of the arguments of this Lua callback: + +* ``method``: The HTTP method (GET, POST, PUT or DELETE). +* ``uri``: The path to the resource (e.g. ``/tools/generate-uid``). +* ``ip``: The IP address of the host that has issued the HTTP request (e.g. ``127.0.0.1``). +* ``username``: If HTTP Basic Authentication is enabled in the + :ref:`configuration file <configuration>`, the name of the user that + has issued the HTTP request (as defined in the ``RegisteredUsers`` + configuration variable). If the authentication is disabled, this + argument is set to the empty string. +* ``httpHeaders``: The HTTP headers of the incoming request. This + argument is available since Orthanc 1.0.1. It is useful if the + authentication should be achieved through tokens, for instance + against a `LDAP + <https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol>`__ + or `OAuth2 <https://en.wikipedia.org/wiki/OAuth>`__ server. + + +.. _lua-auto-routing: + +Auto-Routing of DICOM Images +---------------------------- + +.. highlight:: lua + +Since release 0.8.0, the routing of DICOM flows can be very easily +automated with Orthanc. All you have to do is to declare your +destination modality in the :ref:`configuration file <configuration>` +(section ``DicomModalities``), then to create and install a Lua +script. For instance, here is a sample script:: + + function OnStoredInstance(instanceId, tags, metadata) + Delete(SendToModality(instanceId, 'sample')) + end + +If this script is loaded into Orthanc, whenever a new DICOM instance +is received by Orthanc, it will be routed to the modality whose +symbolic name is ``sample`` (through a Store-SCU command), then it +will be removed from Orthanc. In other words, this is a **one-liner +script to implement DICOM auto-routing**. + +Very importantly, thanks to this feature, you do not have to use the +REST API or to create external scripts in order to automate simple +imaging flows. The scripting engine is entirely contained inside the +Orthanc core system. + +Thanks to Lua expressiveness, you can also implement conditional +auto-routing. For instance, if you wish to route only patients whose +name contains "David", you would simply write:: + + function OnStoredInstance(instanceId, tags, metadata) + -- Extract the value of the "PatientName" DICOM tag + local patientName = string.lower(tags['PatientName']) + + if string.find(patientName, 'david') ~= nil then + -- Only route patients whose name contains "David" + Delete(SendToModality(instanceId, 'sample')) + + else + -- Delete the patients that are not called "David" + Delete(instanceId) + end + end + +Besides ``SendToModality()``, a mostly identical function with the +same arguments called ``SendToPeer()`` can be used to route instances +to :ref:`Orthanc peers <peers>`. It is also possible to modify the +received instances before routing them. For instance, here is how you +would replace the ``StationName`` DICOM tag:: + + function OnStoredInstance(instanceId, tags, metadata) + -- Ignore the instances that result from a modification to avoid + -- infinite loops + if (metadata['ModifiedFrom'] == nil and + metadata['AnonymizedFrom'] == nil) then + + -- The tags to be replaced + local replace = {} + replace['StationName'] = 'My Medical Device' + + -- The tags to be removed + local remove = { 'MilitaryRank' } + + -- Modify the instance, send it, then delete the modified instance + Delete(SendToModality(ModifyInstance(instanceId, replace, remove, true), 'sample')) + + -- Delete the original instance + Delete(instanceId) + end + end + + +**Important remark:** The ``SendToModality()``, ``SendToPeer()``, +``ModifyInstance()`` and ``Delete()`` functions are for the most +common cases of auto-routing (implying a single DICOM instance, and +possibly a basic modification of this instance). For more evolved +auto-routing scenarios, remember that Lua scripts :ref:`have full to +the REST API of Orthanc <lua-rest>`, and that :ref:`other callbacks +are available <lua-callbacks>` to react to other events than the +reception of a single instance (notably ``OnStablePatient()``, +``OnStableStudy()`` and ``OnStableSeries()``).