CTK  0.1.0
The Common Toolkit is a community effort to provide support code for medical image analysis, surgical navigation, and related projects.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
CTK Command Line Modules

Overview about the Command Line Modules support in CTK.

CTK provides an API for interfacing with self-describing runnable modules which can provide an XML description of their supported parameters. A runnable module is usually (but not constrained to) a local executable and also referred to as a command line module.

The XML schema for the parameter description and most of the supported feature set for a module has been adopted from the Slicer Execution Model.

The API provided by CTK allows the management, GUI generation, and asynchronous execution of such modules in a toolkit-independent and interoperable way. Application writers can rely on the provided libraries and their API to quickly integrate command line modules into their applications.

Developers who want to create command line modules which can be run by using the provided tools will want to have a look at the Creating Modules section below. Everything else targets application writers who want to host and mange these command line modules.

CTK also comes with an example application, called ctkCommandLineModuleExplorer which can be used to load different kinds of modules, to verify their correctness, to run - and finally inspect their output.

Features

Here is short overview about the provided feature set:

CTK also provides stable and feature-rich implementations of a Qt based front-end and a back-end handling local processes.

Qt Gui front-end

The provided front-end implementation creates a Qt widgets based user interface. It also allows to customize the GUI generation process, see the ctkCmdLineModuleFrontendQtGui class for more information.

Local process back-end

The default back-end for running modules can handle local executables and runs them in a separate process. See the ctkCmdLineModuleBackendLocalProcess class for details.

Creating Modules

Module writers usually need to create an XML file describing the parameters which are understood by the module (the actual way how such an XML description is provided actually depends on the back-end for which the module is written). For locally executable modules (e.g. command line programs), the XML description is usually emitted to the standard output channel by the executable itself when it is called with a *–xml* command line argument.

The valid XML structure for the parameter description is defined in the corresponding schema documentation (absolute link).

Please note that running a module may fail due to an invalid XML description. The strictness of validation is specific to the application you are using to run the module. However, making sure the XML validates agains the given schema (raw schema file here).

Progress and Result reporting

A module may report progress and intermediate results during its execution. The actual reporting mechanism depends on the type of module. For a local executable being run for example by the ctkCmdLineModuleBackendLocalProcess back-end, reporting is done by printing XML fragments to the standard output channel.

For example a progress report containing a progress value and text would look like:

<filter-start>
  <filter-name>My Filter</filter-name>
  <filter-comment>Starting custom filter...</filter-comment>
</filter-start>
<filter-progress-text progress="0.2">Current progress: 0.2 (from [0,1.0])</filter-progress-text>
<filter-end/>

Here is the XML progress and result schema documentation (absolute link) describing the valid XML fragments. The raw schema file is available here.

Library Design

The Command Line Module support consists of a Core library and so-called Backends and Frontends.

A front-end, a sub-class of ctkCmdLineModuleFrontend, represents a set of parameter values for a specific module, usually associated with some kind of user interface. Front-end implementations need not be accessible outside of the defining library, but may be exposed to allow the configuration of the GUI generation process by sub-classing the corresponding ctkCmdLineModuleFrontendFactory implementation. A front-end can be "run" by calling the ctkCmdLineModuleManager::run(ctkCmdLineModuleFrontend*) method and the object returned by the run() method is used to communicate with the running module. A front-end can be "run" multiple times (with possibly different parameter values) simultaneously.

A back-end, a sub-class of ctkCmdLineModuleBackend, knows how to actually "run" a module. Back-end implementations express their capabilities by overriding the ctkCmdLineModuleBackend::schemes() method and providing a list of URL schemes this back-end can handle. For example, the ctkCmdLineModuleBackendLocalProcess back-end returns "file" since it can handle URLs pointing to local resources (executables). Further, a back-end knows how to get a time-stamp and the module XML description for a specific module.

The central class for managing modules is the ctkCmdLineModuleManager. There must be at least one back-end registered with the manager for module registrations to succeed. A module is registered by calling the ctkCmdLineModuleManager::registerModule(const QUrl&) method, providing the URL to the module. If the URL scheme is not handled by a previously registerd back-end, an exception is thrown. If registration succeeds, the method returns a ctkCmdLineModuleReference object.

Creating specific front-ends for a given module is actually independent of the ctkCmdLineModuleManager, except that a ctkCmdLineModuleReference object is needed. To create a front-end, usually the ctkCmdLineModuleFrontendFactory::create(const ctkCmdLineModuleReference&) method is called, returning a ctkCmdLineModuleFrontend pointer.

This separation of concerns in front and back ends allows for an extensible and flexible design. Front-ends and back-ends work independent of each other and can be combined arbitrarly.

Quick Start

Here is a small code example to get you started quickly. We first instantiate a ctkCmdLineModuleManager object, using a strict validation mode and the built-in caching mechanism.

// Instantiate a ctkCmdLineModuleManager class.
ctkCmdLineModuleManager moduleManager(
// Use "strict" validation mode, rejecting modules with non-valid XML descriptions.
// Use the default cache location for this application
#if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
QDesktopServices::storageLocation(QDesktopServices::CacheLocation)
#else
QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
#endif
);

Next, we instantiate and register a back-end.

// Instantiate a back-end for running executable modules in a local process.
// This back-end handles the "file" Url scheme.
QScopedPointer<ctkCmdLineModuleBackend> processBackend(new ctkCmdLineModuleBackendLocalProcess);
// Register the back-end with the module manager.
moduleManager.registerBackend(processBackend.data());

Now we register an executable as a module with the manager.

try
{
// Register a local executable as a module, the ctkCmdLineModuleBackendLocalProcess
// can handle it.
moduleRef = moduleManager.registerModule(QUrl::fromLocalFile("C:/modules/MyModule.exe"));
}
catch (const ctkInvalidArgumentException& e)
{
// Module validation failed.
qDebug() << e;
return EXIT_FAILURE;
}

To create a front-end, we use the Qt widgets implementation.

// We use the "Qt Gui" frontend factory.
QScopedPointer<ctkCmdLineModuleFrontendFactory> frontendFactory(new ctkCmdLineModuleFrontendFactoryQtGui);
myApp.addLibraryPath(QCoreApplication::applicationDirPath() + "/../");
QScopedPointer<ctkCmdLineModuleFrontend> frontend(frontendFactory->create(moduleRef));
// Create the actual GUI representation.
QWidget* gui = qobject_cast<QWidget*>(frontend->guiHandle());

Last, we run the front-end instance, using the default values for the module parameters.

try
{
ctkCmdLineModuleFuture future = moduleManager.run(frontend.data());
future.waitForFinished();
qDebug() << "Console output:";
qDebug() << future.readAllOutputData();
qDebug() << "Error output:";
qDebug() << future.readAllErrorData();
qDebug() << "Results:";
qDebug() << future.results();
}
{
qWarning() << e;
}

After the ctkCmdLineModuleManager::run() method returns, we wait for the running module to finish and print out some data reported by it.