LUA binding - documentation

  • LUA is fast: this command takes about 1s : dgate "--lua:a=1; for i=0,1000000 do a=Command.DataSetType end; print(a)"


    So lua is fast enough to do elementary image processing (e.g., masking image regions to anonymise). Implement GIMP/GLUAS like calls to read and write pixels in the dicom image.


    Add Data.Compression menber to read and change compression. Required to allow editing pixels in compressed images.


    Timing an operation:


    Code
    [lua]ImportConverter0 = r=100000; a=1; b=os.clock(); for i=0,r-1 do a=Data.PhotometricInterpretation end; print(a); print((os.clock()-b)/r);


    result:
    MONOCHROME2
    2.61e-005


    Merging a complex study:


    Code
    [lua]ImportConverter0 = if Data.Modality=='MR' and string.find(Data.SeriesDescription, 'T/3D/FFE/')~=nil then script('merge study modality MR seriesdesc T/3D/FFE/* after 60') end;MergeSeriesConverter0 = script('newuids except 0020,000d'); Data.ImageNumber = 1000*Data.AcquisitionNumber + Data.ImageNumber; Data.SeriesDescription = 'T/3D/FFE/Merged SENSE'


    image processing:

    Code
    print('lua Import', Data.PatientID); t=os.clock(); c=Data.Columns; for i=0,Data.Rows-1 do a=getrow(i,0); for b=0,c-1 do setpixel(b, i, 0, 400+a[c-b]); end; end; print((os.clock()-t));


    This sample code took 0.42 s for a 512 x 512 image


    More image processing:

    Code
    dgate "--lua:readdicom('C:\\Users\\marcel\\Desktop\\file.v2'); print(Data.Rows); t=os.clock(); for i=0,Data.Columns-1 do a=getrow(i); for j=0,511 do a[j]=a[j]+j*10; end; setrow(i,0,a); end; print(os.clock()-t); writedicom('c:\\t.dcm')"


    Loop took 51 ms for 512x512 image


    Marcel

  • Hi,


    Here is the complete syntax of the lua script interface developed sofar.


    All of these commands can be used in lua scripts that are called as follows in dicom.ini:


    ImportConverter0 = lua "print(Association.Calling)"


    or


    ImportConverter0 = file.lua


    or


    [lua]
    ImportConverter0 = print(Association.Calling)


    or from the command line:


    dgate "--lua:print(Association.Calling)"


    or using zerobrane studio as remote debugger (1.4.17)


    Lua files can be loaded per association and then used for other steps:


    [lua]
    association = require('functions.lua')
    command = function(Command.Priority)
    ImportConverter0 = function2(Data.PatientID)
    endassociation=function3(); print(heapinfo())


    These are all available lua events:
    startup - called when server starts
    nightly - called at 03:00 at night (if GUI closed)
    background - called every second (if GUI closed)
    association - called for each new association
    command - called for each new command
    endassociation - called at end of each association
    clienterror - called when the server recieves a command it does not understand
    QueryConverter0 - allows modification / reject of query
    workListQueryConverter0 - allows modification / reject of worklist query
    RejectedImageConverter0 - allows logging, storage or modification of rejected image; use script('retry') to reenter it
    RejectedImageWorkListConverter0 - allows logging, storage or modification of rejected image by worklist processing ; use script('retry') to reenter it
    RetrieveConverter0 - allows modification / reject of C-move command
    RetrieveResultConverter0 - allows modification of image to be moved
    QueryResultConverter0 - allows modification of query results (called once per result row)
    ModalityWorkListQueryResultConverter0 - allows modification of query results (called once per result row)
    MergeSeriesConverter0 - allows modification of images to be merged
    MergeStudiesConverter0 - allows modification of images to be merged
    ArchiveConverter0 - allows modification of images to be archived
    MoveDeviceConverter0 - - allows modification of images to be moved to another device
    CompressionConverter0..9 - allow custum script to be called from compression mode s0..s9
    ImportConverter0..20 - allow scripts to be run on each recieved object
    ExportConverter0..20 - allow scripts to be run processing on each stored object; triggering e.g. queries, moves or forwards
    VirtualServerQueryConverter0 - modify query/move initiated by virtual server (1.4.17b)
    VirtualServerQueryresultConverter0 - modify result of query initiated by virtual server (1.4.17b)


    There also is a global context for lua, but it can only access Global variables and does not share data with associations which run in their own threads. The CGI web interface makes use of the global context, but it is lost between CGI calls.


    [lua]
    global = Global.DebugLevel = 4


    This is the list of available commands (continuously growing, listing for 1.4.17b):


    To run the above code as a test do (you need some data to check with):


    dgate "--lua:readdicom('c:\\t.dcm'); dofile('test.lua'); collectgarbage('collect'); print(heapinfo())"


    The result of heapinfo() should be stable between successive runs.

  • Web support for lua is now under development:


    In the web configuration dicom.ini you can base pages entirely on lua scripts:

    Code
    [flexviewer2]source = flexviewer2.lua


    in the lua script you can call:

    Code
    HTML(string, arg1, ....) -- to output HTML codeprint(CGI('name'), CGI('name', 'default')) -- to read CGI parameters


    in the web templates you can use this construct to embed lua code (?> must be on separate line):

    Code
    <?lua .....?>


    and this construct to output lua strings as HTML (must all be on one line):

    Code
    <%= .... %>


    These constructs are intended to be similar to Lua CGI.


    In release 1.4.16i, the lua interface also inherits all CGI scripting parameters (e.g., query_string, uploadedfile) as global variables. This allows a lua scripted SOAP interface to be added to the server.


    Marcel

  • This simple script changes the StudyUID of an image when it has previously been refused. The image is sent to Aetitle PAXFIX so that it doesn't get triggered by a normal cmove.
    The error looks like this:
    Thu Mar 10 16:04:20 2011 ***Refused to enter inconsistent link StudyInsta into DICOMSeries: PatientID = '747854' SeriesInst = '1.3.46.670589.28.1.1.2096751459.1259802365.2147483647' AND SeriesPat = '747854', Old='1.2.840.113704.1.111.2388.1259800462.6', Refused='1.3.46.670589.28.1.2.0.21.96.163.65.94.20091203112921.31.2'


    The code:

    Code
    [lua]
    ImportConverter0 = if Association.Called == "PACSFIX" then Data.StudyInstanceUID = dbquery('DICOMSeries', 'StudyInsta', 'SeriesInst = "'..Data.SeriesInstanceUID..'"')[1][1]; end;
  • Hi Marcel,


    some wider sql binding could help sharing data between threads.
    I think of a free function sql(<any valid sql>) as the flexible way.


    Correct me if I'm wrong, dbquery() seems to be limited to SELECT

      a=dbquery(b,c,d)


    does build and execute this

      "SELECT <c> FROM <b> WHERE <d>"


    and returns the result array in a.
    d can contain everything legal after "WHERE" in SELECT syntax.


    With (manually or lua created) additional table "UserTable" in Conquest database

      sql("INSERT INTO UserTable (CounterID,Val) VALUES ('CT',1) ON DUPLICATE KEY UPDATE Val=Val+1")


    would count up the CT counter or create it when not there. (See Lua http://forum.image-systems.biz/viewtopic.php?f=33&t=3111)


    If you could easily make the truncate function for database field names available to lua, this would be nice.


    Is the result of GetVR() allways in little endian order or where can I get the order from?


    Gerald

  • Thanks!


    the db drivers have query/update/insert/createtable implemented for all different databases. For me it is easier to export these all. Taken notice of TruncateFieldName. GetVR returns as littleendian on all machines.


    Marcel

  • Marcel,
    Is it possible to use lua scripting to control filename behavior? I'm running into a problem where we would like the study description to be used for splitting the files delivered into the folders for different labs. Each lab's name is the first word of the study description, followed by either a ' ' or a '^' depending on the actual field you reference in the dicom header (0032,1060 seems to have a space while 0040,0254 has a ^ symbol).


    From there we are planning to make symlinks to these lab names in each of the labs 'home' folders. Eliminating the need for hunting for your data or running cumbersome scripts to retrieve your stuff. Assuming you know the date and time of course.
    -Garrett

  • Hi,


    I have not yet implemented lua for filename generation. Yet, the filenamesyntax that exists should bring you a long way (using e.g., substrings of any tag).


    Added Note: lua filename generation will be implemented in 1.4.17.


    Marcel

  • Quote from marcelvanherk

    Hi,


    I have not yet implemented lua for filename generation. Yet, the filenamesyntax that exists should bring you a long way (using e.g., substrings of any tag).


    Marcel


    This is the way we are going to go for it I believe. We are just going to normalize the syngo interface so that the Users are all at least 4-6 (haven't chosen) characters long and use that to split the files up into the relevant labs.


  • Hi Marcel,


    could you post the contents of flexviewer2.lua (or another example) and the corresponding web template? Maybe in the script library thread?
    I only can get some lua code to run via the command line, but am stuck with these constructs :(


    Thanks,


    Roger

  • Quote from marcelvanherk

    Hi Roger,


    There was a bug in the lua webpage code. Update to 1.4.16f and try again. I have not written any webpages myself in lua yet....


    Marcel


    HI Marcel,


    thanks for your suggestion, but I had already upgraded. Sorry for not having mentioned that.
    Unfortunately, adding this chunk to the bottom of my /usr/local/cgi-bin/dicom.ini:

    Code
    [roger]header= Content-type: text/html\Cache-Control: no-cache\line0 = <head><title>Hello from Lua</title></head>line1 = <body><h1>Dark side of</h1>line2 = <?lualine3 = print("<b>the moon.</b>")line4 = ?>line5 = <p>Pink Floyd</p></body>


    When visiting <my-IP-nr>/cgi-bin/dgate?mode=roger this web page (HTML code) results:

    Code
    <head><title>Hello from Lua</title></head>
    <body><h1>Dark side of</h1>
     
     
    <p>Pink Floyd</p></body>
  • Hi Marcel,


    is there any syntax to use image manipulation like getpixel(),setpixel() with other dicom objects than the default 'Data' ?
    And another question: it seems, that dicomquery() can't handle worklist query level or I have not found the right keyword to use for.
    Regards


    Gerald

  • Hi,


    I missed you post. No, I failed to implement pixel ops for any dicom object. A good wish.


    For worklist query this should in pronciple work, but it may be broken:


    b=newdicomobject(); b.PatientName = '*'; a=dicomquery('CONQUESTSRV1', 'WORKLIST', b);
    print ('First query result has this patientname:', a[0].PatientName);


    Marcel

  • Some scripting updates:


    Getting and setting pixel values rows and columns is now supported for any dicom object.


    There are sample web page scripts with lua constructs in the 1.4.16i update. Note that some require additional lua libraries to be downloaded, notably LuaXml is used for the SOAP example.


    An ECRF image uploading system has also been added to the web interface. It collects data from your PACS and zips and transmits it over SFTP. The suitable anonymization has to be added.


    For that purpose, dicomanonymize.lua has been added in the 1.4.16i update as well.


    Marcel

  • Hi,


    Using zerobrane studio (http://studio.zerobrane.com/), it is possible to debug lua scripts in conquest dicom server:


    You need these files in the dicomserver folder:


    socket\core.dll (from zerobrane bin\clibs) {note: it is hard to find a 64 bit dll, try it with a 32 bit server first}
    socket.lua (from zerobrane lualibs)
    mobdebug.lua (from zerobrane lualibs)


    download and start zerobrane studio; project - start debugger server


    write a lua program that contains line:


    require('mobdebug').start()


    when you
    1) open this program file in zerobrane studio and
    2) then run it from conquest, e.g., using dgate --lua:dofile('program.lua'), the debugger should break at this line.


    From here all of zerobrane debugging facilities should work (watch, modify, set breakpoints, etc).


    Marcel

  • Here is the code of a zerobrane interpreter (to be placed in zerobrane interpreters folder) that allows experimental debugging and live coding of a 32 bits conquest dicom server lua script. Note that in order to work the following files have to be placed in the conquest server folder: lua5.1.dll, lua51.dll, socket\core.dll, socket.lua and mobdebug.lua. All these files can be taken from the latest Zerobrane Studio installation. Do not use this on a production server, as crashes still occur regularly.


    When it works, try 'run as scratchpad' you can see the output in the conquest console change immediately after each keystroke in the code - great fun!


    Marcel


    file conquestdicomserver.lua

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!