HowToDevelopACommandLinePlugin

From user's Wiki!
Jump to: navigation, search

This page provides you some tips on how to extend Gimias framework with a CommandLinePlugin. We call CommandLinePlugin a plugin that is composed of an XML file, command line application and a DLL. You can use the command line application independently of GIMIAS framework. And you can also integrate it into Slicer3D


Extend with a simple Filter

This extension can be done just compiling the CommandLinePlugin separately, all the CommandLinePlugins will be placed into the folder “commandlineplugins” of your binary Gimias folder. Gimias will scan this folder at start up. The steps are the followings:

1. Create .cpp/.cxx file containing the main function that calls your filter (see paragraph 'The main file' for details)

2. Create a .xml file with the description of the filter ( see paragraph 'The xml description file' for details)

3. Update CSnake file and run CSnake (CSnake version at least 2.4.1)

4. Compile

You can use StartNewModule tool to create automatically a template main and xml file and the CSnake file connected to them, in order to create a CommandLinePlugin, or you can start from the templates. Please read more in HowToCreateYourCommandLinePlugin.

The main file

The main file is a .cpp or .cxx main that contains at least the following:

#include "TemplateFilterCLP.h"  // this header file is automatically generated from the xml file
 int main (int argc, char * argv[])
  {
  PARSE_ARGS;
  [...]
  return EXIT_SUCCESS;
  }

PARSE_ARGS will parse automatically all the arguments (input/output and parameters) that are declared in the xml file. You won't need to declare the variable corresponding to them, because this will be done automatically. See the TemplateFilterCLP.h file,automatically generated once you compile your code, to understand better the structure of the variables. In the following paragraph you will have some examples of how these parameters are translated into c++ objects.

The xml description file

Inside the xml file we will find mainly these parts: General description, Setting parameters and Input/Output parameters.

For every parameter block it's always required the label and description.

<?xml version="1.0" encoding="utf-8"?>
<executable>
 [...]

  <parameters>                                            |
    <label>Parameters</label>                             |
    <description>Parameters</description>                 | Parameters block one
    [...]                                                 |
  </parameters>                                           |

  <parameters>                                            |
    <label>IO</label>                                     |
    <description>Input/output parameters</description>    | Parameters block two
    [...]                                                 |
  </parameters>                                           |

</executable>

General Description

In this part you will be able to:

  • set the path to your filter, category will be the submenu name of Tools where you will find your filter named as title.
  • add a description that will be added to the "Help & Acknowledgment" section of the filter in the GUI see picture .
  • add a link to the "online documentation" of the class through the field documentation-url
 <category>ToolsSubmenuCategory</category>                   (required)
 <index></index>                                              
 <title>TemplateFilter</title>                               (required)
 <description>Here goes the description</description>        (required)
 <version></version>
 <documentation-url></documentation-url>
 <license></license>
 <contributor></contributor>
 <acknowledgements></acknowledgements>


See Slicer3D page for more details about xml schema.

Settings Parameters

Once that you have these general parameters set you have to add the parameters you want to have in the GUI. The longflag and flag stand for the option name you will use in the commandline to call this parameter . In this parameter block as in the following one you can add different small block that can have these options:

Xml CLP GUI Notes
<type>
<name></name>
name of the C++ variable used internally required if longflag is not specified
<longflag></longflag>
used as the long flag on the command line. used internally not required if flag is present
<flag></flag>
used as the short flag on the command line used internally not required if longflag is present
<label></label>
not used GuiLabel.PNG required
<description></description>
describes the parameter for --usage and --help. GuiDescription.PNG required
<default></default>
default value,for boolean is automatically set to false. GuiDefault.PNG
<index><index>
specifies the order of an argument that has no flags. (see next paragraph)Only input/output params required if there are no flags specified
<channel><channel>
not used (see next paragraph) Only input/output params required for file, directory and image parameters
<constraints>
  <minimum></minimum> 
  <maximum></maximum>
  <step></step>
</constraints>
not used SlicerConstraints.PNG
<enumeration>
  <element></element>
</enumeration>
GuiSlicerEnum.PNG
</type>

Types of parameters

type tag can be one of the following:

