Plugin IO mechanisms

From KitwarePublic
Jump to navigationJump to search

ITK provides a very flexible ImageIO mechanism supporting factories. This allows for a single reader class and a single writer class that can input/output a variety of file formats. When ImageFileReader begins to read a file, it queries the ObjectFactory system for any factory that can supply an ImageIOBase. This subset of factories is queried in turn to locate a factory that can understand the format of the requested file.

New image file formats can be added to ITK by deriving a class from ImageIOBase that will support reading/writing the new file format. The file format can be added to the factory mechanism by creating a subclass of ObjectFactoryBase for this new ImageIO class. This subclass of ObjectFactoryBase will create an override in the factory mechanism indicating it can create an ImageIOBase. This is done in the constructor of the subclass of ObjectFactoryBase for the new image format:

MyImageIOFactory::MyImageIOFactory()
{
 this->RegisterOverride("itkImageIOBase",
                        "itkMyImageIO",
                        "My Image IO",
                        1,
                        CreateObjectFunction<MyImageIO>::New()); 
}

Registering a factory

For the factory mechanism to use the factory for the new image format, the new factory must be "registered" with the factory mechanism. A stock set of such factories are registered automatically by ImageIOFactory. To register additional factories,

  • Add the new format format to the list of formats registered by default in ImageIOFactory::RegisterBuiltInFactories().
    • This is should only be done if the new format is to be widely adopted by the entire ITK community.
  • Register the factory in your application code that uses that factory
    • This is reasonable if you only need to use the file format in your own applications.
ObjectFactoryBase::RegisterFactory( MyImageIOFactory::New() ); 
  • Use the factory plugin mechanism to dynamically register the factory.
    • This is useful if you have existing ITK applications that you do not wish to recompile (or can recompile) but you would like them to be able to use your new file format.

Plugin mechanism

ITK's object factory mechanism supports dynamically loading factories from shared libraries. This mechanism can be used to dynamically load factories for new image file formats. The object factory will look at the ITK_AUTOLOAD_PATH environment variable to locate dynamically loadable factories (plugins).

export ITK_AUTOLOAD_PATH=/home/me/myplugins

If the ITK_AUTOLOAD_PATH is set, each shared library in each directory in that path is examined for a "C" method (not a C++ method)

ObjectFactorBase* itkLoad()

If the method itkLoad() is found in the shared library, it is called. itkLoad() must return a pointer to an ObjectFactoryBase. The object factory mechanism then calls ObjectFactoryBase::RegisteryFactory(), passing in the pointer returned by itkLoad().

To add a new image file format to an existing application, we can simply wrap the associated ImageIOFactory for the new format inside a shared object that provides the itkLoad() mechanism. Any precompiled ITK application will then be able to read and write this new format.

Caveats

The object factory mechanism will make sure that the dynamically loaded ImageIOFactory was built with the same verion of ITK as the application. If they are not a warning will be presented.

Possible imcompatible factory load.
Running itk version : xx.xx.xx
Loaded Factory verions: yy.yy.yy
Loaded factory: /path/to/factory

Factories dynamically loaded that were built with a different version of ITK than the application may be unstable.


Example

To add the MyImageIOFactory as a dynamically loaded factory, we need to create a shared library with an itkLoad() method and place that shared library in the ITK_AUTOLOAD_PATH. Here is an example CMakeLists.txt file for creating the plugin shared library

SET(MYIO_SRCS itkMyImageIO.cxx itkMyImageIOFactory.cxx)

#
# Library for all of my image formats
ADD_LIBRARY (MYIO ${MYIO_SRCS})
TARGET_LINK_LIBRARIES(MYIO ITKIO)

#
# Shared library that when placed in ITK_AUTOLOAD_PATH, will add MyImageIO
# as an ImageIOFactory.  Need to have separate shared library for each new format.
ADD_LIBRARY(MyPlugin SHARED itkMyPlugin.cxx)
TARGET_LINK_LIBRARIES(MyPlugin MYIO)

MyPlugin will be the shared library that adds support for the MyImageIO file format. The code associated with MyPlugin is

itkMyPlugin.h

#ifndef __itkMyPlugin_h
#define __itkMyPlugin_h

#include "itkObjectFactoryBase.h"

#ifdef WIN32
#ifdef MyPlugin_EXPORTS
#define MyPlugin_EXPORT __declspec(dllexport)
#else
#define MyPlugin_EXPORT __declspec(dllimport)
#endif
#else
#define MyPlugin_EXPORT 
#endif

/**
 * Routine that is called when the shared library is loaded by
 * itk::ObjectFactoryBase::LoadDynamicFactories().
 *
 * itkLoad() is C (not C++) function.
 */
extern "C" {
MyPlugin_EXPORT itk::ObjectFactoryBase* itkLoad();
} 
#endif  

itkMyPlugin.cxx

#include "itkMyPlugin.h"
#include "itkMyImageIOFactory.h"

/**
 * Routine that is called when the shared library is loaded by
 * itk::ObjectFactoryBase::LoadDynamicFactories().
 *
 * itkLoad() is C (not C++) function.
 */
itk::ObjectFactoryBase* itkLoad()
{
  static itk::MyImageIOFactory::Pointer f = itk::MyImageIOFactory::New();
  return f;
}

Discussion

  • It appears as though ObjectFactoryBase::LoadDynamicFactories() is called multiple times. Putting a print statement in the above itkLoad() method verifies this for a simple program of a single reader connected to a single writer.
    • There needs to be a mechanism to cause LoadDynamicFactories() to called multiple times if plugins can be added while an application is running.
    • However, calling LoadDynamicFactories too often will be a performance issue.
    • If LoadDynamicFactories() can be called multiple times, then the factory list must be managed for duplicates and the itkLoad() method probably needs additional memory management and synchronization.
  • Implementing itkLoad() could be made simpler if itkLoad() could return a smart pointer to an ObjectFactoryBase instead of a raw pointer to an ObjectFactoryBase.

Update

LoadDynamicFactories is only called multiple times if ITK and the new IO format class are compiled static. Much of the following discussion may only be pertinent on Windows. The plugin defined above is always compiled shared. But if ITK and IO format class are compiled static, then they do not share the same ObjectFactory. Consider an ITK executable like the example ImageReadWrite. This example just has an ImageFileReader connected to an ImageFileWriter. When the reader is instantiated, the ObjectFactory triggers a call to LoadDynamicFactories(). This causes any plugins to be loaded. When the ImageFileReader needs to read the file, it asks the ObjectFactory for all factories that can create an ImageIOBase. This causes the plugin to instantiate an ImageIO subclass for the new file format. This instantiation is done in the shared library which has its own ObjectFactory since the new file format was compiled static. This ObjectFactory has yet to be initialized and LoadDynamicFactories() is called a second time.

To use plugins properly, the ITK application must be using a shared build of ITK, the new IO format must be using a shared build of ITK, and of course the plugin itself must be built shared. Then, a single ObjectFactory will be used in the application and the plugin.

If the application is built with a static build of ITK and the new IO format is built with a static build of ITK, then LoadDynamicFactories() will be called multiple times: once for the application and once for each plugin that instantiates an object. This may not actually cause a runtime issue other than causing the dynamic libraries to be loaded multiple times. Each plugin and the application will have their own ObjectFactory but they should all be configured identically.