Difference between revisions of "Slicer3:Slicer Daemon"

From NAMIC Wiki
Jump to: navigation, search
m (Text replacement - "http://www.slicer.org/slicerWiki/index.php/" to "https://www.slicer.org/wiki/")
 
(19 intermediate revisions by 3 users not shown)
Line 1: Line 1:
= Goals and Functionality =
+
<big>'''Note:''' We are migrating this content to the slicer.org domain - <font color="orange">The newer page is [https://www.slicer.org/wiki/Slicer3:Slicer_Daemon here]</font></big>
 
 
The Slicer Daemon refers to a network protocol that can be used to connect to a running instance of slicer to read and write data in the MRML scene and execute other commands.  The name is based on the unix system convention of naming network services '[http://en.wikipedia.org/wiki/Daemon_%28computer_software%29 daemons]'.
 
 
 
= Server Implementation =
 
 
 
The file
 
[http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Tcl/slicerd.tcl?view=log slicerd.tcl] implements the server side functionality.
 
 
 
By default it listens for connections on port 18943.
 
 
 
= Clients =
 
 
 
== Tcl ==
 
 
 
Two utilities are provided:
 
 
 
* [http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Tcl/slicerget.tcl?view=log slicerget.tcl] is used to read volumes out of slicer.  The volume is written to the stdout of the slicerget command in nrrd format.
 
 
 
* [http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Tcl/slicerput.tcl?view=log slicerput.tcl] is used to write volumes into slicer.  The volume is read in nrrd format from stdin of slicerput and loaded into the mrml scene.
 
 
 
Some sample commands (assumes your PATH is correctly set to include unu, slicerget and slicerput):
 
 
# a noop -- just copy image onto itself
 
slicerget.tcl 1 | slicerput.tcl noop
 
 
 
# put external data into slicer
 
unu 1op abs -i d:/data/bunny-small.nrrd | slicerput.tcl
 
 
 
# run an external command and put the data back into slicer
 
slicerget.tcl 1 | unu 1op abs -i - slicerput.tcl abs
 
 
 
== Python ==
 
 
 
A [http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Python/ Python based set of code] for interacting with the Slicer Daemon is provided.
 
 
 
For example, the [http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Python/mathExample.py?view=log following code] reads a volume and creates a new volume where each voxel is the square of the corresponding voxel of the input image.  The new image is then sent back to slicer.
 
 
 
<pre>
 
import slicerd
 
import numpy
 
 
 
s = slicerd.slicerd()
 
 
 
n = s.get(0)
 
 
 
im = n.getImage()
 
n.setImage( im * im )
 
 
 
s.put(n, 'newImage')
 
</pre>
 
 
 
For example, the [http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Python/sliceExample.py?view=log following code] reads a volume and extracts a slice of it for plotting using the matplotlib code (see [http://www.scipy.org the SciPy website] for more info on Python numerics and plotting).
 
 
 
<pre>
 
import slicerd
 
import pylab
 
 
 
s = slicerd.slicerd()
 
 
 
n = s.get(0)
 
 
 
slice = n.getImage()[16,:,:]
 
pylab.imshow(slice)
 
pylab.show()
 
</pre>
 
 
 
== Matlab ==
 
''Note: this is initial documentation only.''
 
 
 
 
 
 
 
[http://www.na-mic.org/ViewVC/index.cgi/trunk/Modules/SlicerDaemon/Matlab/ Matlab based] versions of Slicer Daemon client code are available. 
 
 
 
This [http://wiki.na-mic.org/Wiki/index.php/Projects/Slicer3/2007_Project_Week_Slicer_Matlab_Pipeline_for_scalars_and_tensors project ] has been worked on during the NAMIC project week 2007.
 
 
 
[[Image:SlicerMatlabPipeline.png|Slicer Matlab Pipeline Schema]]
 
 
 
 
 
 
 
The Matlab scripts getSlicerVolume.m and putSlicerVolume.m use Matlab extention [http://labrosa.ee.columbia.edu/matlab/popenrw.html popen] to connect to stdout(stdin respectively) of the tcl client slicerget.tcl (slicerput.tcl respectively). The tcl client establishes a channel to the SlicerDaemon socket and requests(sends) data. Thanks to Dan Ellis for letting us incorporate the popen matlab code into Slicer.
 
 
 
The SlicerDaemon in combination with matlab scripts provided here support the exchange of scalar and tensor volumes between Slicer and Matlab that are in orientation right-anterior-superior or left-posterior-superior. DWI volumes and other orientations are not supported yet.
 
 
 
=== Basic Slicer-Matlab tutorial ===
 
Here a step by step tutorial how to send a volume from Slicer to Matlab and then from back from Matlab to Slicer:
 
 
 
* Start Slicer3 with parameter "--daemon" and load the (scalar or tensor) volume you want to send to Matlab.
 
* Start Matlab and for conveniance change into the "Matlab" subdirectory of the SlicerDaemon module in Slicer3 (something like ../Slicer3/Modules/SlicerDaemon/Matlab)
 
* Initally, the popen C functions need to be compiled for your machine (this is not handled by cmake yet). This needs to be done only once in Matlab:
 
mex popen/popenw.c
 
mex popen/popenr.c
 
* Typing the following command in Matlab, the Slicer volume named "wcase1.nhdr" will be piped into a Matlab structure called "slicer_volume":
 
slicer_volume = getSlicerVolume('wcase1.nhdr')
 
 
 
All volumes that come out of Slicer are in 'right-anterior-superior' orientation, have 'raw' encoding, and 'little' endian. Even if the original file loaded into Slicer had other header parameters.
 
 
 
The resulting Matlab strucuture will looks like this (for a scalar volume):
 
slicer_volume =
 
            content: 'wcase1.nhdr'
 
              type: 'short'
 
          dimension: 3
 
              space: 'right-anterior-superior'
 
              sizes: [256 256 124]
 
            endian: 'little'
 
          encoding: 'raw'
 
        spaceorigin: [119.5310 -92.2500 119.5310]
 
        spaceunits: {'mm'  'mm'  'mm'}
 
              kinds: {'space'  'space'  'space'}
 
              data: [256x256x124 int16]
 
    spacedirections: [3x3 double]
 
or like that (for a tensor volume):
 
slicer_volume =
 
            content: 'helix.nhdr'
 
                type: 'float'
 
          dimension: 4
 
              space: 'right-anterior-superior'
 
              sizes: [7 64 32 12]
 
              endian: 'little'
 
            encoding: 'raw'
 
        spaceorigin: [-6.9386 -28.7554 -8.7247]
 
          spaceunits: {'"mm"'  '"mm"'  '"mm"'}
 
              kinds: {'3D-masked-symmetric-matrix'  'space'  'space'  'space'}
 
                data: [4-D single]
 
    spacedirections: [3x3 double]
 
    measurementframe: [3x3 double]
 
          centerings: {'???'  'cell'  'cell'  'cell'}
 
 
 
Instead of typing the name the volume has in Slicer, you can choose the volume by its Slicer-id. The ids are given in the order volumes are loaded in Slicer. This command fetches the volume loaded first in Slicer:
 
slicer_volume = getSlicerVolume(0)
 
 
 
 
 
* Now the volume data can be processed in Matlab. Just for example, here the volume is thresholded:
 
slicer_volume.data(slicer_volume.data > 100) = 0;
 
 
 
* By changing the field "content", the name of the volume node in Slicer will be changed:
 
slicer_volume.content='Matlab_says_hi';
 
 
 
* This command sends the volume back to Slicer:
 
putSlicerVolume(slicer_volume)
 
 
 
 
 
=== Slicer-Matlab interface and tensor data ===
 
 
 
[[Image:Coords-RAS.png|thumb|182px|right|NRRD fields for image orientation and measurement frame]]
 
 
 
When sending tensor data from Matlab to Slicer and vice versa, there are a couple of issues regarding tensor orientation that the user needs to be aware of.
 
If the only thing you want to do is send a tensor volume from Slicer to Matlab, do some Matlab processing, and send the volume back to Slicer, tensor orientation should not be a problem.
 
But in case you want to send tensor volumes from other sources but Slicer from Matlab to Slicer, the following needs to be considered:
 
 
 
 
 
In the original file itself, the tensor data usually lives in "diffusion-sensitizing gradient space". When loading into Slicer3 though, the tensor data automatically gets transformed into so-called IJK-space, or "Index space". This is necessary for good computational performance in Slicer3, since most ITK based filters require the data to be in ijk space.
 
When tensor data is piped into Slicer using the SlicerDaemon, it is assumed this data is in "Index space". Also, the SlicerDaemon expects tensor data to be in LPS orientation.
 
The SlicerDaemon will not do any of these transformations.
 
How measurement frame, space directions and the different coordinate systems are related, is depicted in this figure by Gordon Kindlemann:
 
 
 
Some small and simple scripts that you might find useful to load nhdr files into Matlab and rotate tensor data into the space you want are provided:
 
 
 
==== Load and save nrrd files in Matlab ====
 
 
 
* saveNrrdStructure.m and loadNrrdStructure.m read or write a nrrd file and create a Matlab structure according to the header information . The scripts call the "nrrdLoad" and "nrrdSave" functions of the [http://teem.sourceforge.net/ teem library]. The path to these functions need to be added to the Matlab path.
 
tensor = loadNrrdStructure('/projects/schiz/guest/kquintus/data/testVolumes/tensor/helix.nhdr')
 
 
 
tensor =
 
            content: 'helix'
 
                type: 'float'
 
          dimension: 4
 
              space: 'right-anterior-superior'
 
              sizes: [7 64 32 12]
 
              endian: 'little'
 
            encoding: 'raw'
 
        spaceorigin: [-98.4375 -96.8750 -91.6667]
 
              kinds: {'3D-masked-symmetric-matrix' 'space'  'space'  'space'}
 
                data: [4-D single]
 
    spacedirections: [3x3 double]
 
          centerings: {'???'  'cell'  'cell'  'cell'}
 
    measurementframe: [3x3 double]
 
 
 
This is how to save a nrrd volume:
 
saveNrrdStructure('/var/tmp/tensor.nhdr', tensor)
 
 
 
==== Convert tensor data between gradient space and ijk space ====
 
 
 
Example:
 
*Load original file with loadNrrdStructure. The tensor data will be in diffusion gradient space.
 
  tensor_gradient_space = loadNrrdStructure('/projects/schiz/guest/kquintus/data/testVolumes/tensor/helix.nhdr')
 
 
 
*Convert data into Slicer space:
 
tensor_ijk_space = tensorTransformToSlicerSpace(tensor_gradient_space);
 
Depending on how big your volume is, this might take a while. The script iterates through all tensors and transforms them.
 
The function changes the measurement frame matrix of the nrrd structure. The measurement frame is the rotation matrix that transforms a tensor into world space. When the tensor data is in ijk space, this matrix needs to be the inverse of the RASToIJK matrix (see Gordon's figure).
 
Now you could send this volume to Slicer, since it is in the correct space (the Slicer ijk space)
 
 
 
*Convert data back into Gradient space:
 
tensor_back_to_gradient_space = tensorTransformToGradientSpace(tensor_ijk_space, tensor_gradient_space.measurementframe )
 
The transformation function needs the original measurement frame matrix, otherwise the transformation obviously can't be done.
 
 
 
When such transformations are done, a numerical error is incurred. In part this is due to matrix inversions, especially when the measurement frame is non trivial and tensor elements are very small. To give you an idea, how big this numerical error can be, the following inital test can be performed with the originally file-loaded nrrd structure:
 
tensor_first_to_ijk_then_back_to_gradient = transformationTestLoop(tensor_gradient_space)
 
This script compares tensors element by element which is not a good measure for tensor integrity. Even though the element-wise relative error might be very big, measures like trace of FA might still be very alike.
 

Latest revision as of 17:35, 10 July 2017

Home < Slicer3:Slicer Daemon

Note: We are migrating this content to the slicer.org domain - The newer page is here