Type Example source code Example file
<integer>
    <integer>
       <name>smoothingIterations</name>
       <label>Smoothing iterations</label>
       <longflag>--smoothingIterations</longflag>
       <description>Number of smoothing iterations</description>
       <default>5</default>
    </integer>
ConnectedThreshold
<float>
    <float>
      <name>insideValue</name>
      <longflag>--insideValue</longflag>
      <description>The value assigned to inside pixels</description>
      <label>Inside Value</label>
      <default>1</default>
    </float>
BinaryThresholdImageFilter
<double>
    <double>
        <name>timestep</name>
        <label>Timestep</label>
        <longflag>--timestep</longflag>
        <description>Timestep for curvature flow</description>
        <default>0.0625</default>
        <constraints>
           <minimum>0.001</minimum>
           <maximum>1</maximum>
           <step>0.0001</step>
        </constraints>
    </double>
ConnectedThreshold
<boolean>
    <boolean>
      <name>findUpperThreshold</name>
      <longflag>--findUpperThreshold</longflag>
      <description>Whether to find an upper threshold </description>
      <label>Find Upper Threshold</label>
      <default>true</default>
    </boolean>
IsolatedConnected
<string>
    <string>
      <name>patientName</name>
      <longflag>--patientName</longflag>
      <description>The name of the patient [0010-0010]</description>
      <label>Patient Name</label>
      <default>Anonymous</default>
    </string>
ImageReadDicomWrite
<file>
Example not available Example not available
<directory>
    <directory>
      <name>dicomDirectory</name>
      <longflag>--dicomDirectory</longflag>
      <description>The directory to contain the DICOM</description>
      <label>DICOM Directory</label>
      <default>./</default>
    </directory>
ImageReadDicomWrite
<point [multiple="true|false"] 
[coordinateSystem="lps|ras|ijk"]>
    <point coordinateSystem="ras" multiple="true">
      <name>seed</name>
      <label>Seeds</label>
      <longflag>--seed</longflag>
      <description>Seed point(s) for region growing</description>
      <default>0,0,0</default>
    </point>
ConnectedThreshold
<region [multiple="true|false"] 
[coordinateSystem="lps|ras|ijk"]> 
    <region>
      <name>region</name>
      <longflag>region</longflag>
      <flag>r</flag>
      <label>Region</label>
      <description>region for fixed image</description>
      <default>0,0,0,0,0,0</default>
    </region>
