Skip to content

Technical documentation (WIP)

Florent2305 edited this page Sep 1, 2017 · 2 revisions

General architecture

You will find the following folder in the medInria sources.

├── app
├── cmake
├── doc
├── src
├── src-plugins
└── utils
  • src contains a bunch of libraries providing all what is needed by both plugins and application. This includes various abstractions used throughout the application (ie: views, process, data, etc.). These abstraction are grouped in "layers" (ie: medProcessLayer, medGuiLayer etc.). Several layers can be found in one library if there is dependences between them. Implementations of those abstraction can be accessed dynamically via a dedicated factory and comes from a plugin or directly defined in the application. See the plugins section for more details. Other class used in both plugins and the application, but not loaded dynamically are also present. It is the case for all class deriving from medAbstractParameter for example.

  • app contains the main function and the creation of the main widget composing the application. It is linked and use the header of the libraries inside src. see the [application architecture](#application architecture) section for more details

  • src-plugins contains a bunch of plugins embedded by default in the public version of medInria.

Application architecture

  • medMainWindow
  • medAbstractArea
  • medAbstractWorkspace
  • medStatusBar

Plugins

Each abstraction of concept that we want to be dynamically loadable in medInria are provided via "layers" which regroup all the concept of a generic theme. For example the layer medProcessLayer regroup all the different kind of process used in medInria (medAbstractSubtractImagesProcess, medAbstractErosionProcess, etc.) Those layers are accessible via a lib (for example medCore) in a dedecated folder located in src.

For example, let's say that we want to add the concept of medAbstractArea. It's a GUI element, so it will be provided by the medGui layer in the medCore library

In addition of the medAbstractArea class, we have a class for the plugin itself medAbstractAreaPlugin that will have a concrete implementation for each plugin that specialize medAbstractArea. There is also tow other classes medAbstractAreaPluginManager that will have the role of loading the plugin at the start-up of the application and medAbstractAreaPluginFactory that will have the role of returning a medAbstractArea* of a concrete implementation defined in a plugin. The implementation is classically chosen from one (or many) identifier (a QString). The layer also give utility functions to access the plugin manager, launch the initialization and access the factory. Those function are keep in namespaces. For example to access the medAbstractAreaPluginFactory:

medGuiLayer::area::pluginFactory()

Finally here is what it look likes on the file system:

src
├── CMakeLists.txt
├── medCore
│   ├── CMakeLists.txt
│   ├── gui
│   │   ├── area
│   │   │   ├── medAbstractArea.cpp
│   │   │   ├── medAbstractArea.h
│   │   │   ├── medAbstractAreaPlugin.cpp
│   │   │   └── medAbstractAreaPlugin.h
│   │   ├── medGuiLayer.cpp
│   │   ├── medGuiLayer.h

The utility function of the layer are in the medGuiLayer files and the medAbstractAreaPluginManager medAbstractAreaPluginFactory classes are defined in the medAbstractAreaPlugin files.

Now let's say that we want to make a plugin to provide a new class medFooArea derived from medAbstractArea. It just need a class for medFooArea and a class for medFooAreaPlugin which inherits from medAbstractAreaPlugin. This class will have to implement the initialize method which record the medFooArea type to the medAbstractAreaPluginFactory. In other words it will add an entry to a map of the factory where a function pointer that return an instance of medFooArea is associated to an identifier.

ex:

void medFooAreaPlugin::initialize(void)
{
    medGuiLayer::area::pluginFactory().record("medFooArea",
                                              medFooAreaCreator);
}

Here is what the plugin will look likes on the file system:

medFooArea
├── CMakeLists.txt
├── medFooArea.cpp
├── medFooArea.h
├── medFooAreaPlugin.cpp
├── medFooAreaPlugin.h
└── medFooAreaPlugin.json

The json file is used by Qt during the loading of the plugin. It contains some information on the plugin like its name, a version number, a list of dependences etc.

The last step is to used the plugin in the application. To do it, it first need to load and initialize the plugins of all the needed layers. This is made at the start-up of the application in void medApplication::initialize():

void medApplication::initialize()
{

   [...]

    // gui layer:
    // medGuiLayer::area::pluginManager().setVerboseLoading(true);
    medGuiLayer::area::pluginManager().initialize(pathToThePluginLocation);
}

After that, whenever you want to get an instance of medFooArea in the application you can use the factory:

    medAbstractArea* fooArea = medGuiLayer::area::pluginFactory().create("medFooArea");

The list of all the type knowns by the factory is accessible via QStringList medAbstractAreaPluginFactory::keys()

Views

In medInria views are used to display on a widget one or several data. It provide the widget where the data are displayed as well as toolbar, toolboxes, etc, used to interact with the view.

Those interaction are done via object called interactors and navigators. They can be added dynamically in plugins.

Interactors handle all the interaction with one data in the view ie: changing the window-level of an image, change the colour of a surface, etc. Navigators handle interaction with only the view. This is mainly anything that may concern the camera. Panning, zoom, etc.

To let the interactors and navigators modify the state of the view from a plugin, the view provide a pointer to a medViewBackend which is an empty class that can be extended to encapsulate the object requisite to manipulate the view.

Example:

class MEDVTKINRIA_EXPORT medVtkViewBackend : public medViewBackend
{
public:
    medVtkViewBackend(vtkImageView2D * view2D_, vtkImageView3D * view3D_, vtkRenderWindow * renWin_);

    vtkImageView2D * view2D;
    vtkImageView3D * view3D;
    vtkRenderWindow * renWin;
};

There is three level of view:

  • medAbstractView It is the base class for any view. Extend medAbstractView if you want view type that allow you to display one data at a time

  • medAbstractLayerView Provide the management of a stack of layer. Extend this class if you need to display several data in the view.

  • medAbstractImageView Provide the management a camera. Extend this class if you need to display your data in a three dimensional space.

Each of this level have this corresponding navigators and interactors (medAbstractLayerViewInteractor, medAbstractImageViewInteractor, etc.)

Interactors

Interactors are used to change the way one particular data is displayed. For example in a view containing a grayscale image the interactor will allow you to change the window-level, the opacity, etc. It also construct the widgets used to drive those interactions.

Each data dropped in a view have at least one interactors called primary intercator. It has to inherit from medAbstractLayerViewInteractors if the base class of the view is medAbstractLayerView, medAbstractImageViewInteractors for medAbstractImageView etc. In addition a data can also have other interactors called extra interactors, they can inherits just from medAbstractInetractors. The purpose of the extra interactors is to allow someone to add new way to interact with one particular type of data in a new plugin.

Navigators

Navigators works almost like interactors except that there only one primary navigators for one view, plus optional extra navigators.

Parameters

The purpose of concept of parameter in medInria is to encapsulate a value of a particular type, provide widgets to modify this value and offer a mean to keep several parameter representing the same thing (ie: the opacity of an image, a LUT used to visualized a mesh, etc.) synchronized between them.

Eeach one of this parameter is identified by a name (QString) all the parameter having the same name inside a medParameterPool are synchronized between them.

Here is a list (non exhaustive) of parameters that you can find in medInria:

  • medBoolParameter
  • medIntParameter
  • medDoubleParameter
  • medStringListParameter
  • medVector2DParamter
  • medVector3DParamter
  • medTriggerParameter
  • medCompositeParameter

medTriggerParameter doesn't encapsulate any value, it just emit a triggerd signals, if a button is clicked for example.

medCompositeParameter encapsulate a list of values via a QMap<QString, QVariant>. The keys of the QMap is play the role of identifier for each QVariant. The idea is to trigger the update of all those values at the same time when one of them is changing. This is done for performance purpose (example: if the window-level of an image change we want to update at the same time the window and the level, if the position of the camera change we want to update it in three axes at the same time, etc.).

Data

Process