Difference between revisions of "Documentation/Nightly/Developers/SlicerExecutionModel/Programmatic Invocation"

From Slicer Wiki
Jump to: navigation, search
(Updated example that calls CLI from C++)
 
(5 intermediate revisions by 4 users not shown)
Line 6: Line 6:
  
 
This example shows calling the Model Maker (from the Editor).  This example is in Tcl, but the same API can be used from C++ or Python.  The idea is to query for the Model Maker module (and bail out if it is not loaded).  Then to create a MRMLNode with the parameters you want for the model maker.  These would be the same parameters you would set interactively.  The model maker logic and gui classes are then told about the node and told to start processing.  The processing then goes on in the background.
 
This example shows calling the Model Maker (from the Editor).  This example is in Tcl, but the same API can be used from C++ or Python.  The idea is to query for the Model Maker module (and bail out if it is not loaded).  Then to create a MRMLNode with the parameters you want for the model maker.  These would be the same parameters you would set interactively.  The model maker logic and gui classes are then told about the node and told to start processing.  The processing then goes on in the background.
 +
 +
''Note: check the command line module xml file for the to know the correct symbols and data types for parameters (see the <name> field for the correct parameter name string).''
  
 
<pre>
 
<pre>
Line 107: Line 109:
 
</pre>
 
</pre>
  
