sMILX Help

Devloper Guide


sMILX has a very simple and lightweight Application Programming Interface (API) and plugin system, which are both easy to use and extend.

Extending sMILX

Plugins

sMILX can be extended using separately licensed and distributed custom plugins. The plugins can be classed into three types and their combinations (ordered by the amount of work required to setup):
  1. An extension - a very simple plugin which implements just a few features accessible from the right click menu. The features are implemented as overloaded or new members of sub-classed versions of the milxQtImage or milxQtModel classes. The denoise plugin is an example of this type.
  2. A docked window - a plugin which had docked display. The Python plugin is an example of this type.
  3. An integrated plugin - a plugin which provides file I/O support and a new display window typically by sub-classing milxQtRenderWindow. The shape model plugin is an example of this type.

Introduction

The plugins are sub-classes of the milxQtPluginInterface class. sMILX loads all shared object files (or dynamic libraries) as milxQtPluginInterface objects found in the 'plugins' folder. Therefore, your plugin simply needs to 'look' like this class. This is achieved by sub-classing milxQtPluginInterface and ensuring all members are defined, but not necessarily implemented. Then its just a case of having a consistent name for the class, setting the Boolean flags to tell sMILX what the plugin has available and subclassing either milxQtModel or milxQtImage. The next section illustrates this proceedure by creating a Diffusion Tensor Imaging (DTI) extension.

Adding a new extension for sMILX

The fastest way to setup an extension is to modify some existing code. You can modify the denoise plugin (found in plugin/deNoise), use the plugin guide or the plugin interface class directly to ensure getting the latest and most well documented interface. In all cases, try supplementing your method with the following:
  1. Create a directory called 'dti' (or the name of your plugin) in the 'plugin' directory, copy the milxQtPluginInterface and rename it to 'milxQtDiffusionTensorPlugin' or equivalent.
  2. Open the file in you favourite editor and find/replace 'PluginInterface' with 'DiffusionTensorPlugin'. You may want to deal with the guard block separately ensuring the use of capitals. Make sure to add the include for milxQtPluginInterface and remove the empty constructor and destructor (including the QThread reference) with milxQtPluginInterface.
  3. Change the sub-classing to inherit from milxQtPluginInterface, something like changing from 'milxQtPluginInterface : public QThread' to 'milxQtDiffusionTensorPlugin : public milxQtPluginInterface'. Also, remove the '= 0' of all members except the ones in the 'public slots', which can be replaced with empty {}. Lastly, remove all the non-virtual members, signals and the private members as they are inheritted now.
  4. Change the plugin factory code near the bottom of the header to something like (ensuring the removal of the Q_DECLARE_INTERFACE line):
    class MILXQT_PLUGIN_EXPORT milxQtDiffusionTensorPluginFactory: public QObject,
    public milxQtPluginFactory
    {
        Q_OBJECT
        Q_INTERFACES(milxQtPluginFactory)
    public:
        milxQtPluginInterface* newPlugin(QObject *theParent = 0)
        {   return new milxQtDiffusionTensorPlugin(theParent);  }
    };
    Only two places required changing the 'DeNoise' string with DiffusionTensor. That concludes the header changes.
  5. Copy the CMakeLists.txt from the denoise plugin and replace 'deNoise' with 'DTI' or equivalent. Remember that the plugin header and source files are called 'milxQtDiffusionTensorPlugin'. The name is important here as it must match the name you will give later in the plugin source file. Remove references to other headers and source files, you will eventually add your own here. We can also remove the VTK or ITK references here until we need them later. However, Qt references are required so don't remove them.
  6. Lastly, we need the plugin source file. You can copy and modify the denoise version or create your own. The former is less error prone and when doing the latter ensure that all virtual members are implemented even if their stubs. To modify the denoise source, rename and find/replace as we did before for the header file.
  7. Ensure that the export plugin (last) line looks like:
    Q_EXPORT_PLUGIN2(DTIPlugin, milxQtDiffusionTensorPluginFactory);
    Notice that the first argument is the same name as the plugin in the CMakeLists.txt file. The second argument is the plugin factory class found in the header. For the moment, we will comment out the 'createConnections', 'run()' and 'milxQtDeNoisePlugin::pluginWindow' members fully, and comment the code inside the constructor, 'modelResult()' member, 'isPluginWindow()' member and the 'milxQtDiffusionTensorPlugin::loadExtension()' member.
  8. Ensure that all virtual members have an implementation in either the source file or the header file. You may need to copy some inline implementations from the denoise plugin header file too, such as the'hasOpenSupport()' member etc.
  9. Finally, add the build sub-directory directive in the plugin CMakeLists.txt file as
    if(BUILD_DTI_PLUGIN)
    add_subdirectory(dti)
    endif(BUILD_DTI_PLUGIN)
    Enable the option in the SMILI CMakeLists.txt file as
    OPTION(BUILD_DTI_PLUGIN "Build the Diffusion Tensor plugin for MILX Qt Viewer" OFF)
  10. The plugin should now compile and build. The plugin will be loaded by sMILX upon startup. It should output something like the following in the Log window:
    Attempting to Load libDTIPlugin.so
    Instanced ...
  11. Now simply sub-class milxQtImage and/or milxQtModel and implement functions commented in step 7 as in the denoise plugin to complete your plugin.

Troubleshooting

API

sMILX has two lightweight API layers, one for GUI-independent functions and one for GUI-dependent functions. The GUI-independent layer is called SMILI and the GUI-dependent is called milxQt. To build into sMILX a new functionality, you can follow the following example:

Adding a new model/surface processing filter

In this example, we will add the functionality in the milxQtModel class (this handles the surface/model display) that colours the surface points based on their point IDs.
  1. Add the relevant member to the SMILI milx::Model class. In this case, since the new member is vertex based we place the new member close to the GenerateVertices() member. We copy and paste this member, call it GenerateVertexScalars() and change the vtkVertexGlyphFilter to the vtkIdFilter and add the SetIdsArrayName("Point IDs") and PointIdsOn() lines too. Then change the line for updating the model state with either UpdateModelState() or UpdateModelStateFromModel depending on the filter.
  2. We add the include for vtkIdFilter in the source file and the member prototype definition in the milx::Model header file underneath GenerateVertices(). We also amend the documentation to "Generate vertex scalars for display from point data. This colours the mesh by point ids."
  3. Build to test the changes. If successful, this functionality is now available to be called from all non-GUI related applications.
  4. Next we add the necessary hooks to use the filter in the sMILX GUI by appending a member in milxQtModel. Add the action for the new filter, in this case we add the genPointIDsAct action underneath genLabelsAct and duplicate the relevant entries in createActions(), createConnections() and in generationMenu() or equivalent.
  5. Lastly, we clone the generateLabels() in milxQtModel and change the name to generatePointIDsScalars() or equivalent. We then add the entry model.GenerateVertexScalars() and replace the generate labels code. This new member will actually look more like generateTensorField() member. Make sure to update the header file also and ensure the connect line in createConnections() matches the name of the new member otherwise your context menu entry will not call the member when clicked.
  6. Build and if successful, the context menu entry will be available in the Generate menu.