AffineTransform
<image [type="scalar|label|tensor|
        diffusion-weighted|vector|model"]>
    <image>
      <name>inputVolume</name>
      <label>Input Volume</label>
      <channel>input</channel>
      <index>0</index>
      <description>Input volume to be filtered</description>
    </image>
BinaryThresholdImageFilter
<geometry [type="fiberbundle|model"]>
    <geometry>
      <name>outputMesh</name>
      <label>Output Mesh</label>
      <channel>output</channel>
      <index>1</index>
      <description>Output Mesh</description>
    </geometry>
MarchingCubes
<integer-vector>
<float-vector> 
<double-vector> 
<string-vector>
<integer-enumeration>
<float-enumeration>
<double-enumeration>
<string-enumeration>
   <integer-vector>
     <name>radius</name>
     <longflag>--radius</longflag>
     <description>Radius of the neighborhood</description>
     <label>Radius</label>
     <default>5</default>
   </integer-vector>
   <string-enumeration>
     <name>structuringElement</name>
     <label>Structuring Element</label>
     <longflag>struct</longflag>
     <default>BALL</default>
     <element>BALL</element>
     <element>CROSS</element>
     <description>Type of structuring element</description>
   </string-enumeration>
NeighborhoodConnected

Dilate

<volmesh>
 <volmesh fileExtensions=".vtk" >
     <name>inputVolumeMesh</name>
     <label>Input Volumetric Mesh</label>
     <channel>input</channel>
     <accessmode>multiple</accessmode>
     <index>0</index>
     <description>Input 3D+T volumetric mesh</description>
 </volmesh>
VolumetricMeshScalarValue
<transform>
   <transform fileExtensions=".dof" reference="movingImageFileName">
     <name>OutputTransform</name>
     <description>Transform calculated that aligns the fixed and moving image. </description>
     <label>Output transform</label>
     <index>3</index>
     <channel>output</channel>
   </transform>
AffineTransform
<numericdata>
   <numericdata fileExtensions=".xml">
     <name>inputDescriptorsFilename</name>
     <label>Input Geometric Descriptors</label>
     <channel>input</channel>
     <index>0</index>
     <default>None</default>
     <description>Input Geometric Descriptors Filename.</description>
   </numericdata> 
AneurysmSimilarSearch
<signal>

<signal>

  <name>outputSignal</name>
  <label>Output Signal</label>
  <channel>output</channel>
  <index>1</index>
  <description>Output Signal</description>
</signal>
PixelIntensity
<pointset>
Example not available Example not available

AffineTransform example

Here you have an example of a parameters block from AffineTransform.xml

Xml Gimias
 <parameters>
    <label>Registration</label>
    <description>Registration Parameters</description>
    <boolean>
      <name>initializeTrans</name>
      <longflag>initializeTrans</longflag>
      <flag>t</flag>
      <label>initializeTrans</label>
      <description>Initialize the transform 
        using CenteredTransformInitializer</description>
      <default>false</default>
    </boolean>
    <integer>
      <name>nb_Itr</name>
      <longflag>nb_Itr</longflag>
      <label>Num of iterations</label>
      <description>Num of iterations</description>
      <default>100</default>
    </integer>
    <float>
      <name>nb_maxSteplng</name>
      <longflag>nb_maxSteplng</longflag>
      <label>nb_maxSteplng</label>
      <description>Maximum step length>
        The initial step length</description>
      <default>0.5</default>
    </float>
    <float>
      <name>nb_minSteplng</name>
      <longflag>nb_minSteplng</longflag>
      <label>nb_minSteplng</label>
      <description>Minimum step length> 
         The tolerance for convergence</description>
      <default>0.0001</default>
    </float>
    <integer>
      <name>nb_Bins</name>
      <longflag>nb_Bins</longflag>
      <label>Num of bins</label>
      <description>Number of histogram bins</description>
      <default>64</default>
    </integer>
    <integer>
      <name>frac_S</name>
      <longflag>frac_S</longflag>
      <label>Fraction of samples</label>
      <description>Fraction of samples</description>
      <default>100</default>
    </integer>
    <float>
      <name>paddingValue</name>
      <longflag>paddingValue</longflag>
      <label>paddingValue</label>
      <description>Default pixel value</description>
      <default>0</default>
    </float>
    <boolean>
      <name>add_observer</name>
      <longflag>add_observer</longflag>
      <label>add_observer</label>
      <description>show registration outputs</description>
      <default>true</default>
    </boolean>
 </parameters>
AffineTransformation Parameters in Gimias

That corresponds to commandline input arguments and to the c++ code respectively:

[...] --nb_Itr 100 --nb_maxSteplng 0.5 --nb_minSteplng 0.0001 --nb_Bins 64 --frac_S 100 --paddingValue 0 --add_observer
bool initializeTrans = false;
int nb_Itr = 100;
float nb_maxSteplng = 0.5;
[...]

Input/Output Parameters

This is a special parameter block, because in this part you will set the inputs and outputs of your executable, the inputs will be displayed in the GUI with the label you will give to them. The outputs won't be visible in the GUI but they will be automatically created in the processor, then added to the data list with the given name, and automatically rendered. Have a look to the previous paragraph for a detailed description of the xml categories. The channel flag is necessary to identify which are the inputs and the outputs.The index flag is necessary to give an order to the parameter that do not have flag and longflag.

For each input/output parameter, you can specify the following attributes:

  • fileExtensions: Allows you to specify the extension of the data file
  • reference: Specifies the name of the reference input that will be used as a child of the output data

Here we have the IO block of AffineTransform as an example.

Xml Gimias
 <parameters>
 <label>IO</label>
    <description>Input/output parameters</description>
    <image>
      <name>fixedImageFileName</name>
      <label>Fixed Image</label>
      <channel>input</channel>
      <index>0</index>
      <description>Fixed Image</description>
    </image>
    <image>
      <name>movingImageFileName</name>
      <label>Moving Image</label>
      <channel>input</channel>
      <index>1</index>
      <description>Moving Image</description>
    </image>
    <image>
      <name>maskFileName</name>
      <longflag>maskFileName</longflag>
      <label>Mask Image</label>
      <channel>input</channel>
      <description>Mask Image</description>
    </image>
    <image>
      <name>outputImage</name>
      <longflag>outputImage</longflag>
      <label>Output Image</label>
      <channel>output</channel>
      <description>Output Image</description>
    </image>
    <transform fileExtensions=".dof" 
               reference="movingImageFileName">
      <name>OutputTransform</name>
      <longflag>outputtransform</longflag>
      <description>Transform [...].</description>
      <label>Output transform</label>
      <channel>output</channel>
    </transform>
 </parameters>
AffineTransformation in Gimias

Remark:Be careful because images are passed as a reference, while all the others inputs/outputs are written to disk ( the default extension sometimes is different from Gimias to Slicer, so be careful to handle both of them in the main file if you want your commandLine to be compatible in both frameworks).

The same commandLinePlugin AffineTransformation in Slicer looks like this:

AffineTransformation parameters and IO block in Slicer

Debug

Windows Platform

Debug Command Line Plugin

To debug a command line plugin you need to configure the Working Directory of your Command Line Plugin solution.

1. Open your Visual Studio with your Command Line Plugin solution in Debug mode

2. Select the Command Line Plugin project and set it as StartUp project

StartUp Project

If you try to debug it now, you will get this error:

Error when debugging a Command Line Plugin

It says that it cannot find the library ITKCommon.dll.

3. Select the Command Line Plugin project and open the Properties->Debugging window. Set the Working Directory to the directory where is located GIMIAS.EXE and all the required DLLs, like ITKCommon.dll

Properties window

4. Set the Command Arguments of your Command Line Plugin and start debugging

Debug with GIMIAS

In Windows you will have a solution with your Command Line Plugin in Debug configuration, you will need at least the debug binaries of Gimias to be able to debug your application.

1. Open your Visual Studio with your Command Line Plugin solution in Debug mode

2. Go to the folder where you can find GIMIAS.EXE (Debug version) and double click on GIMIAS.EXE to launch it

3. Go to Edit->Preferences->Selected plugins and load your Command Line Plugin (Debug version)

4. In your visual studio solution go to Debug->"Attach to process" and choose Gimias.exe

Now you can debug your Command Line Plugin

Debug a Command Line Plugin

Linux Platform

Documentation not yet available

Data transfer

By default, when executing a command line plugin (CLP), GIMIAS transfers the images using a memory pointer. You can read more in Extensible_Execution_Component#Command_Line_Plugins.

3D + T processing

A command line plugin can process a 3D+T data. GIMIAS stores 3D+T data in the following format:

image00.vtk, image01.vtk, ... imageNN.vtk

There are two types of filters:

  • single: process a single 3D time step. When GIMIAS executes a filter, if the input parameter is configured as single, it will always transfer a single 3D time step to the command line plugin (image.vtk).
  • multiple: process a whole 3D+T data set. When GIMIAS executes a filter, if the input parameter is configured as multiple, it will always transfer the whole 3D+T data set to the command line plugin (image00.vtk, image01.vtk, ... imageNN.vtk).

This behavior is configured using the tag <accessmode> and can take two values: single or multiple. For example, to define a input image that will be accessed as a whole 3D+T, you need to set the accessmode to multiple. If the tag <accessmode> is not present, by default is single.

   <image fileExtensions=".vtk">
     <name>fixedImageFileName</name>
     <label>Fixed Image</label>
     <channel>input</channel>
     <index>0</index>
     <accessmode>multiple<accessmode>
     <description>Fixed Image</description>
   </image>

When a command line plugin writes a 3D+T data set to disk, GIMIAS will try to read it using the filename. If the file does not exist, it will try to read it using the 3D+T format. So you don't need to specify the accessmode tag for output parameters.

Single time step processing

GIMIAS allows to execute a filter multiple times for 3D+T input data. The filter will be executed for each time step.

To configure this option, there's a new button at the right of the input data selection control.

MultipleProcessing.png

Using this control, you can select the time step you want to process.

From the command line plugin point of view, no change is required.

Multiple time step processing

GIMIAS transfers the whole 3D+T data set to the command line plugin.

When transferring 3D+T data sets, you must transfer the data by disk and not by memory. To specify this, you need to set the extensions of the file (fileExtensions=".vtk"):

   <image fileExtensions=".vtk">
     <name>fixedImageFileName</name>
     <label>Fixed Image</label>
     <channel>input</channel>
     <index>0</index>
     <accessmode>multiple<accessmode>
     <description>Fixed Image</description>
   </image>

You can see a complete example in Volumetric Mesh Scalar Value CLP.

This configuration requires to read the data file names properly by the command line plugin. This example code allows to read a 3D+T data set. In version 1.5, this code is available in the function blITKFileUtils::CheckDataFilenames() and blITKFileUtils::GenerateDataFilenames().

void blITKFileUtils::CheckDataFilenames( const std::string &filename, std::vector<std::string> &inputFileNames )
{
	bool fileExists = true;
	int count = 0;
	
	fileExists = itksys::SystemTools::FileExists( filename.c_str() );
	if ( fileExists )
	{
		inputFileNames.push_back( filename );
	}
	else
	{

		// Try to read file using filename00.vtk, filename01.vtk, ...
		fileExists = true;
		while ( fileExists )
		{

			std::string path = itksys::SystemTools::GetFilenamePath( filename.c_str( ) );;
			std::string fileName = itksys::SystemTools::GetFilenameWithoutExtension( filename.c_str( ) );
			std::string ext = itksys::SystemTools::GetFilenameExtension( filename.c_str( ) );
			std::stringstream sstream;
			sstream << path << "/" << fileName << std::setfill('0') << std::setw(2) << count << ext;
			fileExists = itksys::SystemTools::FileExists( sstream.str().c_str() );
			if( fileExists )
			{
				inputFileNames.push_back(sstream.str().c_str());
			}
			count++;
		}
	}

}


void blITKFileUtils::GenerateDataFilenames( 
	const std::string &filename, const int size, std::vector<std::string> &outputFileNames )
{
	if ( size == 1 )
	{
		outputFileNames.push_back( filename );
	}
	else
	{

		unsigned int shapeIndex;
		for (shapeIndex = 0; shapeIndex<size; shapeIndex++)
		{
			std::string path = itksys::SystemTools::GetFilenamePath( filename.c_str( ) );;
			std::string fileName = itksys::SystemTools::GetFilenameWithoutExtension( filename.c_str( ) );
			std::string ext = itksys::SystemTools::GetFilenameExtension( filename.c_str( ) );
			std::stringstream sstream;
			sstream << path << "/" << fileName << std::setfill('0') << std::setw(2) << shapeIndex << ext;

			outputFileNames.push_back( sstream.str() );
		}
	}

}

Configure rendering properties of your output data

You can configure the rendering properties of your output data. This allows you to change the opacity or to hide or not the input data when the execution of the CLP finishes.

1. Add a new dependency to your CLP to the library baseLibNumericData:

clp.AddProjects( [generateClp, slicer, baseLibNumericData] )

2. Include this files:

#include "itksys/SystemTools.hxx"
#include "blTagMap.h"
#include "blXMLTagMapWriter.h"

2. Create rendering properties

blTagMap::Pointer metadata = blTagMap::New( );
blTagMap::Pointer rendering = blTagMap::New( );
rendering->AddTag( "opacity", float(0.5) );
rendering->AddTag( "hideParent", false );
metadata->AddTag( "Rendering", rendering );

3. Write GMI file

// Write GMI
std::string path = itksys::SystemTools::GetFilenamePath( outputMesh );
std::string filename = itksys::SystemTools::GetFilenameWithoutExtension( outputMesh );
std::string extension = ".gmi";
std::string gmiFilename = path + "/" + filename + extension;
blXMLTagMapWriter::Pointer gmiWriter = blXMLTagMapWriter::New();
gmiWriter->SetFilename( gmiFilename.c_str() );
gmiWriter->SetInput( metadata );
gmiWriter->Update( );

You can find an example in the Marching_cubes_CLP.

Go back to Developers