== Module entry points ==
+
When you are calling a shared object module, you need to add another call to initialize the pointer to the module entry point:
 
 
The Tcl example above assumes that the entry point of [http://www.na-mic.org/Slicer/Documentation/Slicer3/html/classModuleDescription.html module description] has been initialized with the correct target. For the shared object modules, description target specifies module entry point as a string. In case if such target has not been initialized properly (i.e., is set to "Unknown"), it has to be initialized manually. Otherwise, module execution will not happen (this can be concluded from the warning ''Module reports that it is a Shared Object Module but does not have a shared object module target'', and the command line will begin with "Unknown" instead of "slicer:0x.....").
 
 
 
Here's how the module entry point can be properly initialized (refer to ProcessGUIEvents() in ''Modules/CommandLineModule/vtkCommandLineModuleGUI.cxx'', Slicer3 trunk). NOTE: this has been tested for the C++ "Linear registration" module.
 
  
 
<pre>
 
<pre>
   ModuleDescription moduleDesc = moduleNode->GetModuleDescription();
+
   ...
 
+
   $modelMaker SetCommandLineModuleNode $moduleNode
   // Set up the temporary directory for the module (not sure why this is not done in Tcl example)
+
   [$modelMaker GetLogic] SetCommandLineModuleNode $moduleNode
   moduleLogic->SetTemporaryDirectory(app->GetTemporaryDirectory());
+
   [$modelMaker GetLogic] LazyEvaluateModuleTarget $moduleNode
 
+
  [$modelMaker GetLogic] Apply $moduleNode
   if(moduleDesc.GetTarget() == "Unknown"){
+
  ...
 
 
    // Entry point is unknown
 
    assert(moduleDesc.GetType() == "SharedObjectModule");
 
    typedef int (*ModuleEntryPoint)(int argc, char* argv[]);
 
    itksys::DynamicLoader::LibraryHandle lib =
 
      itksys::DynamicLoader::OpenLibrary(moduleDesc.GetLocation().c_str());
 
 
 
    if(lib){
 
      ModuleEntryPoint entryPoint =
 
        (ModuleEntryPoint) itksys::DynamicLoader::GetSymbolAddress(
 
          lib, "ModuleEntryPoint");
 
      if(entryPoint){
 
        char entryPointAsText[256];
 
        std::string entryPointAsString;
 
        sprintf(entryPointAsText, "%p", entryPoint);
 
        entryPointAsString = std::string("slicer:")+entryPointAsText;
 
 
 
        // Set target in the description
 
        moduleDesc.SetTarget(entryPointAsString);
 
 
 
        // Important: Update the description in the module node -- description is not a pointer!
 
        moduleNode->SetModuleDescription(moduleDesc);
 
      } else {
 
        std::cerr << "Failed to find entry point. Abort." << std::endl;
 
        abort();
 
      }
 
    } else {
 
      std::cerr << "Failed to locate module library. Abort." << std::endl;
 
      abort();
 
    }
 
  }
 
 
</pre>
 
</pre>
  
Line 162: Line 129:
 
</pre>
 
</pre>
  
The following code shows an example of executing Linear registration CLM from the C++ code. This was done in the context of ChangeTracker Slicer GUI module.
+
The following code shows an example of creating model node from scalar volume node in C++, using Grayscale Model Maker module.
  
 
<pre>
 
<pre>
// Use LinearRegistration module to rigidly align input scans
+
// All data processing should be implemented in my module logic class. However,
void DoITKGlobalRegistration(vtkSlicerApplication *app){
+
// module logic classes cannot directly access other module logic classes. Therefore,
  // Init some useful references
+
// when my module logic is created in my module class, logic of all other modules that
  vtkCommandLineModuleGUI *linearRegistrationGUI = NULL;
+
// my module uses are retrieved and set in my module logic.
   vtkMRMLCommandLineModuleNode *linearRegistrationNode = NULL;
+
//-----------------------------------------------------------------------------
   vtkMRMLScene *scene = this->ChangeTrackerNode->GetScene();
+
vtkMRMLAbstractLogic* qSlicerMyModule::createLogic()
   vtkMRMLChangeTrackerNode *ctNode = this->ChangeTrackerNode;
+
{
  vtkMRMLScalarVolumeNode *outputVolumeNode = NULL;
+
   vtkSlicerMyModuleLogic* moduleLogic = vtkSlicerMyModuleLogic::New();
  vtkMRMLTransformNode *lrTransform = NULL;
+
   qSlicerAbstractCoreModule* gsModelMakerModule = qSlicerCoreApplication::application()->moduleManager()->module("GrayscaleModelMaker");
 
+
   if (gsModelMakerModule)
  linearRegistrationGUI = static_cast<vtkCommandLineModuleGUI*>(app->GetModuleGUIByName("Linear registration"));
+
    {
  if(!linearRegistrationGUI){
+
    vtkSlicerCLIModuleLogic* gsModelMakerLogic = vtkSlicerCLIModuleLogic::SafeDownCast(gsModelMakerModule->logic());
     std::cerr << "Cannot find LinearRegistration module. Aborting." << std::endl;
+
    moduleLogic->SetGrayscaleModelMakerLogic(gsModelMakerLogic);
     abort();
+
    }
  }
+
  else
 
+
    {
   linearRegistrationGUI->Enter();
+
     qCritical() << Q_FUNC_INFO << ": GrayscaleModelMaker module is not found";
 +
     }  
 +
   return moduleLogic;
 +
}
  
   linearRegistrationNode = static_cast<vtkMRMLCommandLineModuleNode*>(scene->CreateNodeByClass("vtkMRMLCommandLineModuleNode"));
+
// This method implements the processing that it is available from the GUI or other modules
  if(!linearRegistrationNode){
+
//-----------------------------------------------------------------------------
    std::cerr << "Cannot create LinearRegistration node. Aborting." << std::endl;
+
vtkMRMLModelNode* vtkSlicerMyModuleLogic::CreateModelByThresholding(vtkMRMLScalarVolumeNode* volumeNode, double threshold)
    abort;
+
{
  }
+
   vtkMRMLScene *scene = this->GetMRMLScene();
  
   // Add node to the scene
+
   // create output node
   scene->AddNode(linearRegistrationNode);
+
   vtkMRMLModelNode* outputNode = vtkMRMLModelNode::SafeDownCast(scene->AddNewNodeByClass("vtkMRMLModelNode", "modelName"));
  // This will actually search and find the initialized description for the node
 
  linearRegistrationNode->SetModuleDescription("Linear registration");
 
  
   // Create output volume node
+
   // create CLI module node and set parameters
   vtkMRMLScalarVolumeNode *scan1Node = static_cast<vtkMRMLScalarVolumeNode*>(scene->GetNodeByID(ctNode->GetScan1_Ref()));
+
   vtkMRMLCommandLineModuleNode* cliNode = this->GrayscaleModelMakerLogic->CreateNodeInScene();
   outputVolumeNode = this->CreateVolumeNode(scan1Node, "TG_scan2_Global");
+
   cliNode->SetParameterAsNode("InputVolume", volumeNode);
   ctNode->SetScan2_GlobalRef(outputVolumeNode->GetID());
+
   cliNode->SetParameterAsNode("OutputGeometry", outputNode);
 +
  cliNode->SetParameterAsFloat("Threshold", threshold);
  
   linearRegistrationNode->SetParameterAsString("FixedImageFileName",
+
   // run processing (wait until it is completed)
    ctNode->GetScan1_Ref());
+
  this->GrayscaleModelMakerLogic->ApplyAndWait(cliNode);
  
   linearRegistrationNode->SetParameterAsString("MovingImageFileName",
+
   // clean up temporary node
    ctNode->GetScan2_Ref());
+
  scene->RemoveNode(cliNode);
  
   linearRegistrationNode->SetParameterAsString("ResampledImageFileName",
+
   return outputNode;
    ctNode->GetScan2_GlobalRef());
+
}
 
 
  vtkCommandLineModuleLogic *moduleLogic = linearRegistrationGUI->GetLogic();
 
 
 
  linearRegistrationGUI->SetCommandLineModuleNode(linearRegistrationNode);
 
  linearRegistrationGUI->GetLogic()->SetCommandLineModuleNode(linearRegistrationNode);
 
 
 
  // Now check and initialize the module entry point, if necessary
 
  ModuleDescription moduleDesc =
 
    linearRegistrationNode->GetModuleDescription();
 
  moduleLogic->SetTemporaryDirectory(app->GetTemporaryDirectory());
 
  if(moduleDesc.GetTarget() == "Unknown"){
 
    // Entry point is unknown
 
    // "Linear registration" is shared object module, at least at this moment
 
    assert(moduleDesc.GetType() == "SharedObjectModule");
 
    typedef int (*ModuleEntryPoint)(int argc, char* argv[]);
 
    itksys::DynamicLoader::LibraryHandle lib =
 
      itksys::DynamicLoader::OpenLibrary(moduleDesc.GetLocation().c_str());
 
    if(lib){
 
      ModuleEntryPoint entryPoint =
 
        (ModuleEntryPoint) itksys::DynamicLoader::GetSymbolAddress(lib, "ModuleEntryPoint");
 
 
 
      if(entryPoint){
 
        char entryPointAsText[256];
 
        std::string entryPointAsString;
 
 
 
        sprintf(entryPointAsText, "%p", entryPoint);
 
        entryPointAsString = std::string("slicer:")+entryPointAsText;
 
        moduleDesc.SetTarget(entryPointAsString);
 
        linearRegistrationNode->SetModuleDescription(moduleDesc);
 
      } else {
 
        std::cerr << "Failed to find entry point for Linear registration. Abort." << std::endl;
 
        abort();
 
      }
 
    } else {
 
      std::cerr << "Failed to locate module library. Abort." << std::endl;
 
      abort();
 
    }
 
  }
 
 
 
  linearRegistrationGUI->GetLogic()->Apply(linearRegistrationNode);
 
 
 
  std::cout << "Linear registration module started..." << std::endl;
 
 
 
  linearRegistrationNode->Delete();
 
  
  // IMPORTANT: If you delete GUI object, as Tcl example above advices, Slicer will segfault at exit,
+
// Call the logic method from my module widget
  // since GUI objects of this kind are deleted in Slicer3.cxx, line 1986. I would say, don't do it.
+
//-----------------------------------------------------------------------------
  // linearRegistrationGUI->Delete();
+
vtkSlicerMyModuleLogic* myLogic = vtkSlicerMyModuleLogic::SafeDownCast(this->logic());
}
+
vtkMRMLModelNode* outputModel = logic->CreateModelByThresholding(inputVolumeNode, threshold);
 
</pre>
 
</pre>
 
  
 
== Python Example ==
 
== Python Example ==
Line 266: Line 190:
 
import Slicer
 
import Slicer
 
from Slicer import slicer
 
from Slicer import slicer
volume1 = slicer.MRMLScene.GetNodeByID(“vtkMRMLVolumeNode1”)
+
volume1 = slicer.MRMLScene.GetNodeByID(“vtkMRMLScalarVolumeNode1”)
volume2 = slicer.MRMLScene.GetNodeByID(“vtkMRMLVolumeNode2”)
+
volume2 = slicer.MRMLScene.GetNodeByID(“vtkMRMLScalarVolumeNode2”)
 
plugin = Slicer.Plugin(“Subtract Images”)
 
plugin = Slicer.Plugin(“Subtract Images”)
plugin.Execute(volume1.GetID(),volume2.GetID()
+
plugin.Execute(volume1,volume2)
 
</pre>
 
</pre>
  

Latest revision as of 15:03, 6 August 2019

Home < Documentation < Nightly < Developers < SlicerExecutionModel < Programmatic Invocation

Goal

A large amount of functionality is available in command line modules for processing images and models. Some applications would like to use several modules together in a workflow or as a background processing task.

Implementation Example

This example shows calling the Model Maker (from the Editor). This example is in Tcl, but the same API can be used from C++ or Python. The idea is to query for the Model Maker module (and bail out if it is not loaded). Then to create a MRMLNode with the parameters you want for the model maker. These would be the same parameters you would set interactively. The model maker logic and gui classes are then told about the node and told to start processing. The processing then goes on in the background.

Note: check the command line module xml file for the to know the correct symbols and data types for parameters (see the <name> field for the correct parameter name string).


  #
  # create a model using the command line module
  # based on the current editor parameters
  #

  #
  # get the image data for the label layer
  #
  set sliceLogic [lindex [vtkSlicerSliceLogic ListInstances] 0]
  set layerLogic [$sliceLogic GetLabelLayer]
  set volumeNode [$layerLogic GetVolumeNode]
  if { $volumeNode == "" } {
    errorDialog "Cannot make model - no volume node for $layerLogic in $sliceLogic."
    return
  }

  #
  # find the Model Maker
  # - call Enter to be sure GUI has been built
  # - find the GUI - for non-tcl use, try vtkSlicerApplication::GetModuleGUIByName
  set modelMaker ""
  foreach gui [vtkCommandLineModuleGUI ListInstances] {
    if { [$gui GetGUIName] == "Model Maker" } {
      set modelMaker $gui
    }
  }

  if { $modelMaker == "" } {
    errorDialog "Cannot make model: no Model Maker Module found."
  }

  $modelMaker Enter

  #
  # set up the model maker node
  #
  set moduleNode [$::slicer3::MRMLScene CreateNodeByClass "vtkMRMLCommandLineModuleNode"]
  $::slicer3::MRMLScene AddNode $moduleNode
  $moduleNode SetModuleDescription "Model Maker"

  $moduleNode SetParameterAsString "Name" "Quick Model"
  $moduleNode SetParameterAsString "FilterType" "Sinc"
  $moduleNode SetParameterAsBool "GenerateAll" "0"
  $moduleNode SetParameterAsString "Labels" [EditorGetPaintLabel]
  $moduleNode SetParameterAsBool "JointSmooth" 1
  $moduleNode SetParameterAsBool "SplitNormals" 1
  $moduleNode SetParameterAsBool "PointNormals" 1
  $moduleNode SetParameterAsBool "SkipUnNamed" 1
  $moduleNode SetParameterAsInt "Start" -1
  $moduleNode SetParameterAsInt "End" -1
  if { [[$o(smooth) GetWidget] GetSelectedState] } {
    $moduleNode SetParameterAsDouble "Decimate" 0.25
    $moduleNode SetParameterAsDouble "Smooth" 10
  } else {
    $moduleNode SetParameterAsDouble "Decimate" 0
    $moduleNode SetParameterAsDouble "Smooth" 0
  }

  $moduleNode SetParameterAsString "InputVolume" [$volumeNode GetID]

  #
  # output 
  # - make a new hierarchy node if needed
  #
  set outHierarchy [[$::slicer3::MRMLScene GetNodesByClassByName "vtkMRMLModelHierarchyNode" "Editor Models"] GetItemAsObject 0]
  if { $outHierarchy == "" } {
    set outHierarchy [$::slicer3::MRMLScene CreateNodeByClass "vtkMRMLModelHierarchyNode"]
    $outHierarchy SetScene $::slicer3::MRMLScene
    $outHierarchy SetName "Editor Models"
    $::slicer3::MRMLScene AddNode $outHierarchy
  }

  $moduleNode SetParameterAsString "ModelSceneFile" [$outHierarchy GetID]



  # 
  # run the task (in the background)
  # - use the GUI to provide progress feedback
  # - use the GUI's Logic to invoke the task
  # - model will show up when the processing is finished
  #
  $modelMaker SetCommandLineModuleNode $moduleNode
  [$modelMaker GetLogic] SetCommandLineModuleNode $moduleNode
  [$modelMaker GetLogic] Apply $moduleNode

  $this statusText "Model Making Started..."

  #
  # clean up our references
  #
  $moduleNode Delete
  $outHierarchy Delete
  $modelMaker Enter


When you are calling a shared object module, you need to add another call to initialize the pointer to the module entry point:

  ...
  $modelMaker SetCommandLineModuleNode $moduleNode
  [$modelMaker GetLogic] SetCommandLineModuleNode $moduleNode
  [$modelMaker GetLogic] LazyEvaluateModuleTarget $moduleNode
  [$modelMaker GetLogic] Apply $moduleNode
  ...

C++ example

Update the CMakeLists.txt to include

${CommandLineModule_SOURCE_DIR}
${CommandLineModule_BINARY_DIR}

The following code shows an example of creating model node from scalar volume node in C++, using Grayscale Model Maker module.

// All data processing should be implemented in my module logic class. However,
// module logic classes cannot directly access other module logic classes. Therefore,
// when my module logic is created in my module class, logic of all other modules that
// my module uses are retrieved and set in my module logic.
//-----------------------------------------------------------------------------
vtkMRMLAbstractLogic* qSlicerMyModule::createLogic()
{
  vtkSlicerMyModuleLogic* moduleLogic = vtkSlicerMyModuleLogic::New();
  qSlicerAbstractCoreModule* gsModelMakerModule = qSlicerCoreApplication::application()->moduleManager()->module("GrayscaleModelMaker");
  if (gsModelMakerModule)
    {
    vtkSlicerCLIModuleLogic* gsModelMakerLogic = vtkSlicerCLIModuleLogic::SafeDownCast(gsModelMakerModule->logic());
    moduleLogic->SetGrayscaleModelMakerLogic(gsModelMakerLogic);
    }
  else
    {
    qCritical() << Q_FUNC_INFO << ": GrayscaleModelMaker module is not found";
    } 
  return moduleLogic;
} 

// This method implements the processing that it is available from the GUI or other modules
//-----------------------------------------------------------------------------
vtkMRMLModelNode* vtkSlicerMyModuleLogic::CreateModelByThresholding(vtkMRMLScalarVolumeNode* volumeNode, double threshold)
{
  vtkMRMLScene *scene = this->GetMRMLScene();

  // create output node
  vtkMRMLModelNode* outputNode = vtkMRMLModelNode::SafeDownCast(scene->AddNewNodeByClass("vtkMRMLModelNode", "modelName"));

  // create CLI module node and set parameters
  vtkMRMLCommandLineModuleNode* cliNode = this->GrayscaleModelMakerLogic->CreateNodeInScene();
  cliNode->SetParameterAsNode("InputVolume", volumeNode);
  cliNode->SetParameterAsNode("OutputGeometry", outputNode);
  cliNode->SetParameterAsFloat("Threshold", threshold);

  // run processing (wait until it is completed)
  this->GrayscaleModelMakerLogic->ApplyAndWait(cliNode);

  // clean up temporary node
  scene->RemoveNode(cliNode);

  return outputNode;
}

// Call the logic method from my module widget
//-----------------------------------------------------------------------------
vtkSlicerMyModuleLogic* myLogic = vtkSlicerMyModuleLogic::SafeDownCast(this->logic());
vtkMRMLModelNode* outputModel = logic->CreateModelByThresholding(inputVolumeNode, threshold);

Python Example

The following example calls the Substract Images command line module directly from Python (presented by Luca Antiga at the AHM 2009).

import Slicer
from Slicer import slicer
volume1 = slicer.MRMLScene.GetNodeByID(“vtkMRMLScalarVolumeNode1”)
volume2 = slicer.MRMLScene.GetNodeByID(“vtkMRMLScalarVolumeNode2”)
plugin = Slicer.Plugin(“Subtract Images”)
plugin.Execute(volume1,volume2)

The next snippet calls the Grayscale Model Maker on a provided vtkMRMLScalarVolumeNode named outVolume and saves the generated gray scale model to a provided vtkMRMLModelNode named outGeometry.

import Slicer
from Slicer import slicer
# get or create the outVolume and outGeometry nodes
plugin = Slicer.Plugin("Grayscale Model Maker")
plugin.Execute(outVolume,outGeometry,threshold=10,smooth=15,decimate=0.25,splitnormals=True,pointnormals=True)

In both cases the called command line module will run hidden.