Building a 3D Slicer CLI Module from Scratch Using ITK and SlicerExecutionModel
Building a 3D Slicer CLI Module from Scratch Using ITK and SlicerExecutionModel
3D Slicer is a powerful open-source platform widely used for medical image analysis, visualization, and research. One of its most powerful features is the ability to extend its functionality using custom modules.
In this tutorial we will build a Slicer Command Line Interface (CLI) module from scratch using:
- ITK (Insight Toolkit) for image processing
- SlicerExecutionModel for module integration
- CMake for building the project
By the end of this tutorial you will understand:
- How Slicer CLI modules work
- How to configure projects using CMake
- What
GenerateCLPis and why it is needed - How to build a module from scratch
- How to load the module into Slicer
Table of Contents
- Introduction
- Understanding CLI Modules in Slicer
- Installing Dependencies
- Understanding CMake
- Building ITK
- Building SlicerExecutionModel
- Project Structure
- Understanding GenerateCLP
- Writing the XML Interface
- Writing the Thresholding Algorithm
- Writing the CMake Configuration
- Building the Project
- Running the Module
- Loading the Module in Slicer
- Conclusion
1. Introduction
Slicer modules can be implemented in three ways:
| Type | Language | Description |
|---|---|---|
| Scripted modules | Python | Fast development |
| Loadable modules | C++ | High performance |
| CLI modules | C++ executable | Simple integration with automatic GUI |
In this tutorial we focus on CLI modules, which are ideal when you want to:
- Integrate an existing C++ or ITK algorithm
- Automatically generate a GUI
- Allow the algorithm to run both inside and outside Slicer
2. Understanding CLI Modules in Slicer
A Slicer CLI module consists of three components:
XML description
C++ algorithm
CMake build system
Workflow:
XML → GenerateCLP → C++ executable → Slicer GUI
Steps:
- XML describes the interface
-
GenerateCLPgenerates command-line parsing code - C++ implements the algorithm
- Slicer loads the executable and automatically creates a GUI
A major advantage of CLI modules is that the user interface and the algorithm are separated. You describe the interface once in XML, and SlicerExecutionModel handles the tedious command-line parsing and GUI generation for you.
3. Installing Dependencies
You will need:
- CMake
- ITK
- SlicerExecutionModel
Clone the repositories:
git clone https://github.com/InsightSoftwareConsortium/ITK.git
git clone https://github.com/Slicer/SlicerExecutionModel.git
If you also want to test the module inside Slicer, make sure you already have a working 3D Slicer installation on your machine.
4. Understanding CMake
CMake is a cross-platform build system generator. Instead of manually writing platform-specific build files, we write one or more CMakeLists.txt files that describe:
- Source files
- Libraries
- Dependencies
- Include directories
- Build rules
CMake then generates the actual native build system for your platform.
For example, CMake can generate:
| Generator | Typical Use |
|---|---|
| Unix Makefiles | Standard Linux/macOS terminal builds |
| Ninja | Faster incremental builds |
| Visual Studio | Windows projects |
| Xcode | macOS IDE projects |
Typical CMake workflow
Source code → CMake configure step → Generated build system → Compiler
The modern and clean way to configure a project is:
cmake -S . -B build
Here:
-
-S .means the source directory is the current directory -
-B buildmeans the build files will be generated in thebuilddirectory
Then build with:
cmake --build build
This is preferred over building directly inside the source tree because it keeps generated files separate from your source code.
Choosing a generator
You can explicitly choose a generator if needed:
cmake -S . -B build -G "Unix Makefiles"
or
cmake -S . -B build -G Ninja
If you use Unix Makefiles, you may also build with:
make -C build -j8
If you use Ninja, you may build with:
ninja -C build
Using an interactive CMake interface
If you prefer an interactive terminal interface, you can use:
ccmake -S . -B build
This is helpful when you want to inspect or modify CMake cache variables such as:
ITK_DIRSlicerExecutionModel_DIR- build options
- compiler settings
There is also a graphical interface:
cmake-gui
In short, CMake is necessary because it finds dependencies, configures the project, and generates the correct build files for your platform and compiler.
5. Building ITK
Create a build directory:
mkdir ITK-build
Configure:
cmake -S ./ITK -B ./ITK-build
Build:
cmake --build ./ITK-build
If you are using Makefiles, you can also build with multiple CPU cores:
make -C ./ITK-build -j8
On Linux, you can check the number of available CPU cores with:
nproc
After building, remember the path to:
ITK-build
You will use this later when configuring your CLI module project.
6. Building SlicerExecutionModel
Create a build directory:
mkdir SEM-build
Configure:
cmake -S ./SlicerExecutionModel -B ./SEM-build
Build:
cmake --build ./SEM-build
If you are using Makefiles, you can also build with:
make -C ./SEM-build -j8
One important output of this build is the tool:
GenerateCLP
This tool is central to how Slicer CLI modules work.
7. Project Structure
Our project will look like this:
project-root
│
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── SimpleThreshold
├── CMakeLists.txt
├── SimpleThreshold.cxx
└── SimpleThreshold.xml
This structure keeps the project organized:
- the root directory manages the whole project
-
srccollects source modules -
src/SimpleThresholdcontains one CLI module
8. Understanding GenerateCLP
GenerateCLP is a tool provided by SlicerExecutionModel.
Its job is to read your module XML file and automatically generate C++ code for command-line parsing. Instead of writing your own parser for arguments such as:
--inputVolume--outputVolume--lowThreshold--highThreshold
you define them once in XML, and GenerateCLP creates the corresponding parser code for you.
For example, suppose your XML contains:
<double>
<name>lowThreshold</name>
<longflag>lowThreshold</longflag>
<default>0</default>
</double>
From this, GenerateCLP generates support code so that your C++ program can directly use:
lowThreshold
It also generates a header file named after your module. In this tutorial that file is:
SimpleThresholdCLP.h
Inside your .cxx file, you include it:
#include "SimpleThresholdCLP.h"
and then call:
PARSE_ARGS;
This macro parses the command-line arguments based on the XML description and populates the generated variables automatically.
So GenerateCLP is needed because it:
- avoids manual argument parsing
- keeps the XML and the executable interface synchronized
- enables Slicer to generate the GUI automatically from the same interface description
In practice, you usually do not run GenerateCLP manually. Instead, the CMake macro SEMMacroBuildCLI(...) calls it for you during the build.
9. Writing the XML Interface
The XML file defines:
- Module category
- Visible module title
- Inputs
- Outputs
- Parameters
Example:
<?xml version="1.0" encoding="utf-8"?>
<executable>
<category>MyModules</category>
<title>SimpleThreshold</title>
<description>
Simple binary thresholding using ITK.
</description>
<parameters>
<label>Input/Output</label>
<image>
<name>inputVolume</name>
<longflag>inputVolume</longflag>
<label>Input Volume</label>
<channel>input</channel>
<description>Input image volume</description>
</image>
<image>
<name>outputVolume</name>
<longflag>outputVolume</longflag>
<label>Output Volume</label>
<channel>output</channel>
<description>Output thresholded image volume</description>
</image>
</parameters>
<parameters>
<label>Threshold Parameters</label>
<double>
<name>lowThreshold</name>
<longflag>lowThreshold</longflag>
<label>Low Threshold</label>
<description>Lower threshold value</description>
<default>0</default>
</double>
<double>
<name>highThreshold</name>
<longflag>highThreshold</longflag>
<label>High Threshold</label>
<description>Upper threshold value</description>
<default>0</default>
</double>
<double>
<name>insideValue</name>
<longflag>insideValue</longflag>
<label>Inside Value</label>
<description>Value assigned to voxels inside the threshold range</description>
<default>1</default>
</double>
<double>
<name>outsideValue</name>
<longflag>outsideValue</longflag>
<label>Outside Value</label>
<description>Value assigned to voxels outside the threshold range</description>
<default>0</default>
</double>
</parameters>
</executable>
Brief explanation of the XML
This XML defines the interface of the CLI module.
-
<category>controls where the module appears in Slicer -
<title>controls the displayed module name -
<parameters>groups related parameters together in the GUI -
<image>defines image inputs and outputs -
<double>defines numeric parameters -
<name>becomes the generated variable name in C++ -
<longflag>defines the command-line flag name
Because we use:
<category>MyModules</category>
the module will appear under:
Modules → MyModules → SimpleThreshold
This same XML is used both to build the GUI in Slicer and to generate the parser used by the executable.
10. Writing the Thresholding Algorithm
Now we implement the actual algorithm using ITK.
SimpleThreshold.cxx
#include "itkImage.h"
#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"
#include "itkBinaryThresholdImageFilter.h"
#include <cstdlib>
#include "SimpleThresholdCLP.h"
int main(int argc, char * argv[])
{
PARSE_ARGS;
constexpr unsigned int Dimension = 3;
using PixelType = short;
using ImageType = itk::Image<PixelType, Dimension>;
using ReaderType = itk::ImageFileReader<ImageType>;
using WriterType = itk::ImageFileWriter<ImageType>;
using ThresholdFilterType =
itk::BinaryThresholdImageFilter<ImageType, ImageType>;
auto reader = ReaderType::New();
reader->SetFileName(inputVolume);
auto thresholder = ThresholdFilterType::New();
thresholder->SetInput(reader->GetOutput());
thresholder->SetLowerThreshold(static_cast<PixelType>(lowThreshold));
thresholder->SetUpperThreshold(static_cast<PixelType>(highThreshold));
thresholder->SetInsideValue(static_cast<PixelType>(insideValue));
thresholder->SetOutsideValue(static_cast<PixelType>(outsideValue));
auto writer = WriterType::New();
writer->SetFileName(outputVolume);
writer->SetInput(thresholder->GetOutput());
writer->Update();
return EXIT_SUCCESS;
}
Brief explanation of the thresholding code
This code performs binary thresholding on a 3D image. It sets every pixel in the range [lowThreshold, highThreshold] to insideValue and all other pixels to the outsideValue.
-
PARSE_ARGS;loads the parameters defined in the XML -
Dimension = 3means we are processing a 3D volume -
PixelType = shortmeans the image stores signed-short intensities -
ImageFileReaderreads the input image -
BinaryThresholdImageFilterperforms the thresholding -
SetLowerThreshold(...)andSetUpperThreshold(...)define the valid intensity range -
SetInsideValue(...)assigns a value to voxels inside the threshold range -
SetOutsideValue(...)assigns a value to voxels outside the threshold range -
ImageFileWriterwrites the output image
Because ITK chooses the file format from the output filename extension, using:
output.nii.gz
will save the result in NIfTI format.
11. Writing the CMake Configuration
Root CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(SimpleThresholdModule LANGUAGES CXX)
find_package(ITK REQUIRED)
include(${ITK_USE_FILE})
find_package(SlicerExecutionModel REQUIRED GenerateCLP)
include(${GenerateCLP_USE_FILE})
include(${SlicerExecutionModel_USE_FILE})
include(${SlicerExecutionModel_CMAKE_DIR}/SEMMacroBuildCLI.cmake)
add_subdirectory(src)
Brief explanation
This file configures the overall project.
-
cmake_minimum_required(...)sets the minimum required CMake version -
project(...)names the project and declares the language -
find_package(ITK REQUIRED)finds the ITK installation -
include(${ITK_USE_FILE})brings in ITK compiler and include settings -
find_package(SlicerExecutionModel REQUIRED GenerateCLP)finds SlicerExecutionModel and theGenerateCLPtool - the
include(...)lines expose the macros needed for building CLI modules -
add_subdirectory(src)tells CMake to continue processing the source tree
src/CMakeLists.txt
add_subdirectory(SimpleThreshold)
Brief explanation
This file simply tells CMake to process the SimpleThreshold module directory.
Module CMakeLists.txt
set(MODULE_NAME SimpleThreshold)
SEMMacroBuildCLI(
NAME ${MODULE_NAME}
TARGET_LIBRARIES ${ITK_LIBRARIES}
EXECUTABLE_ONLY
)
Brief explanation
This file builds the actual CLI module.
-
MODULE_NAMEdefines the module name -
SEMMacroBuildCLI(...)is a helper macro from SlicerExecutionModel - it runs
GenerateCLPonSimpleThreshold.xml - it generates
SimpleThresholdCLP.h - it builds the executable
- it links the executable against ITK
12. Building the Project
First configure the project:
cmake -S . -B build \
-DITK_DIR=/path/to/ITK-build \
-DSlicerExecutionModel_DIR=/path/to/SEM-build
Then build it:
cmake --build build
If your generator is Unix Makefiles, you can also build with:
make -C build -j8
If you want an interactive configuration interface, you can use:
ccmake -S . -B build
The executable will typically appear in:
build/src/SimpleThreshold/bin
13. Running the Module
Run from the command line:
SimpleThreshold \
--inputVolume input.nii.gz \
--outputVolume output.nii.gz \
--lowThreshold 100 \
--highThreshold 500 \
--insideValue 1 \
--outsideValue 0
This will write the thresholded image to output.nii.gz.
You can also inspect the generated command-line interface using:
./SimpleThreshold --help
14. Loading the Module in Slicer
Start Slicer with:
Slicer --additional-module-paths /path/to/build/src/SimpleThreshold/bin
Your module will appear in:
Modules → MyModules → SimpleThreshold
The GUI is generated automatically from the XML file.
If Slicer cannot save additional module paths in its settings, launching it with --additional-module-paths is a reliable workaround during development.
15. Conclusion
In this tutorial we built a complete Slicer CLI module from scratch using:
- ITK
- SlicerExecutionModel
- CMake
Key takeaways:
- CLI modules separate interface, algorithm, and build system
- XML defines the user interface
-
GenerateCLPhandles argument parsing automatically - ITK implements the image processing
- CMake manages dependencies and builds the executable
- Useful links:
This workflow makes it much easier to integrate custom medical image processing algorithms into 3D Slicer without manually building a GUI.
Congratulations! You can create a Slicer module using your custom algorithm.