FW4SPL documentation

Introduction

Repositories

The fw4spl project is organized around three repositories :
  • fw4spl: main repository, contains the core libraries and bundles.
  • fw4spl-ar: extension of fw4spl, contains functionalities for augmented reality (video tracking)
  • fw4spl-ogre: extension of fw4spl, contains a 3D backend using Ogre3D.
These repositories needs the associated dependencies repository.

Additionnaly, there is a fourth repository called fw4spl-ext for experimental functionalities and proofs of concept. Please note that on top of fw4spl-deps, it needs an extra deps repository to compile, called fw4spl-ext-deps.

fw4spl

This repository contains the core libraries and bundles. It is hosted on GitHub.

Features

  • Reader/Writer
    • DICOM reader/writer
      • PACS connection
      • 3D mesh segmentation reader/writer
      • DICOM filter for reader
    • VTK (images and meshes)

    • ITK

    • Atoms (our custom in-out data format)

  • Visualisation
    • 2D and 3D multi-planar reconstruction
    • volume rendering
    • 3D meshes

Application

VRRender is a medical image and segmentation viewer, containing all the previous features.

_images/SDB.png

Main VRRender view.

_images/MPR.png

MPR view of a medical 3D image.

_images/3D.png

3D view of surfacic meshes.

_images/VR.png

Volume rendering

_images/VR-3D.png

Volume rendering mixed with 3D surfacic meshes.

Tutorials

You can find some tutorials to explain fw4spl concept.

Name Concept
Tuto01Basic Basic application
Tuto02DataServiceBasic Simple image reading and rendering
Tuto02DataServiceBasicCtrl Simple image reading and rendering without XML configuration
Tuto03DataService Image reading and rendering with signal communication
Tuto04SignalSlot Scene point of view synchronisation with signal communication
Tuto05Mesher Simple mesher from a 3D image
Tuto06Filter Simple image filter
Tuto08GenericScene Scene with multi-object rendering
Tuto09MesherWithGenericScene Scene with multi-object rendering and simple mesher
Tuto10MatrixTransformInGS Example of matrix transformation
Tuto11LaunchBasicConfig Example to launch XML config in application
Tuto12Picker Example of scene picker
Tuto13Scene2D Example using the ``scene2d``bundle
Tuto14MeshGenerator Mesh features (point/cell color, normals, ...)
Tuto15Multithread Example of multi-threading using fw4spl worker
Tuto15MultithreadCtrl Second example of multi-threading using fw4spl worker
TutoGui Example of fw4spl gui feature (toolbar, menu, action)
TutoPython Example of pyhton binding in fw4spl
TutoTrianConverterCtrl Utility converting .trian meshes to .vtk

Examples

Name Concept
Ex01VolumeRendering Example of volume rendering using transfer function
Ex02ImageMix Example of image blend
Ex03Registration Example of simple rigid image-mesh registration
Ex04ImagesRegistration Example of simple rigid image-image registration

fw4spl-ar

This repository contains functionalities for augmented reality. It is hosted on GitHub.

Features

This repository brings features for Augmented Reality:

  • webcam, network video and local video playing based on QtMultimedia,
  • mono and stereo camera calibration,
  • ArUco optical markers tracking,
  • openIGTLink support through clients and servers services,
  • TimeLine data, allowing to store buffers of various data (video, matrices, markers, etc...). These can be used to synchronize these data accross time.

Applications

ARCalibration

ARCalibration is a user-friendly application to calibrate mono and stereo cameras. This software is a must-have since camera calibration is a mandatory step in any AR application.

_images/calibration.png

Mono camera intrinsic calibration.

_images/calibrationExt.png

Stereo camera extrinsic calibration.

Examples
Name Concept
Ex01VideoTracking Basic marker tracking on a video
Ex02TimeLine Basic producer-consumer pattern sample with timeLine data
Ex03Igtl Example of some of the openIGTLink features

fw4spl-ogre

This repository brings a new 3D rendering backend using Ogre3D. It is hosted on GitHub.

Features

  • regular surfacic meshes rendering for reconstruction,
  • 2D and 3D negato medical image display with transfer function support,
  • Order-independent transparency (several techniques implemented such as Depth-peeling, Weighted-blended order independent transparency, and Hybrid Transparency),
  • customizable shaders and parameters edition.

Application

OgreViewer is a demonstration application to show the rendering features of the Ogre3D backend.

fw4spl-ext

This repository is dedicated to experimental code, that is not yet mature or is likely to change quickly. Only import this repository if you know what you are doing, since stability may not be guaranteed.

It is hosted on GitHub.

Features

  • navigation along a spline
  • new mesher using the CGoGN library
  • ...

Application

VRRenderExt is an application containing the VRRender features and also the additional fw4spl-ext features.

Proofs of concept

Name Concept
PoC06Scene2DTF Simple use of scene2d bundle

Installation

Latest Release Version

The last release of the project is:

  • fw4spl_0.11.0

Installation for Windows

Prerequisites for Windows users

If not already installed:

  1. Install git
  2. Optionally you can install TortoiseHg
  3. Install Visual Studio 2013 Community
  4. Install Python 2.7
  5. Install CMake
  6. Install jom
  7. Install ninja

Qt is an external library used in FW4SPL. For the successful compilation of Qt for FW4SPL, please see the following requirements:

FW4SPL installation

Good practice in FW4SPL recommend to separate source files, build and install folders. So to prepare the development environment:

  • Create a development folder (Dev)

  • Create a build folder (Dev\Build)

    • Add a sub folder for Debug and Release.
  • Create a source folder (Dev\Src)

  • Create a install folder (Dev\Install)

    • Add a sub folder for Debug and Release.

To prepare the third party environment:

  • Create a third party folder (BinPkgs)

  • Create a build folder (BinPkgs\Build)

    • Add a sub folder for Debug and Release.
  • Create a source folder (BinPkgs\Src)

  • Create an install folder (BinPkgs\Install)

    • Add a sub folder for Debug and Release.
  • Set the environment for a x64 version. To compile BinPkgs and sources, you must use the ‘VS2013 x64 Native Tools Command Prompt’
Dependencies

Warning

Be sure to be in the ‘VS2013 x64 Native Tools Command Prompt’

  • Clone the following repository in the (BinPkgs) source folder:

  • Update the cloned repositories to the fw4spl_0.11.0 tag.

Note

Make sure that CMake executable location is present in your PATH environment variable.

  • Call the cmake-gui
  • During Configure, choose the generator ‘NMake Makefiles JOM’.

Note

Make sure that JOM executable location is present in your PATH environment variable.

  • Set the following arguments:

    • CMAKE_INSTALL_PREFIX: set the install location.
    • CMAKE_BUILD_TYPE: set to Debug or Release.
    • ADDITIONAL_PROJECTS: you can leave it empty, it is only needed if you have an extra source location like fw4spl-ext-deps or a custom repository.
  • Generate the code.

  • Compile the FW4SPL dependencies with jom in the console (e.g. jom all, jom qt, etc).

Source

Warning

Be sure to be in the ‘VS2013 x64 Native Tools Command Prompt’

  • Clone the following repositories in the (Dev) source folder:
  • Update the cloned repositories to the fw4spl_0.11.0 tag.

Note

Make sure that CMake executable location is present in your PATH environment variable.

  • Call the cmake-gui.
  • During configure step, choose the generator (‘NMake Makefiles JOM’ to compile BinPkgs or ‘Ninja’ to compile FW4SPL sources).

Note

Make sure that Ninja and JOM executables location are present in your PATH environment variable.

  • Set the following arguments:

    • ADDITIONAL_PROJECTS: set the source location of fw4spl-ar and fw4spl-ext
    • CMAKE_INSTALL_PREFIX: set the install location.
    • CMAKE_BUILD_TYPE: set to Debug or Release
    • EXTERNAL_LIBRARIES: set the install path of the third part libraries.
    • PROJECT_TO_BUILD set the name of the application to build (see DevSrcApps, ex: VRRender, Tuto01Basic ...), each project should be separated by ”;”.
    • PROJECT_TO_INSTALL set the name of the application to install (i.e. VRRender)

Note

  • If PROJECT_TO_BUILD is empty, all application will be compiled
  • If PROJECT_TO_INSTALL is empty, no application will be installed

Warning

Make sure the arguments concerning the compiler (advanced arguments) point to Visual Studio.

  • Generate the code.
  • Compile the FW4SPL source code with ninja in the console.

Note

it is possible to generate eclipse project with CMake. You just have to check ECLIPSE_PROJECT.

Launch an application

After an successful compilation the application can be launched with the fwlauncher.exe from FW4SPL. Therefore the profile.xml of the application in the build folder has to be passed as argument.

Note

Make sure that the external libraries directory is set to the path (set PATH=<FW4SPL Binpkgs path>\Debug\bin;<FW4SPL Binpkgs path>\Debug\x64\vc12\bin;%PATH%).

_images/launchApp.png

Extensions

fw4spl has two main extension repositories:

  • fw4spl-ar: extension of fw4spl repository, contains functionalities for augmented reality (video tracking for instance).
  • fw4spl-ogre: another extension of fw4spl, contains a 3D backend using Ogre3D.

If you want to use them, clone them first in the (Dev) source folder. Then modify ADDITIONAL_PROJECTS in cmake-gui to set the source location of fw4spl-ar and fw4spl-ogre separated by ‘;’. Last, re-generate the code and compile with ninja.

Installation for Linux

Prerequisites for Linux users

If not already installed:

  1. Install git
  2. Install gcc The minimal version required is 4.8 or clang The minimal version required is 3.5
  3. Install Python 2.7
  4. Install CMake. The minimal version required is 3.7 if you want to compile with precompiled headers (build twice faster, enabled by default). Otherwise you can use a 3.1 version.
  5. Install Ninja

Depending on which linux distribution you use, for example on Debian/Ubuntu/Mint you can do:

$ apt-get install build-essential ninja-build python2.7 git cmake

Warning

If the CMake version of your distribution is not sufficient (Mint 17 for instance ships only the 2.18 version), you can easily grab it on the Kitware website. Download the binary version (much easier than compiling yourself), extract it to a folder (i.e. /home/login/software/cmake/) and add the bin/ folder inside it to your PATH environment variable :

# ~/.bashrc
export PATH=/home/login/software/cmake/bin:$PATH

Qt is an external library used in FW4SPL. For the successful compilation of Qt with FW4SPL, please see the following requirements:

Follow the instructions there to install the necessary packages on your system for Build essentials, libxcb and QtMultimedia. For the latter, please note that we use gstreamer-1.0 by default, so please replace libgstreamer0.10-dev and libgstreamer-plugins-base0.10-dev by libgstreamer1.0-dev and libgstreamer-plugins-base1.0-dev. You can safely ignore instructions for QtWebKit and QtWebEngine, we don’t build them. Since we build Qt with openssl support you also need to install libssl-dev (be sure that the version is equal or upper to 1.0.0). Last for VTK we also need the X Toolkit Intrinsics library headers, that you can easily install for instance on a Debian-based distribution with the package libxt-dev.

FW4SPL installation

FW4SPL uses CMake as build system.

We strongly recommend to build FW4SPL by separating source files, build files and install files. So we propose the following folders layout :

  • Deps/Build/Debug
  • Deps/Build/Release
  • Deps/Src
  • Deps/Install/Debug
  • Deps/Install/Release
  • Dev/Build/Debug
  • Dev/Build/Release
  • Dev/Src
  • Dev/Install/Debug
  • Dev/Install/Release

Of course you can name the folders as you wish, or choose a different layout, but keep in mind to not build inside the source directory. This is strongly discouraged by CMake authors.

Here are the details, if you need some help to create this folders hierarchy :

  • Create a third party folder (Deps)
$ mkdir Deps
  • Create into “Deps” the source, build and install directories
$ mkdir Deps/Src Deps/Build Deps/Install
  • Create sub-folders to separate Debug and Release compilations
$ mkdir Deps/Build/Debug Deps/Build/Release Deps/Install/Debug Deps/Install/Release
  • Then create a “Dev” directory, for FW4SPL
$ mkdir Dev
  • Create into “Dev” the source, build and install directories
$ mkdir Dev/Src Dev/Build Dev/Install
  • Create sub-folders to separate Debug and Release compilations
$ mkdir Dev/Build/Debug Dev/Build/Release Dev/Install/Debug Dev/Install/Release
Dependencies

We need first to build the third-party librairies. We will now fetch the scripts that allow to build them and then launch the compilation.

  • Clone the repository into your source directory of Deps:
$ cd Deps/Src
$ git clone https://github.com/fw4spl-org/fw4spl-deps.git fw4spl-deps
  • Go into fw4spl-deps folder and update to the latest stable version:
$ cd fw4spl-deps
$ git checkout fw4spl_0.11.0
  • Go into your Build directory (Debug or Release) : here an example if you want to compile in DEBUG
$ cd ../../..
$ cd Deps/Build/Debug
Project configuration

To build the dependencies, you must configure the project with CMake into the Build folder. As any CMake based project, there are three different ways to perform that.

1. Command-line

In this case, you give all the necessary variables on the command-line in one shot :

$ cd ~/Deps/Build/Debug
$ cmake ../../Src/fw4spl-deps -DCMAKE_INSTALL_PREFIX=~/Deps/Install/Debug -DCMAKE_BUILD_TYPE=Debug
2. NCurses based editor

This editor allows to set the required each variable in a more interactive way :

$ cd ~/Deps/Build/Debug
$ ccmake ../../Src/fw4spl-deps

Then change the following CMake variables:

  • CMAKE_INSTALL_PREFIX: set the install location, here ~/Deps/Install/Debug
  • CMAKE_BUILD_TYPE: set the build type ‘Debug’ or ‘Release’

Press “c” to configure and then “g” to generate the makefiles.

_images/osx_cmake_binpkgs.png
3. Qt based gui
$ cd ~/Deps/Build/Debug
$ cmake-gui ../../Src/fw4spl-deps

Like ccmake, change the following CMake variables:

  • CMAKE_INSTALL_PREFIX: set the install location, here ~/Deps/Install/Debug
  • CMAKE_BUILD_TYPE: set the build type ‘Debug’ or ‘Release’

Click on “configure” then “generate”.

Warning

Do not compile debug and release with the same Build and Install folders. If you followed the recommended folder layout, this should be fine.

Compilation

Now you can compile the FW4SPL dependencies with make in the console, it will automaticaly download, build and install each dependency.

# Adjust the number of cores depending of the CPU cores and the RAM available on your computer
$ make -j4

Warning

Do NOT use ninja to compile the dependencies, it causes conflict with qt compilation.

If you get compilation errors at this step, please ensure you installed all the requirements, especially those for Qt.

Source
  • Clone fw4spl repository into your source directory:
$ cd Dev/Src
$ git clone https://github.com/fw4spl-org/fw4spl.git fw4spl
  • Go into fw4spl folder and update to the latest stable version:
$ cd fw4spl
$ git checkout fw4spl_0.11.0
  • Go into your Build directory (Debug or Release) : here an example if you want to compile in debug:
$ cd Dev/Build/Debug
  • Now you have to configure the project. You can use one of the three tools explained above.

Also, for FW4SPL, we recommend to use the Ninja generator. It builds faster, and it is much better for everyday use because it is fast as hell to check the files you need to compile. In other words, with Ninja the compilation starts instantly whereas Make spends a dozen of seconds to check what should be compiled before actually compiling something. So if you plan to develop with FW4SPL, go with Ninja. If you only want to give a single try, you can live with the standard “Unix Makefiles” generator.

To use make, here with ccmake :

$ ccmake ../../Src/fw4spl

To use ninja :

$ ccmake -G Ninja ../../Src/fw4spl
  • Change the following cmake arguments
    • CMAKE_INSTALL_PREFIX: set the install location (~/Dev/Install/Debug or Release)
    • CMAKE_BUILD_TYPE: set to Debug or Release
    • EXTERNAL_LIBRARIES: set the install path of the third party libraries you compiled before.(ex : ~/Deps/Install/Debug)
    • PROJECT_TO_BUILD: set the list of the projects you want to build (ex: VRRender, Tuto01Basic ...), each project should be separated by ”;”
    • PROJECT_TO_INSTALL: set the name of the application to install

Note

  • If PROJECT_TO_BUILD is empty, all application will be compiled
  • If PROJECT_TO_INSTALL is empty, no application will be installed
_images/osx_cmake_fw4spl.png

Press “c” to configure and then “g” to generate the makefiles.

Note

To generate the projects in release mode, change CMake argument CMAKE_BUILD_TYPE to Release both for fw4spl and fw4spl-deps

Then, according to the generator you chose, build FW4SPL with make :

# Adjust the number of cores depending of the CPU cores and the RAM available on your computer
$ make -j4

Or with ninja:

$ ninja

If you didn’t specify anything in PROJECT_TO_BUILD you may also build specific targets, for instance:

$ ninja Tuto01Basic VRRender

Launch an application

After a successful compilation the application can be launched with the fwlauncher program from FW4SPL. The profile.xml of the application in the build folder has to be passed as argument to the fwlauncher call in the console.

$ bin/fwlauncher Bundles/MyApplication_Version/profile.xml

Example:

$ cd /Dev/Build/Debug
$ bin/fwlauncher Bundles/VRRender_0-9/profile.xml

Extensions

FW4SPL has two main extension repositories:

  • fw4spl-ar: extension of fw4spl repository, contains functionalities for augmented reality (video tracking for instance).
$ cd Dev/Src
$ git clone https://github.com/fw4spl-org/fw4spl-ar.git fw4spl-ar
$ cd fw4spl-ar
$ git checkout fw4spl_0.11.0

Then you have to reconfigure your CMake project:

$ cd ../../Build/Debug
$ ccmake .

Modify `̀ ADDITIONAL_PROJECTS`̀ : set the source location of fw4spl-ar and fw4spl-ogre separated by a semi-colon.

~/Dev/Src/fw4spl-ar/;~/Dev/Src/fw4spl-ogre/

Installation for MacOSX

Prerequisites for MacOSX users

If not already installed:

  1. Install Xcode
  2. Install git
  3. Install Python 2.7
  4. Install CMake. The minimal version required is 3.7 if you want to compile with precompiled headers (build twice faster, enabled by default). Otherwise you can use a 3.1 version.
  5. Install Ninja : to use instead of make.

For an easy install, you can use the Hombrew project to install missing packages.

$ brew install git
$ brew install python
$ brew install cmake
$ brew install ninja

FW4SPL installation

FW4SPL uses CMake as build system.

We strongly recommend to build FW4SPL by separating source files, build files and install files. So we propose the following folders layout :

  • Deps/Build/Debug
  • Deps/Build/Release
  • Deps/Src
  • Deps/Install/Debug
  • Deps/Install/Release
  • Dev/Build/Debug
  • Dev/Build/Release
  • Dev/Src
  • Dev/Install/Debug
  • Dev/Install/Release

Of course you can name the folders as you wish, or choose a different layout, but keep in mind to not build inside the source directory. This is strongly discouraged by CMake authors.

Here are the details, if you need some help to create this folders hierarchy :

  • Create a third party folder (Deps)
$ mkdir Deps
  • Create into “Deps” the source, build and install directories
$ mkdir Deps/Src Deps/Build Deps/Install
  • Create sub-folders to separate Debug and Release compilations
$ mkdir Deps/Build/Debug Deps/Build/Release Deps/Install/Debug Deps/Install/Release
  • Then create a “Dev” directory, for FW4SPL
$ mkdir Dev
  • Create into “Dev” the source, build and install directories
$ mkdir Dev/Src Dev/Build Dev/Install
  • Create sub-folders to separate Debug and Release compilations
$ mkdir Dev/Build/Debug Dev/Build/Release Dev/Install/Debug Dev/Install/Release
Dependencies

We need first to build the third-party librairies. We will now fetch the scripts that allow to build them and then launch the compilation.

  • Clone the repository into your source directory of Deps:
$ cd Deps/Src
$ git clone https://github.com/fw4spl-org/fw4spl-deps.git fw4spl-deps
  • Go into fw4spl-deps folder and update to the latest stable version:
$ cd fw4spl-deps
$ git checkout fw4spl_0.11.0
  • Go into your Build directory (Debug or Release) : here an example if you want to compile in DEBUG
$ cd ../../..
$ cd Deps/Build/Debug
Project configuration

To build the dependencies, you must configure the project with CMake into the Build folder. As any CMake based project, there are three different ways to perform that.

1. Command-line

In this case, you give all the necessary variables on the command-line in one shot :

$ cd ~/Deps/Build/Debug
$ cmake ../../Src/fw4spl-deps -DCMAKE_INSTALL_PREFIX=~/Deps/Install/Debug -DCMAKE_BUILD_TYPE=Debug
2. NCurses based editor

This editor allows to set the required each variable in a more interactive way :

$ cd ~/Deps/Build/Debug
$ ccmake ../../Src/fw4spl-deps

Then change the following CMake variables:

  • CMAKE_INSTALL_PREFIX: set the install location, here ~/Deps/Install/Debug
  • CMAKE_BUILD_TYPE: set the build type ‘Debug’ or ‘Release’

Press “c” to configure and then “g” to generate the makefiles.

_images/osx_cmake_binpkgs.png
3. Qt based gui
$ cd ~/Deps/Build/Debug
$ cmake-gui ../../Src/fw4spl-deps

Like ccmake, change the following CMake variables:

  • CMAKE_INSTALL_PREFIX: set the install location, here ~/Deps/Install/Debug
  • CMAKE_BUILD_TYPE: set the build type ‘Debug’ or ‘Release’

Click on “configure” then “generate”.

Warning

Do not compile debug and release with the same Build and Install folders. If you followed the recommended folder layout, this should be fine.

Compilation

Now you can compile the FW4SPL dependencies with make in the console, it will automaticaly download, build and install each dependency.

$ make all
$ make install_tool

Warning

Do NOT use ninja to compile the dependencies, it causes conflict with qt compilation.

If you get compilation errors at this step, please ensure you installed all the requirements, especially those for Qt.

Source
  • Clone fw4spl repository into your source directory:
$ cd Dev/Src
$ git clone https://github.com/fw4spl-org/fw4spl.git fw4spl
  • Go into fw4spl folder and update to the latest stable version:
$ cd fw4spl
$ git checkout fw4spl_0.11.0
  • Go into your Build directory (Debug or Release) : here an example if you want to compile in debug:
$ cd Dev/Build/Debug
  • Now you have to configure the project. You can use one of the three tools explained above.

Also, for FW4SPL, we recommend to use the Ninja generator. It builds faster, and it is much better for everyday use because it is fast as hell to check the files you need to compile. In other words, with Ninja the compilation starts instantly whereas Make spends a dozen of seconds to check what should be compiled before actually compiling something. So if you plan to develop with FW4SPL, go with Ninja. If you only want to give a single try, you can live with the standard “Unix Makefiles” generator.

To use make, here with ccmake :

$ ccmake ../../Src/fw4spl

To use ninja :

$ ccmake -G Ninja ../../Src/fw4spl
  • Change the following cmake arguments
    • CMAKE_INSTALL_PREFIX: set the install location (~/Dev/Install/Debug or Release)
    • CMAKE_BUILD_TYPE: set to Debug or Release
    • EXTERNAL_LIBRARIES: set the install path of the third party libraries you compiled before.(ex : ~/Deps/Install/Debug)
    • PROJECT_TO_BUILD: set the list of the projects you want to build (ex: VRRender, Tuto01Basic ...), each project should be separated by ”;”
    • PROJECT_TO_INSTALL: set the name of the application to install

Note

  • If PROJECT_TO_BUILD is empty, all application will be compiled
  • If PROJECT_TO_INSTALL is empty, no application will be installed
_images/osx_cmake_fw4spl.png

Press “c” to configure and then “g” to generate the makefiles.

Note

To generate the projects in release mode, change CMake argument CMAKE_BUILD_TYPE to Release both for fw4spl and fw4spl-deps

Then, according to the generator you chose, build FW4SPL with make :

# Adjust the number of cores depending of the CPU cores and the RAM available on your computer
$ make -j4

Or with ninja:

$ ninja

If you didn’t specify anything in PROJECT_TO_BUILD you may also build specific targets, for instance:

$ ninja Tuto01Basic VRRender

Launch an application

After a successful compilation the application can be launched with the fwlauncher program from FW4SPL. The profile.xml of the application in the build folder has to be passed as argument to the fwlauncher call in the console.

$ bin/fwlauncher Bundles/MyApplication_Version/profile.xml

Example:

$ cd /Dev/Build/Debug
$ bin/fwlauncher Bundles/VRRender_0-9/profile.xml

Extensions

FW4SPL has two main extension repositories:

  • fw4spl-ar: extension of fw4spl repository, contains functionalities for augmented reality (video tracking for instance).
$ cd Dev/Src
$ git clone https://github.com/fw4spl-org/fw4spl-ar.git fw4spl-ar
$ cd fw4spl-ar
$ git checkout fw4spl_0.11.0

Then you have to reconfigure your CMake project:

$ cd ../../Build/Debug
$ ccmake .

Modify `̀ ADDITIONAL_PROJECTS`̀ : set the source location of fw4spl-ar and fw4spl-ogre separated by a semi-colon.

~/Dev/Src/fw4spl-ar/;~/Dev/Src/fw4spl-ogre/

Installation for Android

_images/fw4spl-droid_logo.png

This page summarizes the steps to build FW4SPL on Android.

Warning

Work in progress

Android environnement

The following tools are necessary:

Note

On Linux, openjdk is also reported to work well. You can easily install it with the packaging tools of your favorite distribution.

Note

Since OSX 10.10 (Yosemite), JDK 8 is the minimal requirement.

SDK install

The SDK is a set of libraries and development tools necessary to build, test and debug and Android application. The initial archive you will download contains only the basic tools of the SDK. It doesn’t contain any version of the API (Android Platform) and doesn’t provide the whole set of tools you actually need to develop an application.

  • Download the SDK archive:

  • Extract the archive

  • Set the environment variable ANDROID_SDK to its actual path

# i.e for Linux and OSX
export ANDROID_SDK=/ABSOLUTE/PATH/TO/THE/ANDROID_SDK
  • The SDK Manager tool ease the downloading and the management of the different SDK versions, as well as the downloading of the development tools:

    • On Windows, SDK Manager.exe is located in the SDK root folder
    • On Linux and OSX, launch the following command in the SDK root folder :
./tools/android sdk
_images/sdk_manager.png
  • You can also update the sdk directly through the command line:
./tools/android update sdk —no-ui

After that, you have to download at least one version of the API and one Platform-tools version. The latter contains the build tools (adb, etc...) and are updated independently of the SDK. More informations can be found here.

Warning

For the Windows users, it can be interesting to check that adb is well installed for your device by using the peripheral manager. If it’s not the case, please try to update the drivers of your tablet/phone.

_images/WindowPeriphericManager.png

NDK install

The NDK is the tool allowing people to develop some parts of your application using native code (C/C ++). It also contains the system headers necessary to link successfully your librairies with the latest releases :

  • libc (C library) headers
  • libm (math library) headers
  • JNI interface headers
  • libz (Zlib compression) headers
  • liblog (Android logging) header
  • OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headers
  • libjnigraphics (Pixel buffer access) header (for Android 2.2 and above).
  • A Minimal set of headers for C++ support
  • OpenSL ES native audio libraries
  • Android native application APIS

The NDK also provides a build system which will allow you to compile your source code without taking care of the toolchain/platform/CPU/ABI (ABI = Application Binary Interface). You have to ensure to have the latest version of the SDK. Indeed, the NDK is compatible with the previous versions of the Android API, but this is not the case for the Platform tools.

  • Download the NDK for your host platfom here and extract the zip archive.

More informations can be found here.

Configuration for FW4SPL

In order to obtain an Android development environment compatible with FW4SPL and its third-party libraries, you must fulfill the conditions below.

The APIs <= 8 (Froyo) are not supported because of compilation error in boost. Thus it is recommended to install the SDK Manager with the following versions versions:

  • Tools:
    • Android SDK Tools >= 23.0.5.
    • Android SDK Platform-Tools >= 21.
    • Android SDL Build-tools >= 21.0.2.
  • Android 4.4.2 (API 21):
    • SDK Platform >= 21.
  • On top of that, Qt 5.x does need multiple versions of the API (only download the SDK Platform):
    • API 10 and 11 for QtMultimedia.
    • API 16 for QtBase.

So don’t forget to install them before building the FW4SPL deps.

You must also add the following environment variables.

ANDROID_NDK=/PATH/TO/NDK
ANDROID_SDK=/PATH/TO/SDK
JAVA_HOME=/PATH/TO/JDK

Please remember that JAVA_HOME is the root folder of the JDK and not the binary folder.

Warning

For windows, the path to the JDK binaries (java, javac, etc...) must also be in the PATH environment variable.

FW4SPL installation

FW4SPL uses CMake as build system.

We strongly recommend to build FW4SPL by separating source files, build files and install files. So we propose the following folders layout :

  • Deps/Build/Debug
  • Deps/Build/Release
  • Deps/Src
  • Deps/Install/Debug
  • Deps/Install/Release
  • Dev/Build/Debug
  • Dev/Build/Release
  • Dev/Src
  • Dev/Install/Debug
  • Dev/Install/Release

Of course you can name the folders as you wish, or choose a different layout, but keep in mind to not build inside the source directory. This is strongly discouraged by CMake authors.

Here are the details, if you need some help to create this folders hierarchy :

  • Create a third party folder (Deps)
$ mkdir Deps
  • Create into “Deps” the source, build and install directories
$ mkdir Deps/Src Deps/Build Deps/Install
  • Create sub-folders to separate Debug and Release compilations
$ mkdir Deps/Build/Debug Deps/Build/Release Deps/Install/Debug Deps/Install/Release
  • Then create a “Dev” directory, for FW4SPL
$ mkdir Dev
  • Create into “Dev” the source, build and install directories
$ mkdir Dev/Src Dev/Build Dev/Install
  • Create sub-folders to separate Debug and Release compilations
$ mkdir Dev/Build/Debug Dev/Build/Release Dev/Install/Debug Dev/Install/Release
Dependencies

We need first to build the third-party librairies. We will now fetch the scripts that allow to build them and then launch the compilation.

  • Clone the repository into your source directory of Deps:
$ cd Deps/Src
$ git clone https://github.com/fw4spl-org/fw4spl-deps.git fw4spl-deps
  • Go into fw4spl-deps folder and update to the latest stable version:
$ cd fw4spl-deps
$ git checkout fw4spl_0.11.0
  • Go into your Build directory (Debug or Release) : here an example if you want to compile in DEBUG
$ cd ../../..
$ cd Deps/Build/Debug
Toolchain

The toolchain allows to cross-compile for Android by specifying all the necessary variables (compiler, target system, etc ...). Currently, the toolchain we use is a modified version of this toochain from the github user taka-no-me (fork of the OpenCV project).

  • Download the toolchain:
git clone https://github.com/fw4spl-org/android-cmake.git ~/Dev/Droid
Project configuration

To build the dependencies, you must configure the project with CMake into the Build folder. As any CMake based project, there are three different ways to perform that.

1. Command-line

In this case, you give all the necessary variables on the command-line in one shot :

$ cd ~/Dev/Deps/Build/Debug
$ cmake ../Src/fw4spl-deps -DCMAKE_INSTALL_PREFIX=~/Dev/Deps/Install/Debug -DCMAKE_BUILD_TYPE=Debug -DCROSS_COMPILING=ON -DANDROID_NATIVE_API_LEVEL=21 -DCMAKE_TOOLCHAIN_FILE=~/Dev/Droid/android.toolchain.cmake
2. NCurses based editor

This editor allows to set the required each variable in a more interactive way :

$ cd ~/Dev/Deps/Build/Debug
$ ccmake ../Src/fw4spl-deps

Then change the following CMake variables:

  • CMAKE_INSTALL_PREFIX: set the install location, here ~/Dev/Deps/Install/Debug
  • CMAKE_BUILD_TYPE: set the build type ‘Debug’ or ‘Release’
  • CROSS_COMPILING: set to ‘ON’

Press “c” to configure. Now you have got two new variables to set:

  • ANDROID_NATIVE_API_LEVEL: set to ‘21’
  • CMAKE_TOOLCHAIN_FILE: set to ~/Dev/Droid/android.toolchain.cmake

Press “c” to configure and then “g” to generate the makefiles.

_images/ccmake_fw4spl-deps.png
3. Qt based gui
$ cd ~/Dev/Deps/Build/Debug
$ cmake-gui ../Src/fw4spl-deps

Like ccmake, change the following CMake variables:

  • CMAKE_INSTALL_PREFIX: set the install location, here ~/Dev/Deps/Install/Debug
  • CMAKE_BUILD_TYPE: set the build type ‘Debug’ or ‘Release’
  • CROSS_COMPILING: set to ‘ON’

Press “c” to configure. Now you have got two new variables to set:

  • ANDROID_NATIVE_API_LEVEL: set to ‘21’
  • CMAKE_TOOLCHAIN_FILE: set to ~/Dev/Droid/android.toolchain.cmake

Click on “configure” then “generate”.

_images/cmake-gui.png

Warning

Do not compile debug and release with the same Build and Install folders. If you followed the recommended folder layout, this should be fine.

Warning

For Windows host platform, do not put the bin/ folder of git in the PATH of your build terminal. It may cause troubles with Qt compilation (some Makefiles are badly generated with simple quotes).

Warning

Do NOT use ninja to compile the dependencies, it causes conflict with qt compilation.

If you get compilation errors at this step, please ensure you installed all the requirements, especially those for Qt.

Source
  • Clone fw4spl repository into your source directory:
$ cd Dev/Src
$ git clone https://github.com/fw4spl-org/fw4spl.git fw4spl
  • Go into fw4spl folder and update to the latest stable version:
$ cd fw4spl
$ git checkout fw4spl_0.11.0
  • Clone fw4spl-droid repository into your source directory:
$ cd ..
$ git clone https://github.com/fw4spl-org/fw4spl-droid.git fw4spl-droid
$ cd fw4spl-droid
$ git checkout fw4spl_0.11.0
  • Go into your Build directory (Debug or Release) : here an example if you want to compile in debug:
$ cd Dev/Build/Debug
  • Now you have to configure the project. You can use one of the three tools explained above.

Also, for FW4SPL, we recommend to use the Ninja generator. It builds faster, and it is much better for everyday use because it is fast as hell to check the files you need to compile. In other words, with Ninja the compilation starts instantly whereas Make spends a dozen of seconds to check what should be compiled before actually compiling something. So if you plan to develop with FW4SPL, go with Ninja. If you only want to give a single try, you can live with the standard “Unix Makefiles” generator.

To use make, here with ccmake :

$ ccmake ../../Src/fw4spl

To use ninja :

$ ccmake -G Ninja ../../Src/fw4spl
  • Change the following cmake arguments
    • CMAKE_INSTALL_PREFIX: set the install location (~/Dev/Install/Debug or Release)
    • CMAKE_BUILD_TYPE: set to Debug or Release
    • EXTERNAL_LIBRARIES: set the install path of the third party libraries you compiled before.(ex : ~/Dev/Deps/Install/Debug)
    • PROJECT_TO_BUILD: set the list of the projects you want to build (ex: PoC09Android, ...), each project should be separated by ”;”
    • PROJECT_TO_INSTALL: set the name of the application to install
    • CROSS_COMPILING: set to ‘ON’

Press “c” to configure. Now you have got two new variables to set:

  • ANDROID_NATIVE_API_LEVEL: set to ‘21’
  • CMAKE_TOOLCHAIN_FILE: set to ~/Dev/Droid/android.toolchain.cmake

Note

  • If PROJECT_TO_BUILD is empty, all application will be compiled
  • If PROJECT_TO_INSTALL is empty, no application will be installed
_images/osx_cmake_fw4spl.png

Press “c” to configure and then “g” to generate the makefiles.

Note

To generate the projects in release mode, change CMake argument CMAKE_BUILD_TYPE to Release both for fw4spl and fw4spl-deps

Then, according to the generator you chose, build FW4SPL with make :

# Adjust the number of cores depending of the CPU cores and the RAM available on your computer
$ make -j4

Or with ninja:

$ ninja

If you didn’t specify anything in PROJECT_TO_BUILD you may also build specific targets, for instance:

$ ninja PoC09Android

To deploy the application, connect your Android device to the USB port, be sure that it is in developer mode and that USB debugging is enabled. Then run:

$ ninja install

Thanks to Gradle and adb, the application will be packaged and copied automatically to your device.

Software Architecture Description (SAD)

General

Introduction

The framework FW4SPL (FrameWork for Software Production) is an open-source framework, developed by IRCAD (research institute against cancer and disease). The purpose of FW4SPL is to ease the creation of applications in the medical imaging field. Therefore it provides features like digital image processing in 2D and 3D, visualization or simulation of medical interactions.

FW4SPL is based on a component architecture composed of C++ libraries. The three main concepts of the architecture, explained in the following sections, are:

  • object-service concept
  • component approach
  • signal-slot communication

The framework is multi-platform and runs under Windows, Linux, MacOS and Android. Building an application with FW4SPL only requires to write one or several XML files. Its functionalities can be also extended by writing new components in C++, which is the coding language of the framework.

This document will introduce the general architecture of FW4SPL.

Object-Service concept

Introduction

In the Object Oriented Programming (OOP) paradigm, an object is an instance of a class which contains the data and the algorithms. For instance, a class Image contains the image buffer, the size and format attributes, along with the methods to process the image, such as reading, writing, visualizing, analysing, etc.

This design works well, but has some drawbacks. First the code of the class implementation can become very big if you put everything in it, making collaborative work harder. Then if you use different third-party libraries in your methods (for instance DCMTK for I/O, VTK for visualization, OpenCV or ITK for the filtering methods), your class becomes dependent of all of these libraries even if you only need one or two functionalities. If we want something modular, that does not work. Last, because of the two previous points, the maintenance of source code is quite tough.

Instead, FW4SPL proposes an Object-Service paradigm where data and algorithms are separated into different code units.

Object

Objects represent data used in the framework. They can be simple (boolean, integer, string, etc.) or advanced structures (image, mesh, video, patient, etc.). They are generic, which means they do not depend on the original input format or the future output format. This way they can used with different third-party libraries, and we provide helper methods to convert them into the corresponding formats.

These object classes contain only data features and their corresponding getter/setter methods.

For instance, the Image object:

  • contains a buffer pointer, a buffer size, the image’s dimension and origin,
  • has public setter/getter methods to access these members,
  • does not have methods such as reading or writing a buffer

The fwData library contains the standard simple and advanced data. It is the main data library of FW4SPL. There is also the fwMedData library which contains several structures to store medical data. A data list with a brief description is available in the appendixes.

Creating data

New data must be created as described below.

In the header file (MyData.hpp):

class MyData : public ::fwData::Object
{

public :
    fwCoreClassDefinitionsWithFactoryMacro( (MyData)(::fwData::Object),
        (()), ::fwData::factory::New< MyData >) ;


    // Private constructor, required for data factory
    MyData(::fwData::Object::Key key);

    /// Destructor, required for all data
    virtual ~MyData();

    /// Defines shallow copy, required for all data
    void shallowCopy( const Object::csptr& _source );

    /// Defines deep copy, required for all data
    void cachedDeepCopy(const Object::csptr& _source,
                            DeepCopyCacheType &cache);

};

In the source file (MyData.cpp), this line must also be added to declare MyClass as data of the framework architecture :

fwDataRegisterMacro( MyData );

Service

A service represents a functionality which uses or modifies data. It is associated with one or several objects. For example, a service working on a single image could be a reader, a writer, or a visualization service. A service working on two images could be a filtering service, or a service working on a image and a mesh, a mesher.

Service type

Some service categories exist in FW4SPL. These categories are called service types and are represented by an abstract class. The basic service types are:

  • io::IReader: base interface for reader services.
  • io::IWriter: base interface for writer services.
  • fwGui::IActionSrv: base interface to manage action from a button or a menu in the GUI.
  • gui::editor::IEditor: base interface to create new widget in the GUI.
  • fwRender::IRender: base interface to create new visualization widgets in the GUI.
  • fwServices::IController: does nothing in particular but can be considered as a default service type to be implemented by unclassified services.

All services require a type association and must inherit from an abstract type service.

Service methods

Several methods exist to manipulate a service. The main methods are: configure, start, stop, and update.

  • configure: parses the service parameters and analyzes its configuration. For example, this method is used to configure an image file path on the file system for an image reader service.
  • start: initializes and launches the service (be careful, starting and instantiating a service is not the same thing. For example, for a visualization service, the start method instantiates all GUI widgets necessary to visualize the data but the service itself is instantiated before.).
  • stop: stops the service. For example, for a visualization service, this method detaches and destroys all GUI widgets previously instantiated earlier in the start method.
  • update method is called to perform an action on the data associated with the service. For example, for an image reader service, the service reads the image, converts it and loads it into the associated data.

These methods are mandatory, but can be empty. This is because some services do not need a configuration step, a start/stop process, or an update process.

Service states

These methods must follow a calling sequence. For example, it is not possible to stop a service before starting it. To secure the process, a state machine has been implemented to control the calling sequence.

The calling sequence to manage a service is:

MyData::sptr myData = MyData::New();
MyService::sptr mySrv = MyService::New();
mySrv->setObject(myData);

mySrv->setConfiguration( ... ); // set parameters
mySrv->configure(); // check parameters
mySrv->start(); // start the service
mySrv->update(); // update the service
mySrv->stop(); // stop the service

Note

FW4SPL extensively uses std::shared_ptr to handle objects and services. The basic declaration macros of data and services define a typedef sptr as an alias to std::shared_ptr<this_class> and a typedef csptr as an alias to std::shared_ptr<const this_class>.

Create a service

A new service must be created as described below.

In the header file (MyService.hpp):

class MyService : public AbstractServiceType
{
public:

    // Macro to define few important parameters/functions used by the architecture
    fwCoreServiceClassDefinitionsMacro((MyService)(AbstractServiceType));

    // Service constructor
    MyService() throw() ;

    // Service destructor.
    virtual ~MyService() throw() ;

protected:

    // To configure the service
    void configuring() throw(fwTools::Failed);

    // To start the service
    void starting() throw(::fwTools::Failed);

    // To stop the service
    void stopping() throw(::fwTools::Failed);

    // To update the service
    void updating() throw(::fwTools::Failed);
};

In the source file (MyService.cpp), this line must also be added to declare MyService as a service of the framework architecture:

fwServicesRegisterMacro( AbstractServiceType, MyService, MyData );

Note

When a new service is created, the following functions must be overriden from IService class : configuring, starting, stopping and updating. The top level functions from IService class check the service state before any call to the overridden method.

Object and service factories

To instantiate an object or a service, the architecture requires the use of a factory system. In class-based programming, the factory method pattern is a creational pattern which uses factory methods to deal with the problem of creating classes without specifying the exact class that will be created. This is done by creating classes via a factory method, which is either specified in an interface (abstract class) and implemented in child classes (concrete classes) or implemented in a base class (optionally as a template method), which can be overridden when inherited in derivative classes; rather than by a constructor.

Object factory

The fwData library has a factory to register and create all objects. The registration is managed by two macros:

// in .hpp file
fwCoreClassDefinitionsWithFactoryMacro( (MyData)(::fwData::Object),
    (()), ::fwData::factory::New< MyData >);

// in .cpp file
fwDataRegisterMacro( MyData );

Then, there data can be instantiated in two ways:

// Direct creation
MyData::sptr obj = MyData::New();

// Factory creation (here obj is an object of type
// MyData, it is then possible to cast it dynamically)
::fwData::Object::sptr obj = ::fwData::factory::New("MyData");
MyData::sptr myData = MyData::dynamicCast(obj);
Service factory

The fwService library has a factory to register and create all services. The registration is managed by two macros:

// in .hpp file
fwCoreServiceClassDefinitionsMacro ((MyService)(AbstractServiceType));

// in .cpp file
fwServicesRegisterMacro( AbstractServiceType, MyService, MyData );

Then, there is only one way to build a service in the framework:

::fwServices::registry::ServiceFactory::sptr srvFactory
        = ::fwServices::registry::ServiceFactory::getDefault();

// Factory creation (here srv is a service of type MyService,
// it is possible to cast it)
::fwServices::IService::sptr srv = srvFactory->create("MyService");

Object-Service registry

The FW4SPL architecture is standardized thanks to:

  • Abstract classes fwData::Object and fwService::IService.
  • The two factory systems.

In an application, one of the problems is managing the life cycle of a large number of object instances and their services. This problem is solved by the class fwServices::registry::ObjectService which maintains the relationship between objects and services. This class concept is very simple :

// OSR is a singleton
class ObjectService
{
public:
  // ...

  // Associates a service to an object
  void registerService ( ::fwData::Object::sptr obj,
                         const ::fwServices::IService::KeyType& objKey,
                         ::fwServices::IService::AccessType access,
                         ::fwServices::IService::sptr service);

  // Dissociates a service from an object
  void unregisterService ( const ::fwServices::IService::KeyType& objKey,
                           ::fwServices::IService::AccessType access,
                           IService::sptr service );

  // ...
}

This registry manages the object-service relationships and guarantees the non-destruction of an object while some services are still working on it.

Each object associated with the service must provide a key and an access type. The key is used to retrieve the object in the service code, while the access type tells how the object can be accessed: read, read/write or write.

Object retrieval

Thus, to retrieve the registered objects of a service, there are two different methods :

class IService
{
public:
  // ...
  template<class DATATYPE> CSPTR(DATATYPE) getInput( const KeyType& key) const;
  template<class DATATYPE>  SPTR(DATATYPE) getInOut( const KeyType& key) const;
  // ...
};

For instance, if we have a ::fwData::Image registered as "image" key with INOUT access type, and a ::fwData::Mesh registered as "mesh" key with IN access type we can retrieve them in a method of the service this way:

::fwData::Image::sptr image = this->getInOut< ::fwData::Image>("image");
::fwData::Mesh::csptr mesh  = this->getInput< ::fwData::Mesh>("mesh");

Object access type

How to choose between the different access type for a given data ?

  1. Read-only (IN)
    • If you don’t modify the data and so that means you can deal with a const pointer on the data, then this is the right choice.
  2. Write-only (OUT)
    • This is a special case when the service will actually create the data. The data doesn’t exist before the service creation. At some point, during start(), or update() or elsewhere, the data is allocated, filled and registered in the OSR :
::IService::setOutput(const KeyType& key, const ::fwData::Object::sptr& object);
//..
::fwData::Image::sptr image = ::fwData::Image::New();
this->setOutput("outputImage", image);
  1. Read-Write
    • The object already exists, and you need to modify it.

This topic is explained more widely in the AppConfig section.

Object-Service concept example

To conclude, the generic object-service concept is illustrated with this example:

// Create an object
::fwData::Object::sptr obj = ::fwData::factory::New("::fwData::Image");

// Create a reader and a view for this object
::fwServices::IService::sptr reader
    = ::fwServices::add(obj, "::io::IReader", "MyCustomImageReader");
::fwServices::IService::sptr view
    = ::fwServices::add(obj, "::fwRender::IRender", "MyCustomImageView");

// Configure and start services
reader->setConfiguration ( /* ... */ );
reader->configure();
reader->start();

view->setConfiguration ( /* ... */ );
view->configure();
view->start();

// Execute services
reader->update(); // Read image on filesystem
view->update(); // Refresh visualization with the new image buffer

// Stop services
reader->stop();
view->stop();

// Destroy services
::fwServices::registry::ObjectService::unregisterService(reader);
::fwServices::registry::ObjectService::unregisterService(view);

This example shows the code to create a small application to read an image and visualize it. You can easily transform this code to build an application which reads and displays a 3D mesh by changing object and services implementation strings only.

However, most applications made with FW4SPL are not built this way. Instead, we use AppConfig, which allows to simplify the code above by a declarative approach based on XML files.

Signal-slot communication

Overview

“Signals and slots” is a language construct introduced in Qt [1] for communication between objects.

[1]http://wiki.qt.io/Qt_signal-slot_quick_start

The concept is that objects and services(explained in 2.3) can send signals containing event information which can be received by other services using special functions known as slots.

FW4SPL implementation

In the FW4SPL architecture, the library fwCom provides a set of tools dedicated to communication. These communications are based on the signal and slot concept.

fwCom provides the following features :

  • function and method wrapping
  • direct slot calling
  • asynchronous slot calling
  • ability to work with multiple threads
  • auto-disconnection of slot and signals
  • arguments loss between slots and signals

Slots

Slots are wrappers for functions and class methods that can be attached to a fwThread::Worker. The purpose of this class is to provide synchronous and asynchronous mechanisms for method and function calling.

Slots have a common base class : SlotBase. This allows the storage of them in the same container. Slots are designed such that they can be called, even where only the argument type is known.

Examples :

A slot wrapping the function sum, which is a function with the signature int (int, int) :

::fwCom::Slot< int (int, int) >::sptr slotSum = ::fwCom::newSlot( &sum );

A slot wrapping the function start with signature void() of the object a which class type is A :

::fwCom::Slot< void() >::sptr slotStart = ::fwCom::newSlot(&A::start, &a);

Execution of the slots using the run method :

slotSum->run(40,2);
slotStart->run();

Execution of the slots using the method call, which returns the result of the execution :

int result = slotSum->call(40,2);
slotStart->call();

A slot declaration and execution, through a SlotBase :

::fwCom::Slot< size_t (std::string) > slotLen
        = ::fwCom::Slot< size_t (std::string) >::New( &len );
::fwCom::SlotBase::sptr slotBaseLen = slotLen;
::fwCom::SlotBase::sptr slotBaseSum = slotSum;
slotBaseSum->run(40,2);
slotBaseSum->run<int, int>(40,2);

// This one needs the  explicit argument type
slotBaseLen->run<std::string>("R2D2");
result = slotBaseSum->call<int>(40,2);
result = slotBaseSum->call<int, int, int>(40,2);
result = slotBaseLen->call<size_t, std::string>("R2D2");

Signals

Signals allow to perform grouped calls on slots. For this purpose, a signal class provides a mechanism to connect slots.

Examples:

The following instruction declares a signal with a void signature.

::fwCom::Signal< void() >::sptr sig = ::fwCom::Signal< void() >::New();

The connection between a signal and a slot of the same information type:

sig->connect(slotStart);

The following instruction will trigger the execution of all slots connected to this signal:

sig->emit();

It is possible to connect multiple slots with the same information type to the same signal and trigger their simultaneous execution.

Signals can take several arguments as a signature which will trigger their connected slots by passing the right arguments.

In the following example a signal is declared of type void(int, int). The signal is connected to two different types of slot, void (int) and int (int, int).

using namespace fwCom;
Signal< void(int, int) >::sptr sig2 = Signal< void(int, int) >::New();
Slot< int(int, int) >::sptr    slot1 = Slot< int(int, int) >::New(...);
Slot< void(int) >::sptr        slot2 = Slot< void(int) >::New(...);

sig2->connect(slot1);
sig2->connect(slot2);

sig2->emit(21, 42);

Here 2 points need to be highlighted :

  • A signal cannot return a value. Consequently their return type is void. Thus, the return value of a slot, triggered by a signal, equally cannot be retrieved.
  • To successfully trigger a slot using a signal, the minimum requirement as to the number of arguments or fitting argument types has to be given by the signal. In the last example the slot slot2 only requires one argument of type int, but the signal is emitting two arguments of type int. Because the signal signature fulfills the slot’s argument number and argument type, the signal can successfully trigger the slot slot2. The slot slot2 takes the first emitted argument which fits its parameter (here 21, the second argument is ignored).
Disconnection

The disconnect method is called between one signal and one slot, to stop their existing connection. A disconnection assumes a signal slot connection. Once a signal slot connection is disconnected, it cannot be triggered by this signal. Both connection and disconnection of a signal slot connection can be done at any time.

sig2->disconnect(slot1);
sig2->emit(21, 42); // do not trigger slot1 anymore

The instructions above will cause the execution of slot2. Due to the disconnection between sig2 and slot1, the slot slot1 is not triggered by sig2.

Connection handling

The connection between a slot and a signal returns a connection handler:

::fwCom::Connection connection = signal->connect(slot);

Each connection handler provides a mechanism which allows a signal slot connection to be disabled temporarily. The slot stays connected to the signal, but it will not be triggered while the connection is blocked :

::fwCom::Connection::Blocker lock(connection);
signal->emit();
// 'slot' will not be executed while 'lock' is alive or until lock is
// reset

Connection handlers can also be used to disconnect a slot and a signal :

connection.disconnect();
// slot is not connected anymore
Auto-disconnection

Slots and signals can handle an automatic disconnection :

  • on slot destruction : every signal slot connection to this slot will be destroyed
  • on signal destruction : every slot connection to the signal will be destroyed

All related connection handlers will be invalidated when an automatic disconnection occurs.

Manage slots or signals in a class

The library fwCom provides two helper classes to manage signals or slots in a structure.

HasSlots

The class HasSlots offers mapping between a key (string defining the slot name) and a slot. HasSlots allows the management of many slots using a map. To use this helper in a class, the class must inherit from HasSlots and must register the slots in the constructor:

struct ThisClassHasSlots : public HasSlots
{
  typedef Slot< int()> GetValueSlotType;

  ThisClassHasSlots()
  {
      newSlot("sum", &SlotsTestHasSlots::getValue, this);
  }

  int sum(int a, int b)
  {
      return a+b;
  }

  int getValue()
  {
      return 4;
  }
};

Then, slots can be used as below :

ThisClassHasSlots obj;
obj.slot("sum")->call<int>(5,9);
obj.slot< ThisClassHasSlots::GetValueSlotType >("getValue")->call();
HasSignals

The class HasSignals provides mapping between a key (string defining the signal name) and a signal. HasSignals allows the management of many signals using a map, similar to HasSlots. To use this helper in a class, the class must inherit from HasSignals as seen below and must register signals in the constructor:

struct ThisClassHasSignals : public HasSignals
{
  typedef ::fwCom::Signal< void()> SignalType;

  ThisClassHasSignals()
  {
      newSignal< SignalType >("sig");
  }
};

Then, signals can be used as below:

ThisClassHasSignals obj;
Slot< void()>::sptr slot = ::fwCom::newSlot(&anyFunction)
obj.signal("sig")->connect( slot );
obj.signal< SignalsTestHasSignals::SignalType >("sig")->emit();
obj.signal("sig")->disconnect( slot );

Signals and slots used in objects and services

Signals are used in both objects and services, whereas slots are only used in services. The abstract class fwData::Object inherits from the HasSignals class as a basis to use signals :

class Object : public ::fwCom::HasSignals
{
  /// Key in m_signals map of signal m_sigObjectModified
  static const ::fwCom::Signals::SignalKeyType s_MODIFIED_SIG;
  //...

  /// Type of signal m_sigObjectModified
  typedef ::fwCom::Signal< void ( CSPTR( ::fwServices::ObjectMsg ) ) >
                ObjectModifiedSignalType;

  /// Signal that emits an ObjectMsg when an object is modified
  ObjectModifiedSignalType::sptr m_sigObjectModified;

  Object()
  {
      m_sigObjectModified = newSignal< ObjectModifiedSignalType >(s_MODIFIED_SIG);
      //...
  }
}

Moreover the abstract class fwService::IService inherits from the HasSlots class and the HasSignals class, as a basis to communicate through signals and slots. Actually, the methods start(), stop(), swap() and update() are all slots. Here is an extract with update():

class IService : public ::fwCom::HasSlots, public ::fwCom::HasSignals
{
  typedef ::boost::shared_future< void > SharedFutureType;

  /// Key in m_slots map of slot m_slotUpdate
  static const ::fwCom::Slots::SlotKeyType s_UPDATE_SLOT;

  /// Type of signal m_slotUpdate
  typedef ::fwCom::Slot<SharedFutureType()> UpdateSlotType;

  /// Slot to call update method
  UpdateSlotType::sptr m_slotUpdate;

  IService()
  {
      //...
      m_slotUpdate = newSlot( s_UPDATE_SLOT, &IService::update, this ) ;
      //...
  }

  //...
}

To automatically connect object signals and service slots, it is possible to override the method IService::getAutoConnections(). Please note that to be effective the attribute “autoConnect” of the service must be set to “yes” in the xml configuration (see App-config). The default implementation of this method connect the s_MODIFIED_SIG object signal to the s_UPDATE_SLOT slot.

IService::KeyConnectionsMap IService::getAutoConnections() const
{
    KeyConnectionsMap connections;
    connections.push( "data1", ::fwData::Object::s_MODIFIED_SIG, s_UPDATE_SLOT ) );
    connections.push( "data2", ::fwData::Object::s_MODIFIED_SIG, s_UPDATE_SLOT ) );
    return connections;
}

Object signals

Objects have signals that can be used to inform of modifications. The base class ::fwData::Object has the following signals available.

Objects Available messages
Object {modified, addedFields, changedFields, removedFields}

Thus all objects in FW4SPL can use the previous signals. Some object classes define extra signals.

Objects Available messages
Composite {addedObjects, changedObjects, removedObjects}
Graph {updated}
Image {bufferModified, landmarkAdded, landmarkRemoved, landmarkDisplayed, distanceAdded, distanceRemoved, distanceDisplayed, sliceIndexModified, sliceTypeModified, visibilityModified, transparencyModified}
Mesh {vertexModified, pointColorsModified, cellColorsModified, pointNormalsModified, cellNormalsModified, pointTexCoordsModified, cellTexCoordsModified}
ModelSeries {reconstructionsAdded, reconstructionsRemoved}
PlaneList {planeAdded, planeRemoved, visibilityModified}
Plane {selected}
PointList {pointAdded, pointRemoved}
Reconstruction {meshModified, visibilityModified}
ResectionDB {resectionAdded, safePartAdded}
Resection {reconstructionAdded, pointTexCoordsModified}
Vector {addedObjects, removedObjects}
... ...

Proxy

The class ::fwServices::registry::Proxy is a communication element and singleton in the architecture. It defines a proxy for signal/slot connections. The proxy concept is used to declare communication channels: all signals registered in a proxy’s channel are connected to all slots registered in the same channel. This concept is useful to create multiple connections or when the slots/signals have not yet been created (possible in dynamic programs).

The following shows an example where one signal is connected to several slots:

const std::string CHANNEL = "myChannel";

::fwServices::registry::Proxy::sptr proxy
    = ::fwServices::registry::Proxy::getDefault();

::fwCom::Signal< void() >::sptr sig = ::fwCom::Signal< void() >::New();

::fwCom::Slot< void() >::sptr slot1 = ::fwCom::newSlot( &myFunc1 );
::fwCom::Slot< void() >::sptr slot2 = ::fwCom::newSlot( &myFunc2 );
::fwCom::Slot< void() >::sptr slot3 = ::fwCom::newSlot( &myFunc3 );

proxy->connect(CHANNEL, sig);

proxy->connect(CHANNEL, slot1);
proxy->connect(CHANNEL, slot2);
proxy->connect(CHANNEL, slot3);

sig->emit(); // All slots are called

App-config

Dynamic program with factories

As shown in the Object-Service concept example, it is easy to change an application’s behaviour by simply changing the appropriate data and services. For example changing an image visualisation application to a 3D model visualisation application. Unfourtunely, this is limited to applications based on one service and one data, and thus would impossible to apply on applications containing multiple services and object.

To overcome this, the FW4SPL architecture provides a dynamic management of configurations to allow the use of multiple objects and services.

The xml configuration for an application is defined with the extension ::fwServices::registry::AppConfig.

Dynamic program with application configuration

In the fwServices library, an application configuration parser allows to parse XML files and creates and manages objects, services and communications.

// The parser
void main (int argc , char * argv [])
{
    string xmlAppConfigPath = argv [1];

    ::fwServices::AppConfigManager::sptr acm
            = ::fwServices::AppConfigManager::New();

    acm->setConfig(xmlAppConfigPath);
    acm->create(); // Creates objects and services from config.
    acm->start(); // Starts services specified in config.
    acm->update(); // Updates services specified in config.

    acm->stop(); // Stops services specified in config.
    acm->destroy(); // Destroy all services and then data.
}

The following part corresponds to the configuration XML file of the previous Object-Service concept example.

<object uid="image" type ="::fwData::MyData" />

<service uid="frame" impl="DefaultFrame" type="IFrame">
    <!-- service configuration -->
</service>

<service uid="view" impl="MyCustomImageView" type="::fwRender::IRender">
    <in key="object" uid="image" />
    <!-- service configuration -->
</service>

<service uid="reader" impl="MyCustomImageReader" type="::io::IReader" >
    <in key="object" uid="image" />
    <!-- service configuration -->
</service>

<!-- view listen now image modification -->
<connect>
    <signal>image/objectModified</signal>
    <slot>view/receive</slot>
</connect>

<start uid="frame" />
<start uid="view"/>
<start uid="reader"/>

<!-- Read the image on filesystem and notify
     the view to refresh is content -->
<update uid ="reader"/>

This simple example shows how it is possible to build an application with several objects and services using only a program and its configurations files.

Example

<extension implements="::fwServices::registry::AppConfig2">
    <id>myAppConfigId</id>
    <parameters>
        <param name="appName" default="my Application" />
        <param name="appIconPath" />
    </parameters>
    <desc>Image Viewer</desc>
    <config>

        <object uid="myImage" type="::fwData::Image" />

        <!--
            Description service of the GUI:
            The ::gui::frame::SDefaultFrame service automatically positions the various
            containers in the application main window.
            Here, it declares a container for the 3D rendering service.
        -->
        <service uid="myFrame" type="::gui::frame::SDefaultFrame">
            <gui>
                <frame>
                    <name>${appName}</name>
                    <icon>${appIconPath}</icon>
                    <minSize width="800" height="600" />
                </frame>
            </gui>
            <registry>
                <!-- Associate the container for the rendering service. -->
                <view sid="myRendering" />
            </registry>
        </service>

        <!--
            Reading service:
            The <file> tag defines the path of the image to load. Here, it is a relative
            path from the repository in which you launch the application.
        -->
        <service uid="myReaderPathFile" type="::ioVTK::SImageReader">
           <inout key="target" uid="myImage" />
           <file>./TutoData/patient1.vtk</file>
        </service>

        <!--
            Visualization service of a 3D medical image:
            This service will render the 3D image.
        -->
        <service uid="myRendering" type="::vtkSimpleNegato::SRenderer">
           <in key="image" uid="myImage" />
        </service>

        <!--
            Definition of the starting order of the different services:
            The frame defines the 3D scene container, so it must be started first.
            The services will be stopped the reverse order compared to the starting one.
        -->
        <start uid="myFrame" />
        <start uid="myReaderPathFile" />
        <start uid="myRendering" />

        <!--
            Definition of the service to update:
            The reading service load the data on the update.
            The render update must be called after the reading of the image.
        -->
        <update uid="myReaderPathFile" />
        <update uid="myRendering" />

    </config>
</extension>
Parameters
id

The id is the configuration identifier, and is thus unique to each configuration.

parameters (optional)

The parameters is a list of the parameters used by the configuration.

  • param:

    defines the parameter

    • name:

      parameter name, used as ${paramName} in the configuration. It will be replaced by the string defined by the service, activity or application that launchs the configuration.

    • default (optional):

      default value for the parameter, it is used if the value is not given by the config launcher.

desc (optional)

The description of the application.

Object

the <object> tags define the objects of the AppConfig.

  • uid (optional):

    Unique identifier of the object (::fwTools::fwID). If it is not defined, it will be automatically generated.

  • type:

    Object type (ex: ::fwData::Image, ::fwData::Composite)

  • src (optional, “new” by default)

    possible values: “new”, “ref”, “deferred”

    • “new” : defines that the object should be created
    • “ref” : defines that the object already exists in the application. The uid must be the same as the first declaration of this object (with “new”).
    • “deferred” : defines that the object will be created later (by a service).
Specific object configuration
  • matrix (optional):

    It works only for ::fwData::TransformationMatrix3D objects. It defines the value of the matrix.

<object uid="matrix" type="::fwData::TransformationMatrix3D">
    <matrix>
    <![CDATA[
        1  0  0  0
        0  1  0  0
        0  0  1  0
        0  0  0  1
    ]]>
    </matrix>
</object>
  • value (optional):

    Only these objects contain this tag : ::fwData::Boolean, ::fwData::Integer, ::fwData::Float and ::fwData::String. It allows to define the value of the object.

<object type="::fwData::Integer">
    <value>42</value>
</object>
  • colors (optional):

    Only ::fwData::TransferFunction contains this tag. It allows to fill the transfer function values.

<object type="::fwData::TransferFunction">
    <colors>
        <step color="#ff0000ff" value="1" />
        <step color="#ffff00ff" value="500" />
        <step color="#00ff00ff" value="1000" />
        <step color="#00ffffff" value="1500" />
        <step color="#0000ffff" value="2000" />
        <step color="#000000ff" value="4000" />
    </colors>
</object>
  • item (optional):

    It defines a sub-object of a composite or a field of any other object.

    • key: key of the object
    • object: the ‘item’ tag can only contain ‘object’ tags that represents the sub-object
<item key="myImage">
    <object uid="myImageUid" type="::fwData::Image" />
</item>
Service

The <service> tags represent a service working on the object(s). Services list the data the use and how they access them. Some services needs a specific configuration, it is usually described in the doxygen.

  • uid (optional):

    Unique identifier of the service. If it is not defined, it will be automatically generated.

  • impl:

    Service implementation type (ex: ::ioVTK::SImageReader)

  • type (optional):

    Service type (ex: ::io::IReader)

  • autoConnect (optional, “no” by default):

    Defines if the service receives the signals of the working object

  • worker (optional):

    Allows to run the service in another worker (see Multithreading)

<service uid="mesher" type="::opMesh::SMesher">
    <in key="image" uid="imageId" />
    <out key="mesh" uid="meshId" />
</service>
  • in:

    input object, it is const and cannot be modified

  • inout:

    input object that can be modified

  • out:

    output object, it must be created by a service and registered with the ‘setOutput(key, obj)’ method. The output object must be declared as “deferred” in the <object> declaration.

    • key : object key used to retrieve the object into the service
    • uid : unique identifier of the object declared in the <object> tag
    • optional : (optional, default “no”, values: “yes” or “no”) If “yes”, the service can be started even if the object is not present. By definition, the output objects are always optional.
::fwData::Image::csptr image = this->getInput< ::fwData::Image >("image");
::fwData::Mesh::sptr mesh = ::fwData::Mesh::New();
// mesher .....
this->setOutput("mesh", mesh);
Connection
  • connect (optional):

    allows to connect one or more signal(s) to one or more slot(s). The signals and slots must be compatible.

    • channel (optional):

      name of the channel use for the connections.

<connect channel="myChannel">
    <signal>object_uid/signal_name</signal>
    <slot>service_uid/slot_name</slot>
</connect>
Start-up
  • start:

    defines the service to start when the AppConfig is launched. The services will be automatically stopped in the reverse order when the AppConfig is stopped.

<start uid="service_uid" />

The service using “deferred” object as input will be automatically started when the object is created.

  • update:

    defines the service to update when the AppConfig is launched.

<update uid="service_uid" />

Activities

An activity is defined by the extension ::fwActivities::registry::Activities. It is used to launch an AppConfig with the selected data, it will create a new data ::fwMedData::ActivitySeries that inherits from a fwMedData::Series.

The service ::activities::action::SActivityLauncher allows to launch an activity. Its role is to create the specific Activity associated with the selected data.

This action should be followed by the service guiQt::editor::DynamicView : this service listens the action signals and launchs the activity in a new tab.

  • ::activities::action::SActivityLauncher uses the selected data to generate the activity.
  • ::guiQt::editor::DynamicView displays the activity in the application.
  • ::fwData::Vector contains the set of selected data .

Activity series

The ::fwMedData::ActivitySeries has a ::fwData::Composite that contains all the data required by the activity.

class FWMEDDATA_CLASS_API ActivitySeries : public ::fwMedData::Series
{
public:

    /// Constructor
    FWMEDDATA_API ActivitySeries();

    /// Destructor
    FWMEDDATA_API virtual ~ActivitySeries();

    /// Defines shallow copy
    FWMEDDATA_API void shallowCopy( const ::fwData::Object::csptr &_source );

    /// Defines deep copy
    FWMEDDATA_API void cachedDeepCopy( const ::fwData::Object::csptr &_source, DeepCopyCacheType &cache );

    /**
     * @brief Data container
     * @{ */
    ::fwData::Composite::sptr getData () const;
    void setData(const ::fwData::Composite::sptr & val);
    /**  @} */

    /**
     * @brief Activity configuration identifier
     * @{ */
    const std::string &getActivityConfigId () const;
    void setActivityConfigId (const std::string &val);
    /**  @} */

protected:

    /// Activity configuration identifier
    ConfigIdType m_activityConfigId;

    /// Data container
    ::fwData::Composite::sptr m_data;
};

Example

<extension implements="::fwActivities::registry::Activities">
   <id>myActivityId</id>
   <title>3D Visu</title>
   <desc>Activity description ...</desc>
   <icon>Bundles/media_0-1/icons/icon-3D.png</icon>
   <requirements>
        <requirement name="param1" type="::fwData::Image" /> <!-- defaults : minOccurs = 1, maxOccurs = 1-->
        <requirement name="param2" type="::fwData::Mesh" maxOccurs="3" >
            <key>Item1</key>
            <key>Item2</key>
            <key>Item3</key>
        </requirement>
        <requirement name="param3" type="::fwData::Mesh" maxOccurs="*" container="vector" />
        <requirement name="imageSeries" type="::fwMedData::ImageSeries" minOccurs="0" maxOccurs="2" />
        <requirement name="modelSeries" type="::fwMedData::ModelSeries" minOccurs="1" maxOccurs="1">
             <desc>Description of the required data....</desc>
             <validator>::fwActivities::validator::ImageProperties</validator>
        </requirement>
        <requirement name="transformationMatrix" type="::fwData::TransformationMatrix3D" minOccurs="0" maxOccurs="1" create="true" />
       <!-- ...-->
   </requirements>
   <builder>::fwActivities::builder::ActivitySeries</builder>
   <validator>::fwActivities::validator::ImageProperties</validator><!-- pour fw4spl_0.9.2 -->
   <appConfig id="myAppConfigId">
       <parameters>
           <parameter replace="registeredImageUid" by="@values.param1" />
           <parameter replace="orientation" by="frontal" />
           <!-- ...-->
       </parameters>
   </appConfig>
</extension>

The activity parameters are (in the following order):

id

The activity unique identifier.

title

The activity title that will be displayed on the tab.

desc

The description of the activity. It is displayed by the SActivityLauncher when several activity can be launched with the selected data.

icon

The path to the activity icon. It is displayed by the SActivityLauncher when several activity can be launched with the selected data.

requirements

The list of the data required to launch the activity. This data must be selected in the vector (::fwData::Vector).

requirement:

A required data.

name:
Key used to add the data in the activity Composite.
type:
The data type (ex: ::fwMedData::ImageSeries).
minOccurs (optional, “1” by default):
The minimum number of occurrences of this type of object in the vector.
maxOccurs (optional, “1” by default):
The maximum number of occurrences of this type of object in the vector.
container (optional, “vector” or “composite”, default: composite):
Container used to contain the data if minOccurs or maxOccurs are not “1”. If the container is “composite”, you need to specify the “key” of each object in the composite.
create (optional, default “false”):
If true and (minOccurrs == 0 && maxOccurs == 1), the data will be automatically created if it is not present.
desc (optional):
description of the parameter
validator (optional):
validator to check if the associated data is well formed (inherited of ::fwAtivities::IObjectValidator)
builder

Implementation of the activity builder. The default builder is ::fwActivities::builder::ActivitySeries : it creates the ::fwMedData::ActivitySeries and adds the required data in its composite with de defined key.

The builder ::fwActivities::builder::ActivitySeriesInitData allows, in addition to what the default builder does, to create data when minOccurs == 0 and maxOccurs == 0.

validators (optional)

It defines the list of validators. If you need only one validator, you don’t need the “validators” tag (only “validator”).

validator (optional):

It allows to validate if the selected required objects are correct for the activity.

For example, the validator ::fwActivities::validator::ImageProperties checks that all the selected images have the same size, spacing and origin.

appConfig

It defines the AppConfig to launch and its parameters

id:
Identifier of the AppConfig
parameters:
List of the parameters required by the AppConfig
parameter:

Defines a parameter

replace:
Name of the parameter as defined in the AppConfig
by:
Defines the string that will replace the parameter name. It should be a simple string (ex. frontal) or define a sesh@ path (ex. @values.myImage). The root object of the sesh@ path is the composite contained in the ActivitySeries.

Validators

There is three types of validator :

Pre-build validator

This type of validators checks if the current selection of data is correct to build the activity. It inherits of ::fwActivities::IValidator and must implement the methods:

ValidationType validate(
       const ::fwActivities::registry::ActivityInfo& activityInfo,
       SPTR(::fwData::Vector) currentSelection ) const;
Activity validator

This type of validator checks if the ::fwMedData::ActivitySeries is correct to launch its associated activity. It inherits of ::fwActivities::IActivityValidator and must implement the method:

ValidationType validate(const CSPTR(::fwMedData::ActivitySeries) &activity ) const;

The validator ::fwActivities::validator::DefaultActivity is applied if no other validator is defined. It checks if all the required objets are present in the series and if all the parameters delivered to the AppConfig are present.

It provides some method useful to implement your own validator.

Object validator

This type of validator checks if the required object is well formed. It can check a single object or a Vector or a Composite containing one type of object. It inherits of ::fwActivities::IObjectValidator and must implement the method:

ValidationType validate(const CSPTR(::fwData::Object) &currentData ) const;

Wizard

Services are available to create/launch activities :

SActivityLauncher

This action allows to launch an activity according to the selected data.

_images/SActivityLauncher.png
SCreateActivity

There is an action or an editor (`::activities::action::SCreateActivity or ::activities::editor::SCreateActivity). This services display the available activities according to the .configuration.

When the activity is selected, the service sends a signal with the activity identifier. It should works with the ::uiMedData::editor::SActivityWizard that creates or updates the activitySeries.

<service uid="action_newActivity" type="::activities::action::SCreateActivity">
    <!-- Filter mode 'include' allows all given activity id-s.
         Filter mode 'exclude' allows all activity id-s excepted given ones. -->
    <filter>
        <mode>include</mode>
        <id>2DVisualizationActivity</id>
        <id>3DVisualizationActivity</id>
        <id>VolumeRenderingActivity</id>
    </filter>
</service>
filter (optional):
it allows to filter the activity that can be proposed.
mode: ‘include’ or ‘exclude’.
Defines if the activity in the following list are proposed (include) or not (exclude).
id:
id of the activity
SActivityWizard

This editor allows to select the data required by an activity in order to create the ActivitySeries. This editor displays a tab widget (one tab by data). It works on a ::fwMedData::SeriesDB and adds the created activity series into the seriesDB.

_images/SActivityWizard.png

Multithreading

Overview

The multithreading paradigm has become more popular as efforts to further exploit instruction level parallelism have stalled since the late 1990s. This allowed the concept of throughput computing to re-emerge to prominence from the more specialized field of transaction processing:

  • Even though it is very difficult to further speed up a single thread or single program, most computer systems are actually multi-tasking among multiple threads or programs.
  • Techniques that would allow speedup of the overall system throughput of all tasks would be a meaningful performance gain.

Some advantages include:

  • If a thread gets a lot of cache misses, the other thread(s) can continue, taking advantage of the unused computing resources, which thus can lead to faster overall execution, as these resources would have been idle if only a single thread was executed.
  • If a thread cannot use all the computing resources of the CPU (because instructions depend on each other’s result), running another thread can avoid leaving these idle.
  • If several threads work on the same set of data, they can actually share their cache, leading to better cache usage or synchronization of its values.

Some criticisms of multithreading include:

  • Multiple threads can interfere with each other when sharing hardware resources such as caches or translation look aside buffers (TLBs).
  • Execution times of a single thread are not improved but can be degraded, even when only one thread is executing. This is due to slower frequencies and/or additional pipeline stages that are necessary to accommodate thread-switching hardware.
  • Hardware support for multithreading is more visible to software, thus requiring more changes to both application programs and operating systems than multiprocessing.
  • Thread scheduling is also a major problem in multithreading.

Michael K. Gschwind, et al. [1]

[1]Michael K. Gschwind, Valentina Salapura. 2011. Using Register Last Use Infomation to Perform Decode-Time Computer Instruction Optimization US 20130086368 A1 [Patent]. http://www.google.com/patents/US20130086368

Worker and Timer

In the FW4SPL architecture, the library fwThread provides few tools to execute asynchronous tasks on different threads.

In this library, the class Worker creates and manages a task loop. The default implementation creates a loop in a new thread. Some tasks can be posted on the worker and will be executed on the managed thread. When the worker is stopped, it waits for the last task to be processed and stops the loop.

::fwThread::Worker::sptr worker = ::fwThread::Worker::New();

::boost::packaged_task<void> task( ::boost::bind( &myFunction ) );
::boost::future< void > future = task.get_future();
::boost::function< void () > f = moveTaskIntoFunction(task);

worker->post(f);

future.wait();
worker->stop();

The Timer class provides single-shot or repetitive timers. A Timer triggers a function once after a delay, or periodically, inside the worker loop. The delay or the period is defined by the duration attribute.

::fwThread::Worker::sptr worker = ::fwThread::Worker::New();

::fwThread::Timer::sptr timer = worker->createTimer();

timer->setFunction(  ::boost::bind( &myFunction)  );

::boost::chrono::milliseconds duration
        = ::boost::chrono::milliseconds(100) ;
timer->setDuration(duration);

timer->start();
//...
timer->stop();

worker->stop();

Mutex

The namespace fwCore::mt provides common foundations for multithreading in FW4SPL, especially tools to manage mutual exclusions. In computer science, mutual exclusion refers to the requirement of ensuring that two concurrent threads are not in a critical section at the same time, it is a basic requirement in concurrency control, to prevent race conditions. Here, a critical section refers to a period when the process accesses a shared resource, such as shared memory. A lock system is designed to enforce a mutual exclusion concurrency control policy.

Currently, FW4SPL uses Boost Thread library which allows the use of multiple execution threads with shared data, keeping the C++ code portable. fwCore::mt defines a few typedef over Boost:

namespace fwCore
{
namespace mt
{

typedef ::boost::mutex Mutex;
typedef ::boost::unique_lock< Mutex > ScopedLock;

typedef ::boost::recursive_mutex RecursiveMutex;
typedef ::boost::unique_lock< RecursiveMutex > RecursiveScopedLock;

/// Defines a single writer, multiple readers mutex.
typedef ::boost::shared_mutex ReadWriteMutex;
/**
* @brief Defines a lock of read type for read/write mutex.
* @note Multiple read lock can be done.
*/
typedef ::boost::shared_lock< ReadWriteMutex > ReadLock;
/**
* @brief Defines a lock of write type for read/write mutex.
* @note Only one write lock can be done at once.
*/
typedef ::boost::unique_lock< ReadWriteMutex > WriteLock;
/**
* @brief Defines an upgradable lock type for read/write mutex.
* @note Only one upgradable lock can be done at once but there
        may be multiple read lock.
*/
typedef ::boost::upgrade_lock< ReadWriteMutex > ReadToWriteLock;
/**
* @brief Defines a write lock upgraded from ReadToWriteLock.
* @note Only one upgradable lock can be done at once but there
        may be multiple read lock.
*/
typedef ::boost::upgrade_to_unique_lock< ReadWriteMutex >
            UpgradeToWriteLock;

} //namespace mt
} //namespace fwCore

Multithreading and communication

Asynchronous call

Slots are able to work with fwThread::Worker. If a Slot has a Worker, each asynchronous execution request will be run in its worker, otherwise asynchronous requests can not be satisfied without specifying a worker.

Setting worker example:

::fwCom::Slot< int (int, int) >::sptr slotSum
        = ::fwCom::newSlot( &sum );
::fwCom::Slot< void () >::sptr slotStart
        = ::fwCom::newSlot( &A::start, &a );

::fwThread::Worker::sptr w = ::fwThread::Worker::New();
slotSum->setWorker(w);
slotStart->setWorker(w);

asyncRun method returns a boost::shared_future< void >, that makes it possible to wait for end-of-execution.

::boost::future< void > future = slotStart->asyncRun();
// do something else ...
future.wait(); //ensures slotStart is finished before continuing

asyncCall method returns a boost::shared_future< R > where R is the return type. This allows facilitates waiting for end-of-execution and retrieval of the computed value.

::boost::future< int > future = slotSum->asyncCall();
// do something else ...
future.wait(); //ensures slotStart is finished before continuing
int result = future.get();

In this case, the slots asynchronous execution has been weakened. For an async call/run pending in a worker queue, it means that :

  • if the slot is detroyed before the execution of this call, it will be canceled.
  • if slot’s worker is changed before the execution of this call, it will also be canceled.
Asynchronous emit

As slots can work asynchronously, triggering a Signal with asyncEmit results in the execution of connected slots in their worker :

sig2->asyncEmit(21, 42);

The instruction above has the consequence of running each connected slot in its own worker.

Note: Each connected slot must have a worker set to use asyncEmit.

Object-Service and Multithreading

Object

The architecture allows the writing of thread safe functions which manipulate objects easily. Objects have their own mutex (inherited from fwData::Object) to control concurrent access from different threads. This mutex is available using the following method:

::fwCore::mt::ReadWriteMutex & getMutex();

The namespace fwData::mt contains several helpers to lock objects for multithreading:

  • ObjectReadLock: locks an object mutex on read mode.
  • ObjectReadToWriteLock: locks an object mutex on upgradable mode.
  • ObjectWriteLock: locks an object mutex on exclusive mode.

The following example illustrates how to use these helpers:

::fwData::String::sptr m_data = ::fwData::String::New();
{
    // lock data to write
    ::fwData::mt::ObjectReadLock readLock(m_data);
} // helper destruction, data is no longer locked


{
    // lock data to write
    ::fwData::mt::ObjectWriteLock writeLock(m_data);

    // unlock data
    writeLock.unlock();

    // lock data to read
    ::fwData::mt::ObjectReadToWriteLock updrageLock(m_data);

    // unlock data
    updrageLock.unlock();

    // lock again data to read
    updrageLock.lock();

    // lock data to write
    updrageLock.upgrade();

    // lock data to read
    updrageLock.downgrade();

} // helper destruction, data is no longer locked
Services

The service architecture allows the writing of a thread-safe service by avoiding the requirement of explicit synchronization. Each service has an associated worker in which service methods are intended to be executed.

Specifically, all inherited IService methods (start, stop, update, receive, swap) are slots. Thus, the whole service life cycle can be managed in a separate thread.

Since services are designed to be managed in an associated worker, the worker can be set/updated by using the inherited method :

// Initializes m_associatedWorker and associates
// this worker to all service slots
void setWorker( ::fwThread::Worker::sptr worker );

// Returns associate worker
::fwThread::Worker::sptr getWorker() const;

Since the signal-slot communication is thread-safe and IService::receive(msg) method is a slot, it is possible to attach a service to a thread and send notifications to execute parallel tasks.

Note

Some services use or require GUI backend elements. Thus, they can’t be used in a separate thread. All GUI elements must be created and managed in the application main thread/worker.

Serialization

Overview

Serialization is the process to save plain C++ structures from memory to hard drive. In fw4spl, fwAtoms library provides tools to serialize all data (and especially Object that extend ::fwData::Object) to a JSON format [1]. Of course, this process is also available for loading data from JSON format to plain C++ structures.

[1]Introducing JSON. http://json.org/

To achieve this serialization, fwAtoms provides basic structures (which extend ::fwAtoms::Base) to manage better plain C++ structure evolution. Thus, there are two main steps in the serialization process:

  • Converting a ::fwData::Object into a ::fwAtoms::Object
  • Serializing a ::fwAtoms::Base in a JSON format

Atom objects

The basic structures provided by fwAtoms library are a set of restricted C++ type. All these structures extend ::fwAtoms::Base and cover all basic types and containers:

type brief
::fwAtoms::Base Base class of all atoms
::fwAtoms::String Atom to represent string types
::fwAtoms::Numeric Atom to represent numeric types (floating number or integer)
::fwAtoms::Boolean Atom to represent a boolean value
::fwAtoms::Map Atom to represent an associative container (std::string to ::fwAtoms::Base)
::fwAtoms::Sequence Atom to represent a sequence of object like vector or list
::fwAtoms::Object Atom to represent a C++ object with attributes
::fwAtoms::Blob Atom to represent binary information like buffers

For instance, consider the following C++ class:

class SimpleClass
{
    bool m_myBoolean;
};

class ComplexClass
{
    std::string m_myString;
    float m_myFloat;
    SimpleClass* m_mySimpleClass;
};

It’s Atom equivalent is (simplified code):

fwAtoms::Object
{
    metaInfos
    {
        "CLASSNAME_METAINFO" : "SimpleClass"
        "ID_METAINFO" : "<ID of the object>"
    }

    attributes
    {
        "myBoolean" : ::fwAtoms::Boolean
    }
}


fwAtoms::Object
{
    metaInfos
    {
        "CLASSNAME_METAINFO" : "ComplexClass"
        "ID_METAINFO" ; "<ID of the object>"
    }

    attributes
    {
        "myString" : ::fwAtoms::String
        "myFloat" : ::fwAtoms::Numeric
        "mySimpleClass" : ::fwAtoms::Object("SimpleClass")
    }
}

The main advantage of this representation is the ability to change easily the form of a class. In fact, all plain C++ objects are represented as Atoms::Object with a map of attributes. Thus, adding, removing or changing the content of an attribute is easy. Moreover, because of these restricted types, atom parsing is also made easier. The main difficulty is how to convert plain C++ object using this set of restricted types.

Convert a fwData::Object

As explained earlier, all objects in fw4spl inherit from the ::fwData::Object class. To convert a C++ object in Atom, it must inherit from this class. To allow this conversion, some work must be done.

The first thing is to update the header file of the structure and add these lines :

// Before all namespace
fwCampAutoDeclareDataMacro((<namespace elem>)
        (<namespace elem>)(<class name>), <method export macro>);

// In the public class part
fwCampMakeFriendDataMacro((<namespace elem>)
        (<namespace elem>)(<class name>));

These two functions allow the declaration of the class to the conversion process.

Next, the conversion systems must know the class information including attributes, base class, library location and data version. This is achieved by creating a class which defines these properties.

Example

This can be illustrated by taking the previous class and creating these two files:

Header file of the newly created class: ComplexClass.hpp

// Reference class

fwCampAutoDeclareDataMacro((fwData)(ComplexClass), FWDATA_API);

namespace fwData
{
class ComplexClass : public ::fwData::Object
{
    fwCampMakeFriendDataMacro((fwData)(ComplexClass));

    std::string m_myString;
    float m_myFloat;
    ::fwData::SimpleClass* m_mySimpleClass;
};
}

Header file of serialization class :

// hpp binding file
#include <fwCamp/macros.hpp>
#include <fwData/ComplexClass.hpp>
#include "fwDataCamp/config.hpp"

fwCampDeclareAccessor((fwData)(ComplexClass), (fwData)(SimpleClass));

Source file of serialization class :

// cpp binding file
// include previous cpp file

#include <fwCamp/UserObject.hpp>

fwCampImplementDataMacro((fwData)(ComplexClass))
{
    builder
        .tag("object_version", "1")
        .tag("lib_name", "fwData")
        .base< ::fwData::Object>()
        .property("myString" , &::fwData::ComplexClass::m_myString)
        .property("myFloat" , &::fwData::ComplexClass::m_myFloat)
        .property("mySimpleClass" , &::fwData::ComplexClass::m_mySimpleClass)
        ;
}

In a header file, the method fwCampDeclareAccessor is necessary when an object has a pointer or a smart pointer to another object.

In a source file, fwCampImplementDataMacro declares the properties of the bound object with an object called a builder: it provides several methods to describe the object to bind.

method brief
tag(key, value) Register a tag in the atom meta information.
base<BaseClass>() Identify the base class of the bound object
property(arg1, arg2) Set property of the object and how to access it

Most of the work is completed when the header file of the relevant class has been updated and a binding class created. The last step is to register the binding class in the conversion system using the following line in the library containing binding classes:

localDeclarefwDataComplexClass();

In fw4spl, data are located in fwData library whereas data binding classes are located in fwDataCamp library. The above line registering a binding class can be found in fwDataCamp autoload.hpp files.

Serialization file example

For more information about serialization see:

location brief
Srclib/core/fwData/include/ fwData header files with serialization macros
Srclib/core/fwDataCamp Serialization description of all fw4spl data
Srclib/core/fwDataCamp/include/fwDataCamp/autoload.hpp Auto loading data bindings in the system
fwData::Object to fwAtoms::Object conversion

The requirements to convert an fwData::Object into an fwAtoms::Object are in the fwAtomConversion library.

Two functions are necessary to achieve this conversion:

//Convert a fwData::Object into fwAtoms::Object
SPTR(::fwAtoms::Object) convert( const SPTR(::fwData::Object) &data );

//Convert a fwAtoms::Object into fwData::Object
SPTR(::fwData::Object) convert( const SPTR(::fwAtoms::Object) &atom );

Serialize an Atoms object to JSON format

When a fw4spl data is converted into Atoms, it can be saved in JSON format. Both an Atom reader and Atom writer are available in the fwAtomsBoostIO fw4spl library: simply instantiate one of these classes with an Atom object and call the read or write method.

To serialize atoms into JSON, a visitor pattern is used. An example can be found in the fwAtomsBoostIO/Reader.cpp file.

Conclusion

Accordingly, you have now the requirements to serialize data in the framework and a basic knowledge about the mechanism behind it. To conclude, this is a diagram of the serialization mechanism:

_images/serialization.png

Medical patient folder

DICOM is a software integration standard that is used in Medical Imaging. All modern medical imaging systems (aka Imaging Modalities) equipment like X-Rays, Ultrasounds, CT (Computed Tomography), and MRI (Magnetic Resonance Imaging) support DICOM and use it extensively. The core of DICOM is a file format and a networking protocol.

All Medical Images are saved in DICOM format. Medical Imaging Equipment creates DICOM files. Doctors use DICOM Viewers, computer software applications that can display DICOM images.

DICOM files contain more than just images. Every DICOM file holds patient information (name, ID, sex and birth date), important acquisition data (e.g., type of equipment used and its settings), and the context of the imaging study that is used to link the image to the medical treatment it was part of.

Roni Z. 2011. Introduction to DICOM [1]:

[1]Roni Z. 2011. Introduction to DICOM. Introduction. http://dicomiseasy.blogspot.fr/2011/10/introduction-to-dicom-chapter-1.html

The objects representing the medical patient data In FW4SPL are aligned with the DICOM standard. In the library fwMedData several structures and values have been retrieved:

  • Patient: name, primary hospital identification number, birth date and sex.
  • Study: unique identifier of the study, study date and time, referring physician, institution-generated description, age of the patient.
  • Equipment: institution where the equipment that produced the composite instances is located.
  • Series: unique identifier of the series, type of equipment that originally acquired the data used to create this series, series date and time, series description, name of the physician(s) administering the series.

In FW4SPL, the class Series is the main structure and contains pointers to Patient, Study and Equipment structure. The class SeriesDB is a container holding several instances of the Series class.

To specify an object of type Series, the library fwMedData holds the following classes inherited from Series:

  • ImageSeries which corresponds to the image series of DICOM (CT images, MRI images, etc).
  • ModelSeries which corresponds to the meshes series of DICOM and also represents 3D patient models.

The fwMedData library also provides a custom series called ActivitySeries. An ActivitySeries is a Series linked to an activity (sub part an application). Hence it is possible to save the state of all the objects used in the activity. Further application specific parameters which are not referred to an object can also be saved in an ActivitySeries. Application parameters in relation to the patient can be the view point on an organ, landmarks, calculated distances between organ points, etc.

Component-based software

The FW4SPL is also a component-based architecture.

Component-based software engineering (CBSE) (also known as component-based development (CBD)) is a branch of software engineering that emphasizes the separation of concerns in respect of the wide-ranging functionality available throughout a given software system. It is a reuse-based approach to defining, implementing and composing loosely coupled independent components into systems. This practice aims to bring about an equally wide-ranging degree of benefits in both the short-term and the long-term for the software itself and for organizations that sponsor such software. Excerpt from “Component-based software engineering” [1] on Wikipedia

[1]Component-based software engineering http://en.wikipedia.org/wiki/Component-based_software_engineering

Definitions and characteristics

An individual software component is a software package that encapsulates a set of related code: resources, objects, services, XML configuration, etc.

All the architecture is placed into separate components so that all of the data and functions inside each component are semantically related. Because of this principle, it is often said that components are modular and cohesive.

Components communicate with each other via interfaces. When a component offers services to the rest of the system, it adopts a provided interface which specifies services that other components can use. The generic architecture provided by classes Object/IService and the factory system make this interfacing easier.

Re-usability is an important characteristic of a high-quality software component. Programmers should design and implement software components in such a way that many different programs can reuse them.

Component-based implementation

Implementation requires a dynamic structure which represents the component and a software launcher which loads and manages these components. A component, called a bundle, is just a simple folder that contains :

  • the component description file (plugin.xml) to describe the content of the dynamic library
  • the dynamic library, the type of which (.so, .dll, .dylib) differs between operating systems
  • other shared resources (icons, XSD file, media files, ...)

The software launcher uses the library fwRuntime to parse the software description file (profile.xml) and load required dynamic libraries:

./fwlauncher.exe mySoftware/profile.xml

The component description file (plugin.xml) is used to describe the content of the dynamic library. This file reveals which concepts and concept implementations are proposed by the component. These terms are identified in the file by keywords:

  • Extension point: the concept
  • Extension: a concept implementation (there can be many implementations one of a single concept)

In some cases, the Extension point is represented by an abstract class in a component, and the Extension by the class that it inherits from the abstract class of another component.

One example is the service concept. The component description file of servicesReg introduces the concept of service and incorporates the class IService into the dynamic library:

<plugin id="serviceReg">

  <library name="servicesReg" />

  <extension-point id="::fwServices::registry::ServiceFactory" />

</plugin>

And in another component, a new service is proposed in the dynamic library and the information is shared in the description file.

<plugin id="myBundle">

   <library name ="myBundle" />

   <! -- myBundle requires the bundle servicesReg to run -->
   <requirement id="servicesReg" />

   <! -- Need code related to ::io::IReader -->
   <requirement id="io" />

   <extension implements =" ::fwServices::registry::ServiceFactory ">
       <! -- service type -->
       <type>::io::IReader</type>
       <! -- the service name available in this component library -->
       <service>::myBundle::myReader</service>
       <! -- the object type associated to the service -->
       <object>::fwData::myData</object>
       <desc>Description of my reader</desc>
   </extension>

</plugin>

Even if it is often the case, concepts are not limited to class level. A lot a concepts can be defined : service configurations, operator parameters, etc.

Manager and updater services

Concepts

In the FW4SPL architecture, there is an object container which is often used: ::fwData::Composite. This container is also an Object and represents a map which associates a string with an Object. The architecture provides two main services to manage a Composite: a composite updater and a service manager.

Updater

The updater service extends service type ::ctrlSelection::IUpdaterSrv and the work on a selection composite. This kind of service listens specific events from objects identified by their UID. When it receives an event, it performs an operation on an object in the selection composite and notifies composite listeners.

Available operations on composite are:

  • Adding an object
  • Swapping an object
  • Removing an object
  • Removing an object if present
  • Adding or swapping an object
  • Doing nothing

There are few generic updater services which listen all events sent by Objects, and few other which work with particular Object events.

Implementation

Updater

An updater implementation must inherit from the ::ctrlSelection::IUpdaterSrv service.

In the example below, an updater is used to manage a ::fwData::Reconstruction object identified with the reconstruction key in a selection composite. This ::fwData::Reconstruction is stored in a ::fwMedData::ModelSeries and we used a specific updater to listen signals and manage the structure.

The updater provides slots to react on object/service signals.

Example

For example, the updater ::ctrlSelection::SObjFromSlots provides the following slots :

  • add(object): add the given object in the composite with the configured key
  • swapObj(object): swap the given object in the composite with the configured key
  • addOrSwap(object): if the configured key exists in the composote, the object is swapped, else it is added
  • remove(): remove the object with the configured key from the composite
  • removeIfPresent(): remove the object if the configured key exists in the composite

Updater configuration example:

<object id="model" type="::fwMedData::ModelSeries">
    <service uid="listOrganEditor" type="::uiMedData::editor::SModelSeriesList" autoConnect="yes" />
</object>

<object type="::fwData::Composite">
    <service uid="myUpdater" type="::ctrlSelection::updater::SObjFromSlot" >
        <out key="object" uid="reconstruction" /> <!-- key of the updated object -->
    </service>
</object>

<!-- connect updater to listen the reconstruction selection -->
<connect>
    <signal>listOrganEditor/reconstructionSelected</signal>
    <slot>myUpdater/addOrSwap</slot>
</connect>

Graphical User Interface

Overview

Graphical User Interface (GUI) is the process of displaying the graphical components of an application. In fw4spl, the fwGui library provides abstract tools to display components like windows, buttons, textfield, aso.

The software architecture provides a way of selecting different backends in order to manage the GUI components. As a result, the fwGuiQt library has been created to display components created using the Qt soup. Presently, this backend is the only one supported by the applications.

Backend

When creating an application, we need to specify which gui backend we want to use. To do so, the chosen gui bundle must be activated and started in the profile.xml of the application. The main gui bundle for any application is guiQt. The gui bundle must be activated regardless of the chosen backend.

<activate id="gui" version="0-1" />
<activate id="guiQt" version="0-1" />

<!-- ... -->

<start id="guiQt" />

Warning : The gui backend bundle must be started before any other bundle in the profile.xml.

Configuration

Frames

The frame is the main component of a GUI. The main service used to represent a general frame is ::fwGui::IFrameSrv. The service ::gui::frame::DefaultFrame is the default implementation for the main application frame. Every backend must provide its own implementation of this service.

The DefaultFrame service is configurable with different parameters :

  • Application name
  • Application icon
  • Minimum window size
  • GUI elements (toolbar, menubar, aso.)
<service uid="mainFrame" type="::fwGui::IFrameSrv"
    impl="::gui::frame::DefaultFrame" autoConnect="no" >
    <gui>
        <frame>
            <name>Application name</name>
            <icon>path_to_application_icon</icon>
            <minSize width="800" height="600"/>
        </frame>
        <menuBar />
        <toolBar >
            <toolBitmapSize height= "32" width="32" />
        </toolBar>
    </gui>
    <registry>
        <menuBar sid="menuBar" start="yes" />
        <toolBar sid="toolBar" start="yes" />
        <view sid="view" start="yes" />
    </registry>
</service>
Layouts

The layouts are used to organize the different parts of a GUI. The main service used to manage layouts is ::fwGui::IGuiContainerSrv. The service ::gui::view::DefaultView is the default implementation. Every backend must provide its own implementation of this service.

Several types of layout can be used :

  • Line layout
  • Cardinal layout
  • Tab layout

Every layout can be configured with a set of parameters (orientation, alignment, aso.).

<service uid="subView" type="::gui::view::IView"
    impl="::gui::view::DefaultView" autoConnect="no" >
    <gui>
        <layout type="::fwGui::LineLayoutManager" >
            <orientation value="horizontal" />
            <view caption="view1" />
            <view caption="view2" />
        </layout>
    </gui>
    <registry>
        <view sid="subView1" start="yes" />
        <view sid="subView2" start="yes" />
    </registry>
</service>

Multi-threading

The fwGui library has been designed to support multi-thread application. When a GUI component needs to be accessed, the function call must be encapsulated in a lambda declaration as shown in this example:

::fwGui::registry::Worker::get()->postTask<void>(
[&] {
        //TODO Write function calls
}
).wait();

This encapsulation is required because all access to GUI components must be performed in the thread containing the GUI. It moves the function calls from the current thread, to the GUI thread.

Generic Scene

Overview

A generic scene in FW4SPL is visualization feature to visualize several elements like meshes or images in a scene. The scene is based on VTK. The main task of the generic scene is to manage all visualization services of the different elements contained in the scene. The generic scene is universal and therefore applicable for divers visualization tasks.

As used in FW4SPL, the scene generic configures a VTK scene with a simple xml configuration. Hence, FW4SPL is mainly used for medical assignments, the generic scene can be seen as fusion of a negatoscope and the 3D model visualization.

Components

Manager

The SRender is the manager service of the VTK scene. This service works on an object of type fwData::Composite that contains all objects to display.

The manager retrieves its specified container. The VTK context (vtkRender and vtkRenderWindow) is installed in the container of the manager.

The manager listens to the object signals of the associated fwData::Composite object. The transferred signals inform the manager if objects in the fwData::Composite have been added, removed or changed. In response to the modifications within the fwData::Composite object the manager supervises the starting and stopping of the visualization services (adaptors explained below), which are specified in its configuration. Thus, an object is added or removed to the fwData::Composite object, the corresponding adaptor which works on this object is started or stopped.

Adaptor

An adaptor (inherited from ::fwRenderVTK::IVtkAdaptorService) is a service to manipulate or display a FW4SPL data. Services representing an adaptor are managed by a generic scene (SRender). The adaptors are the gateway between FW4SPL objects and VTK objects. To respect the principles of the framework, adaptors are kept as generic as possible. Therefore they are reusable in further applications or even adaptors.

An adaptor is a specific service that need to implements the methods doStart, doStop, doUpdate, doConfigure` and doSwap instead of the usual starting, updating, ...

class MyAdaptor : public ::fwRenderVTK::IVtkAdaptorService
{

public:

    fwCoreServiceClassDefinitionsMacro ( (MyAdaptor)(::fwRenderVTK::IVtkAdaptorService) );

protected:

    /// Parse the adaptor "config" tag
    void configuring() throw(fwTools::Failed);

    /// Initialize the vtk pipeline (actor, mapper, ...)
    void doStart();

    /// Clear the vtk pipeline
    void doStop();

    /// Update the pipeline from the current object
    void doUpdate();

    /// Update the pipeline with the new object (eventually call doStop();doStart();)
    void doSwap();
};

Configuration

<service uid="generiSceneUID" impl="::fwRenderVTK::SRender" type="::fwRender::IRender">
    <scene renderMode="auto|timer|none" offScreen="imageKey" width="1920" height="1080">
        <renderer id="myRenderer" layer="0" background="0.0" />
        <vtkObject id="transform" class="vtkTransform" />
        <picker id="negatodefault" vtkclass="fwVtkCellPicker" />

        <adaptor id="tmAdaptor" class="::visuVTKAdaptor::Transform" uid="adaptorUID" objectId="tm3dKey">
            <config transform="transform" />
        </adaptor>
        <adaptor id="snapshot" class="::visuVTKAdaptor::Snapshot" objectId="self">
            <config renderer="myRenderer" />
        </adaptor>

        <!-- ...... -->

        <connect>
            <signal>adaptorUID/modified</signal>
            <slot>serviceUid/updateTM</slot>
        </connect>

        <connect waitForKey="tm3dKey">
            <signal>modified</signal><!-- signal for object "tm3dKey" -->
            <slot>serviceUid/updateTM</slot>
        </connect>

        <proxy channel="myChannel">
            <signal>adaptor2UID/modified</signal>
            <slot>service2Uid/updateTM</slot>
        </proxy>
    </scene>
    <fps>30</fps><!-- used if renderMode=="timer" -->
</service>
renderMode (optional, “auto” by default)
This attribute is forwarded to all adaptors. For each adaptor, if renderMode=”auto”, the scene is automatically rendered after doStart, doUpdate, doSwap, doStop and m_vtkPipelineModified=true. If renderMode=”timer” the scene is rendered at N frame per seconds (N is defined by fps tag). If renderMode=”none” you should call ‘render’ slot to call reder the scene.
offScreen (optional):
Key of the image used for off screen render
width (optional, “1280” by default):
Width for off screen render
height (optional, “720” by default):
Height for off screen render
renderer

Defines a renderer. At least one renderer is mandatory, but there can be multiple renderer on different layers.

  • id (mandatory): the identifier of the renderer
  • layer (optional): defines the layer of the vtkRenderer. This is only used if there are layered renderers.
  • background (optional): the background color of the rendering screen.

The color value can be defines as a grey level value (ex . 1.0 for white) or as a hexadecimal value (ex : #ffffff for white).

vtkObject
Represents a vtk object. It is usually used for vtkTransform or vtkImageBlend.
  • id (mandatory): the identifier of the vtkObject
  • class (mandatory): the classname of the vtkObject to create. For example vtkTransform, vtkImageBlend, ...
picker

Represents a picker.

  • id (mandatory): the identifier of the picker
  • vtkclass (optional, by default vtkCellPicker): the classname of the picker to create.
adaptor
Defines the adaptors to display in the scene.
  • id (mandatory): the identifier of the adaptor
  • class (mandatory): the classname of the adaptor service
  • uid (optional): the fwID to specify for the adaptor service
  • objectId (mandatory): the key of the adaptor’s object in the scene’s composite.
  • autoConnect (optional, “yes” by default): if “yes” the service slot are automatically connected to the object signals.
  • config: adaptor’s configuration. It is parsed in the adaptor’s configuring() method.

Note

The “self” key is used when the adaptor works on the scene’s composite.

connect/proxy (optional)
Connects signal to slot
  • waitForKey (optional): defines that the connection is made only if the key is present in the scene composite.
  • signal (mandatory): must be signal holder UID, followed by ‘/’, followed by signal name.
  • slot (mandatory): must be slot holder UID, followed by ‘/’, followed by slot name.

Note

To use the signal of the object (defined by waitForKey), you don’t have to write object uid, only the signal name.

Data file migration

Overview

The data migration system consists on converting the data to another version. It allows us to adapt any version of data to any version of software, and thus ensuring compatibility between data and software independently of their version.

Migration process is applied on two independent steps :

  • In ::ioAtoms::SReader while reading data files, previously serialized with fwAtoms, right before converting said data to ::fwData::Objects.
  • In ::ioAtoms::SWriter after data is converted to fwAtoms::Base.

Definitions

Context
It represents a complex chunk of data. For example, the medical patient folder, the software preference file, etc. Hereafter we will consider a medical patient folder which is called MedicalData.
Structural patch
This sort of patch affects only one object of the serialized data regardless of the context (ex: add or remove attribute, type, ...), see Structural patch.
Semantic patch
This sort of patch is applied on a context to migrate to a given version without changing the data structure. (These patches are sometimes called contextual patches), see Semantic patch.
Patcher
A patcher defines the methods to parse the data and applies the structural and semantic patches, see Patcher.

Data Version

After the conversion from ::fwData::Object to ::fwAtoms::Object, each data is assigned a version number. Said number is defined in the camp serialization source files (see Serialization). Each data structure modification causes an incrementation of the data version.

Example of data declaration for introspection (used to convert to fwAtoms):

#include <fwCamp/UserObject.hpp>

fwCampImplementDataMacro((fwData)(ComplexClass))
{
    builder
        .tag("object_version", "1") // data version
        .tag("lib_name", "fwData")
        .base< ::fwData::Object>()
        .property("myString" , &::fwData::ComplexClass::m_myString)
        .property("myFloat" , &::fwData::ComplexClass::m_myFloat)
        .property("mySimpleClass" , &::fwData::ComplexClass::m_mySimpleClass)
        ;
}

Context version

The context version must be incremented after a data version modification.

Note

  • If several data versions are modified simultaneously, only one incrementation of the context version is necessary.
  • A single context version can contain data with different versions (see the example below).

The .versions file contains a detailed description of the context version, and the version of each data.

Example of V1.versions:

{
    "context": "MedicalData",
    "version_name": "V1",
    "versions":
    {
        "::fwData::Array": "1",
        "::fwData::Boolean": "1",
        "::fwData::Image": "1",
        "::fwData::Integer": "1",
        "::fwData::Material": "1",
        "::fwData::Mesh": "1",
        "::fwData::Patient": "1",
    }
}

Example of V2.versions:

{
    "context": "MedicalData",
    "version_name": "V2",
    "versions":
    {
        "::fwData::Array": "1",
        "::fwData::Boolean": "1",
        "::fwData::Image": "2",
        "::fwData::Integer": "1",
        "::fwData::Material": "1",
        "::fwData::Mesh": "1",
        "::fwMedData::Patient": "1",
    }
}

Migration

The migration is applied on a given context. It is described in the .graphlink file. It defines how to migrate from a context version to another.

Example of V1ToV2.graphlink:

{
    "context" : "MedicalData",
    "origin_version" : "V1",
    "target_version" : "V2",
    "patcher" : "DefaultPatcher",
    "links" : [
        {
            "::fwData::Patient" : "1",
            "::fwMedData::Patient" : "1"
        },
        {
            "::fwData::Image" : "1",
            "::fwData::Image" : "2"
        }
    ]
}

The links tag represents the data version modifications, by doing so, associated patches can be applied.

Warning

Two .versions files must be defined, one for each version (V1.versions and V2.versions).

Note

It is not necessary to specify a simple data version incrementation on the links tag, the patching system establishes this information from the data version defined in the .versions files.

Graph

The .graphlink and .versions files are parsed and the information is stored in the ::fwAtoms::VersionsManager. Each context defines a graph.

Example of graph:

_images/patchGraph.png

The graph is used to find the migration path from an initial version to a target version. In our example, it is possible to migrate from V1 to V5, the data is converted to V3, V4 then V5. If several paths are possible, the shortest path is used.

Structure

The fwAtomsPatch library contains the base classes to perform the migration.

_images/patch_structure.png
PatchingManager
This class provides the transformTo() method used to migrate the data. It uses the graph to apply the patcher on each version.
patcher::IPatcher
Base class for patchers.
patcher::DefaultPatcher
Patcher used by default. It performs the data migration in two steps: first it applies the structural patches recursivly on each sub-object and then applies the semantic patches recursivly on each sub-object .
IPatch
Base class for structural and semantic patches. It provides an apply() method that must be implemented in sub-classes.
ISemanticPatch
Base class for semantic patches.
IStructuralPatch
Base class for structural patches.
IStructuralCreator
Base class for creators. It provides a create() method that must be implemented in sub-classes.
SemanticPatchDB
Singleton used to register all the semantic patches.
StructuralPatchDB
Singleton used to register all the structural patches.
CreatorPatchDB
Singleton used to register all the creator patches.
VersionsGraph
Registers the migration graphs.
VersionsManager
Singleton used to register all the version graph.

The fwStructuralPatch library contains the structural patches for fwData and fwMedData conversion.

The fwMDSemanticPatch library contains the semantic patches for fwData and fwMedData conversion in the MedicalData context.

The patchMedicalData bundle must be activated in your application to allow migration in MedicalData context.

Structural patch

The structural patches are registered in the ::fwAtomsPatch::StructuralPatchDB singleton. A structural patch provides a method apply that performs the structure conversion. The constructor defines the classname and versions of the origin and target objects as described in the .graphlink links section.

Example of structural patch to convert the fwData::Image from version 1 to 2. We add three attributes related to medical imaging: the number of components nb_components, the window center window_center and the window width window_width.

#include "fwStructuralPatch/fwData/Image/V1ToV2.hpp"

#include <fwAtoms/Numeric.hpp>
#include <fwAtoms/Numeric.hxx>

namespace fwStructuralPatch
{

namespace fwData
{

namespace Image
{

V1ToV2::V1ToV2() : ::fwAtomsPatch::IStructuralPatch()
{
    m_originClassname = "::fwData::Image";
    m_targetClassname = "::fwData::Image";
    m_originVersion   = "1";
    m_targetVersion   = "2";

}

// ----------------------------------------------------------------------------

void V1ToV2::apply(
    const ::fwAtoms::Object::sptr& previous, // object in the origin version
    const ::fwAtoms::Object::sptr& current, // clone of the previous object to convert in the targer version
    ::fwAtomsPatch::IPatch::NewVersionsType& newVersions) // map < previous object, new object > association
{
    // Check if the previous and current object version and classname correspond
    IStructuralPatch::apply(previous, current, newVersions);

    // Update object version
    this->updateVersion(current);

    // Create helper
    ::fwAtomsPatch::helper::Object helper(current);

    helper.addAttribute("nb_components", ::fwAtoms::Numeric::New(1));
    helper.addAttribute("window_center", ::fwAtoms::Numeric::New(50));
    helper.addAttribute("window_width", ::fwAtoms::Numeric::New(500));
}

} // namespace Image

} // namespace fwData

} // namespace fwStructuralPatch

To register the structural patch:

// fwStructuralPatch/autoload.cpp

::fwAtomsPatch::StructuralPatchDB::sptr structuralPatches = ::fwAtomsPatch::StructuralPatchDB::getDefault();
structuralPatches->registerPatch(::fwStructuralPatch::fwData::Image::V1ToV2::New());
Creator

The creator provides a method create that allows to create a new object with the default attribute initialization. The creator is used in structural patches to create new sub-objects. Creators are registered in the ::fwAtomsPatch::StructuralCreatorDB singleton.

Creators are useful for adding an attribute that is a non-null object.

Example of creator for the ::fwMedData::Patient :

#include "fwStructuralPatch/creator/fwMedData/Patient1.hpp"

#include <fwAtoms/String.hpp>

#include <fwAtomsPatch/helper/Object.hpp>

namespace fwStructuralPatch
{
namespace creator
{
namespace fwMedData
{

Patient1::Patient1()
{
    m_classname = "::fwMedData::Patient";
    m_version   = "1";
}

// ----------------------------------------------------------------------------

::fwAtoms::Object::sptr Patient1::create()
{
    // Create an empty ::fwAtoms::Object with the classname, version and ID informtation
    ::fwAtoms::Object::sptr patient = this->createObjBase();

    ::fwAtomsPatch::helper::Object helper(patient);

    helper.addAttribute("name", ::fwAtoms::String::New(""));
    helper.addAttribute("patient_id", ::fwAtoms::String::New(""));
    helper.addAttribute("birth_date", ::fwAtoms::String::New(""));
    helper.addAttribute("sex", ::fwAtoms::String::New(""));

    return patient;
}

} // namespace fwMedData
} // namespace creator
} // namespace fwStructuralPatch

To register the creator:

// fwStructuralPatch/creator/autoload.cpp

::fwAtomsPatch::StructuralCreatorDB::sptr creators = ::fwAtomsPatch::StructuralCreatorDB::getDefault();
creators->registerCreator(::fwStructuralPatch::creator::fwMedData::Equipment1::New());
Semantic patch

The semantic patches are registered in the ::fwAtomsPatch::SemanticPatchDB singleton. The structural patch provides a method apply that performs the structure conversion. The constructor defines the origin classname, the origin version of the object, and the origin and the target context version as described in the .graphlink.

The semantic patch is used when we need several objects to perform the object migration.

Example of semantic patch :

#include "fwMDSemanticPatch/V2/V3/fwData/Image.hpp"

#include <fwAtoms/Object.hpp>
#include <fwAtoms/Object.hxx>
#include <fwAtoms/Numeric.hpp>
#include <fwAtoms/Numeric.hxx>

#include <fwAtomsPatch/helper/functions.hpp>


namespace fwMDSemanticPatch
{
namespace V2
{
namespace V3
{
namespace fwData
{

Image::Image() : ::fwAtomsPatch::ISemanticPatch()
{
    m_originClassname = "::fwData::Image";
    m_originVersion   = "1";
    this->addContext("MedicalData", "V2", "V3"); // Context version
}

// ----------------------------------------------------------------------------

void Image::apply(
    const ::fwAtoms::Object::sptr& previous, // object in the origin version
    const ::fwAtoms::Object::sptr& current, // clone of the previous object to convert in the targer version
    ::fwAtomsPatch::IPatch::NewVersionsType& newVersions) // map < previous object, new object > association
{
    // Check if the previous and current object version and classname correspond
    ISemanticPatch::apply(previous, current, newVersions);

    // Cleans object fields (also creates them if they are missing)
    ::fwAtomsPatch::helper::cleanFields( current );

    ::fwAtomsPatch::helper::Object helper( current );

    ::fwAtoms::Object::sptr array        = ::fwAtoms::Object::dynamicCast(previous->getAttribute("array"));
    ::fwAtoms::Numeric::sptr nbComponent =
             ::fwAtoms::Numeric::dynamicCast(array->getAttribute("nb_of_components"));

    helper.replaceAttribute("nb_components", nbComponent->clone());
}

// ----------------------------------------------------------------------------

} // namespace fwData
} // namespace V3
} // namespace V2
} // namespace fwMDSemanticPatch

This patch changed the attribute nb_components in the image copied from array nb_of_components.

To register the semantic patch:

// fwMDSemanticPatch/V1/V2/fwData/autoload.cpp
::fwAtomsPatch::SemanticPatchDB::sptr contextPatchDB = ::fwAtomsPatch::SemanticPatchDB::getDefault();
contextPatchDB->registerPatch(::fwMDSemanticPatch::V1::V2::fwData::Composite::New());
Patcher

The patcher defines the methods to parse the data and applies the structural and semantic patches. It must inherit from fwAtomsPatch::patcher::IPatcher and implements the transformObject() method.

We usually use the DefaultPatcher. The conversion is processed in two steps: first it applies the structural patches recursivly on each sub-objects, then it applies the semantic patches recursively on each sub-objects.

Rules
Rule 1
A change in data (fwData, fwMedData, ...) involves the incrementation of the data version and the context version and thus, the creation of structural and/or semantic patch.
Rule 2
The creator patch creates the fwAtoms::Object representing the data object. The ::fwAtoms::Object created must be the same as the data created with a New() and converted to fwAtoms.
Rule 3
The buffer object (converted as BLOB in fwAtoms) is just reused (without copy) during the migration. If its structure is modified, you should clone the buffer before applying the patch.
Rule 4
If an object is contained in the fwAtoms::Object to migrate but is not present in the current context version (in the .versions file), this object will be erased from the fwAtoms::Object.

Usage

If you have to modify data, you don’t have to re-implement all the migration system, but there are steps to perform :

step 1
Increment the data version in camp declaration (and update the declaration of the attribute if needed). See Data Version.
step 2
Increment the context version: create new .versions files (with the associated data version). See Context version.
step 3
Create the .graphlink file. See graphlink.
step 4 (optional)
Create the creator if you need to add a new non-null objet. See Creator.
step 5
Create the structural patch. See Structural patch.
step 6 (optional)
Create the semantic patch if you need other objects to update the current one. See Semantic patch.

Note

You can create migration patches from V1 to V3 without using the V1 to V2 and V2 to V3.

Coding style

Terminology

  • Rules are mandatory. Any rule can be (exceptionally) exceeded, but if so, it has to be rigorously justified.

  • Recommendations are optional.

  • Camel case is the practice of writing compound words or phrases such that each word or abbreviation begins with a capital letter. In programming languages, camel case is assumed to start with a lowercase letter. We will use the term upper camel case when it starts with a capital.

    camelCaseLabel
    UpperCamelCaseLabel
    

Generalities

Rule 44 : Preferred language
English is the preferred language for types, variables, functions naming, and code comments.
Rule 45 : Maximum size of a line
A source code line must not exceed 120 characters.
Rule 46 : Indentation
Use only spaces, and an indent level has four spaces.

C++ coding

Source and files

Rule 4 : Files tree
Source files must be placed in a folder src/. Public header files must be placed in a folder include/. Private headers may be placed in a different location.
Rule 5 : Files hierarchy
The file hierarchy should follow the namespace hierarchy. For instance, the implementation of a class ::ns1::ns2::SService should be put in src/ns1/ns2/SService.cpp.
Rule 6 : Files extensions

Header files use the extension .hpp.

Implementation files use the extension .cpp.

Files containing implementation of “template” classes use the extension .hxx.

Recommendation 2 : Only one class per file
It is recommended to declare (or to implement) only one class per file. However tiny classes may be declared inside the same file.
Rule 7 : Includes
Use the right include directive depending on the context. #include "..." must be used to import headers from the same module, whereas #include <...> must be used to import headers from other modules.
Rule 8 : Include path
The include path is not an absolute path depending on a local file system. A correct include path does respect the letter case of the filenames and folders (since some platforms require it) and uses the character ‘/’ as a separator.
Rule 9 : Protection against multiple inclusions

You must protect your files against multiple inclusions. To this end, use the standard directives of the precompiler #ifndef and #define (since #pragma once is only supported by Microsoft compilers).

Use the name of the file and the namespace hierarchy inside the define name in order to prevent any conflict with a file which has the same name but located in a different namespace. Namespaces and file name must be separated by a single underscore _. The define name must be prefixed and suffixed by two underscores __. Last, a comment must be placed after #endif to quote the define.

#ifndef __NAMESPACEA_NAMESPACEB_SAMPLE_HPP__ // Preamble protecting against
#define __NAMESPACEA_NAMESPACEB_SAMPLE_HPP__ // multiple inclusions.

#endif // __NAMESPACEA_NAMESPACEB_SAMPLE_HPP__
Recommendation 3 : Independent headers

A header should compile alone. All necessary includes should be contained inside the header itself. In the following sample :

// Header.hpp

class Foo
{
public:
    std::string m_string;
}

you will be forced to include the file in this way to get a successful build :

// Source.hpp

#include <string>
#include "Header.hpp"

This is a bad practice, the header should rather be written :

// Header.hpp

#include <string>

// Header.hpp
class Foo
{
public:
    std::string m_string;
}

So that people can simply include the header :

// Source.hpp

#include "Header.hpp"
Recommendation 4 : Minimize inclusions
Try to minimize as much as possible inclusions inside a header file. Include only what you use. Use forward declarations when you can (i.e. a type or class structure is not referenced inside the header). This will limit dependency between files and reduce compile time. Hiding the implementation can also help to minimize inclusions (see Hide implementation)
Rule 10 : Sort headers inclusions

You must sort headers in the following order : same module, framework libraries, bundles, external libraries, standard library. This way, this helps to make each header independent. The rule can be broken if a different include order is necessary to get a successful build.

#include "currentModule.hpp"

#include <libSampleB/second.hpp>
#include <libSampleA/first.hpp>
#include <libSampleB/subModule/first.hpp>

#include <Qt/QtGui>
#include <vector>
#include <map>
Recommendation 5 : Sort inclusions alphanumerically

In addition to the previous sort, you may sort includes in alphanumerical order, according to the whole path. Thus they will be grouped by module. For a better readability, an empty line can be added between each module.

#include "currentModule.hpp"

#include <libSampleA/first.hpp>
#include <libSampleB/second.hpp>

#include <libSampleB/subModule/first.hpp>
#include <libSampleB/subModule/second.hpp>

#include <Qt/QtGui>

#include <map>
#include <vector>

Naming conventions

Rule 11 : Class
Class names must be written in upper camel case. It should not repeat a namespace name. For instance ::editor::SCustomEditor should be rather called ::editor::SCustom.
Rule 12 : File
The name of the file should be based on the class name defined in it. It must follow the same letter case.
Rule 13 : Namespace

Namespaces must be written in camel case. A comment quoting the namespace must be placed next to the ending ‘}’.

namespace namespaceA
{
namespace namespaceB
{
    class Sample
    {
    ...
    };
} // namespace namespaceB
} // namespace namespaceA

When referring a namespace, you must put :: if this is a root namespace, with an exception for std namespace. Ex: ::boost::filesystem.

Rule 14 : Function and method names
Functions and methods names must be written in camel case.
Recommendation 6 : Correct naming of functions
Try as much as possible to help the users of your code by using comprehensive names. You may for instance help them to indicate the cost of a function. A function that executes a search to retrieve an object must not be called like a getter. In this case, it is better to call it findObjet() instead of getObject().
Rule 15 : Variable

Variable names must be written in camel case. Members of a class are prefixed with a m_.

class SampleClass
{
private:
   int m_identifier;
   float m_value;
};

Static variables are prefixed with a s_.

static int s_staticVar;
Rule 16 : Constant

Constant variables must be written in snake_case but in capitals, and follow the previous rule.

class SampleClass
{
    static const int s_AAA_BBB_CCC_VALUE = 1;
};

void fooFunction()
{
    const int AAA_BBB_VAR = 1;
    ...
}
Rule 17 : Type

Type names, like classes, must be written in upper camel case.

typedef int CustomType;
typedef vector<int> CustomContainer;
Rule 18 : Template parameter

Template parameters must be written in capitals. In addition, they must be short and explicit.

template< class KEY, class VALUE > class SampleClass
{
    ...
};
Rule 19 : Macro

Macros without parameters must be written in capitals. On the contrary, there is no specific rule on macros with parameters.

#define CUSTOM_FLAG_A 1
#define CUSTOM_FLAG_B 1

#define CUSTOM_MACRO_A( x ) x
#define Custom_Macro_B( x ) x
#define custom_Macro_C( x ) x
#define custom_macro_d( x ) x
Rule 20 : Enumerated type

An enumerated type name must be written in upper camel case. Labels must be written in capitals. If a typedef is defined, it follows the upper camel case standard.

typedef enum SampleEnum
{
    LABEL_1,
    LABEL_2
    ...
} SampleEnumType;
Rule 21 : Service
A service implementation is identified by a S at the beginning of the class name. Example : SCustomEditor. A service interface is identified by a I at the beginning of the class name. Example : IEditor.
Rule 22 : Signal

A signal name must be prefixed with sig. It should be suffixed by a past action (ex: Updated, Triggered, Cancelled, CakeCookedAndBaked). It follows other common variable naming rules (member of a class, etc...).

class Sample
{
    SigType::sptr m_sigImageDisplayed;
};
Rule 23 : Slot

A slot name must be prefixed with slot. It should be suffixed by an imperative order (Ex: Update, Run, Detach, Deliver, OpenWebBrowser, GoToFail). It follows other common variable naming rules (member of a class, etc...).

class Sample
{
    SlotType::sptr m_slotDisplayImage;
}

Coding rules

Blocks
Rule 24 : Indentation

Code block indentation and bracket positioning follow the Allman style.

void function(void)
{
    if(x == y)
    {
        something1();
        something2();
    }
    else
    {
        somethingElse1();
        somethingElse2();
    }
    finalThing();
}
Rule 25 : Indentation of namespaces

Namespaces are an exception of the previous rule. They should not be indented.

namespace namespaceA
{
namespace namespaceB
{
    ...
} // namespace namespaceB
} // namespace namespaceA
Rule 26 : Blocks are mandatory
After a control statement (if, else, for, while/do...while, try/catch, switch, foreach, etc...), it is mandatory to open a block, whatever is the number of instructions inside the block.
Rule 27 : Scope

The keywords public, protected and private are not indented, they should be aligned with the keyword class.

class Sample
{
public:
    ...
private:
    ...
};
Class declaration
Recommendation 7 : Only three scope sections
When possible, use only one section of each scope type public, protected and private. They must be declared in this order.
Recommendation 8 : Group class members by type
You may group class members in each scope according to their type: type definitions, constructors, destructor, operators, variables, functions.
Rule 28 : Hide implementation
Avoid non-const public member variables except in very small classes (i.e. a 3D point). The Pimpl idiom may also be helpful to separate the implementation from the declaration.
Recommendation 9 : Hide implementation
Try to put variables as much as possible in the private section.
Rule 29 : Accessors
Since you protect your member variables from the outside, you will have to write accessors, named getXXX() and setXXX(). Getters are always const.
Rule 30 : Template class function definition

The function definition of a template class must be defined after the declaration of the class.

template < typename TYPE >
class Sample
{
public:
    void function(int i);
};

template < typename TYPE >
inline Sample<TYPE>::function(int i)
{
    ...
}
Recommendation 10 : Separate template class function definition

In addition of the previous rule, you may put the definition of the function in a .hxx file. This file will be included in the implementation file right after the header file (the compile time will be reduced comparing with an inclusion of the .hxx in the header file itself).

#include <namespaceA/file.hpp>
#include <namespaceA/file.hxx>
Initializer list
Rule 31 : One initializer per line

In a class constructor, use the initialization list as much as possible. Place one initializer per line. Constructors of base classes should be placed first, followed by member variables. Do not specify an initializer if it is the default one (empty std::string for instance).

SampleClass::SampleClass( const std::string& name, const int value ) :
    BaseClassOne( name ),
    BaseClassTwo( name ),
    m_value( value ),
    m_misc( 10 )
{}
Recommendation 11 : Align everything that improves readability

To improve readability, you may align members on one hand and argument lists on the other hand.

SampleClass::SampleClass( const std::string& name, const int value ) :
    BaseClassOne  ( name ),
    BaseClassTwo  ( name ),
    m_value       ( value ),
    m_misc        ( 10 )
{}
Functions
Rule 32 : Constant reference

Whenever possible, use constant references to pass arguments of non-primitive types. This avoids useless and expensive copies.

void badFunction( std::vector<int> array )
{
    ...
}

void goodFunction( const std::vector<int>& array )
{
    ...
}
Recommendation 12 : Constant reference for shared pointers
For performance sake, it is preferable to use const& to pass arguments of type ::boost::shared_ptr. It is only useful to pass the pointer by copy if the pointer can be invalidated by an another thread during the function call. If you have any doubt, it is safer to pass the argument by copy.
Rule 33 : Constant functions

Whenever a member function should not modify an attribute of a class, it must be declared as const.

void readOnlyFunction( const std::vector<int>& array ) const
{
    ...
}
Recommendation 13 : Limit use of expression in arguments

When passing arguments, try to limit the use of expressions to the minimum.

// This is bad
function( fn1(val1 + val2 / 4 ), fn2( fn3( val3 ), val4) );

// This is better
const float res0 = val1 + val2 / 4;

const float res1 = fn1(res0);
const float res3 = fn3(val3);
const float res2 = fn2(res3, val4);

function( res1 , res2 );
Miscellaneous
Rule 34 : Enumerator labels

Each label must be placed on a single line, followed by a comma. If you assign values to labels, align values on the same column.

enum OpenFlag
{
    OPEN_SHARE_READ      = 1,
    OPEN_SHARE_WRITE     = 2,
    OPEN_EXISTING        = 4,
};
Rule 35 : Use of namespaces

You have to organize your code inside namespaces. By default, you will have at least one namespace for your module (application or bundle). Inside this namespace, it is recommended to split your code into sub-namespaces. This helps notably to prevent naming conflicts.

It is forbidden to use the expression``using namespace`` in header files but it is allowed in implementation files. It is however recommended to use aliases in this latter case.

namespace bf = ::boost::filesystem;
Rule 36 : Keyword const
Use this keyword as much as possible for variables, parameters and functions.
Recommendation 14 : Keyword auto
Use this keyword as much as possible to improve maintainability and robustness of the code.
Rule 37 : Prefer constants instead of #define
Use a static constant object or an enumeration instead of a #define. This will help the compiler to make type checking. You will also be able to check the content of the constants while debugging. You can also define a scope for them, inside the namespace, inside a class, private to a class, etc...
Rule 38 : Prefer references over pointers
When possible, use references instead of pointers, especially for function parameters. Pointer as parameter should only be used if it is considered to have a NULL pointer or when passing a C-like array. If you use a pointer, always check it if is null in the current scope before dereferencing it.
Rule 39 : Type conversion
For type conversion, use the C++ operators which are static_cast, dynamic_cast, const_cast and reinterpret_cast. Use them wisely in the appropriate case. You may read this documentation.
Recommendation 15 : Strings to numbers/numbers to string conversion
When converting strings to numbers or numbers to string, prefer the use of boost::lexical_cast.
Recommendation 16 : Exceptions
Exceptions are the preferred mechanism to handle error notifications.
Rule 40 : Explicit integer types

When you do need a specific integer size, use type definitions declared in <cstdint>, for example :

Bits Signed Unsigned
8 int8_t uint8_t
16 int16_t uint16_t
32 int32_t uint32_t
64 int64_t uint64_t

Documentation

Rule 41 : Document the code
The code must be documented with Doxygen, an automated tool to generate documentation.
Rule 42 : Location of the documentation

Every documentation that can be useful to a user must be placed inside the header files. Thus a user of a module can find the declaration of a class and its documentation at the same place. Inside the implementation file, the documentation will give more details about algorithms.

Moreover, every documentation must be placed next to the entity it is refering to, in order to help searching inside the code.

Recommendation 17 : Lightweight documentation

Inside a documentation block, only use necessary tags. This will avoid to overload the documentation and makes it readable. By the way, empty tags will be presented inside the generated documentation and will be useless. Just use an empty line to make a separation inside a documentation block.

Don’t indicate parameter types when using @param directive. This is useless since it will duplicate information of the function prototype. Also, prefer the use of /// whenever possible.

Example 1 : Bad documentation block

/**
* @brief        A very short description.
*
* A longer description, giving more details about the documented piece
* of code.
*********************************************
* @param
*********************************************
* @return
*********************************************
* @exception
*********************************************
* @todo
*********************************************

Example 2 : Good documentation block

/**
* @brief        A very short description.
*
* A longer description, giving more details about the documented piece
* of code.
*/

Example 3 : Function documentation

class Sample
{
public:
    /**
    * Retrieve the thing.
    *
    * @return       The thing value.
    */
    const std::string& getThing( void ) const;
    /**
    * @brief        Set the thing.
    *
    * @param        thing   :  The new thing.
    */
    void setThing( const std::string& thing );

private:
    /// stored thing
    std::string     m_thing;
};
Recommendation 18 : Structured documentation
Doxygen provides a default structure when you generate the documentation. However, when dealing with a big documented entity, it is often recommended to use the group feature (@name). With this feature you will build a logical view of the class interfaces.
Rule 43 : Document service

The service must be properly documented.

This should include first a brief description, then a long description if necessary.

/**
 * @brief This is the short description.
 *
 * This is the long description.
 *

After that the signals and slots must be documented in two distinct sections.

*
* @section Signals Signals
* - \b signal2(::fwData::Mesh::sptr) : Emitted when the mesh has changed.
* - \b signal1(std::int64_t) : Emitted when ...
*
* @section Slots Slots
* - \b modified() : Modify the data.
*

Last the xml configuration of the service must be described into a dedicated section. It should indicate first the input, input/outputs and outputs in three subsections. The type and the name of the data should appear along with a short description. A fourth subsection describes the rest of the parameters, and tells if it they are optional or not.

*
* @section XML XML Configuration
*
* @code{.xml}
       <service type="::namespace::SService">
           <in key="data1" uid="model" />
           <inout key="data2" uid="mesh" />
           <out key="data3" uid="image2" />
           <out key="data4" uid="image1" />
           <option1>12</option1>
           <option2>12</option2>
       </service>
  @endcode
* @subsection Input Input
* - \b data1 [::fwMedData::ModelSeries]: blablabla.
* @subsection In-Out In-Out
* - \b data2 [::fwData::Mesh]: blablabla.
* @subsection Output Output
* - \b data3 [::fwData::Image]: blablabla.
* - \b data4 [::fwData::Image]: blablabla.
* @subsection Configuration Configuration
* - \b option1 : first option.
* - \b option2(optional) : second option.
*
*/

Please follow the template above as much as possible to keep the documentation as clear and homogeneous as possible.

XML coding

Rule 48 : Id name

Id should have a semantic name. Avoid id like myXXXXX or customXXXXX. Moreover, id must be written in lower case with an underscore as separator.

<service id="generic_scene" />

CMakeLists coding

Rule 1 : Function name

Standard CMake functions and macros should be written in lower case. Each word is generally separated by an underscore (this is a rule of CMake anyway).

add_subdirectory("library/")
include_directories(SYSTEM "/usr/local")
Rule 2 : Macro name

Custom macros should be written in camel case.

fwLoadProperties()
fwLink("boost")
Rule 3 : Variable name

Variables should be written in upper case letters separated if needed by underscores.

set(VARIABLE_NAME "")
Recommendation 1 : Expression in block ending

In the past, CMake enforced to specify the label or expression in block ending, for instance :

function(name arg1 arg2)
    ...
    if(expr1)
        ...
    else(expr1)
        ...
    endif(expr1)
    ...
endfunction(name)

This is no longer needed in latest CMake versions, and we recommend to use this possibility for the sake of simplicity.

function(name arg1 arg2)
    ...
    if(expr1)
        ...
    else()
        ...
    endif()
    ...
endfunction()

Licence

Rule 47 : LGPL

Do not forget to put the LGPL licence block on fw4spl.

/* ***** BEGIN LICENSE BLOCK *****
 * FW4SPL - Copyright (C) IRCAD, 2009-2015.
 * Distributed under the terms of the GNU Lesser General Public License (LGPL) as
 * published by the Free Software Foundation.
 * ***** END LICENSE BLOCK ***** */

Frequently Asked Questions (FAQ)

What is fw4spl?

The framework FW4SPL (FrameWork for Software Production) is an open-source framework, developed by IRCAD (research institute against cancer and disease). The purpose of FW4SPL is to ease the creation of applications in the medical imaging field. Therefore it provides features like digital image processing in 2D and 3D, visualization or simulation of medical interactions.

Building an application with FW4SPL only requires to write one or several XML files. Its functionalities can be also extended by writing new components in C++, which is the coding language of the framework.

What does fw4spl mean?

FW4SPL means FrameWork for Software Production Line. It is also called F4S (“forces”).

What are the features of fw4spl?

FW4SPL is based on a component architecture composed of C++ libraries. The three main concepts of the architecture, explained in the following sections, are:

  • object-service concept
  • component approach
  • signal-slot communication

The framework is multi-platform and runs under Windows, Linux, MacOS and Android. Building an application with FW4SPL only requires to write one or several XML files. Its functionalities can be also extended by writing new components in C++, which is the coding language of the framework.

Which platforms does fw4spl run on?

This framework can run under Windows, Linux and MacOS and we are working on the Android part.

Where can I find applications developed with fw4spl ?

Some tutorials are provided with the framework and you can also build VRRender, a free visualization software. With fw4spl-ar repository, you can also get ARCalibration, a user-friendly camera calibration tool.

Which prerequistes do I need to develop new services and bundles ?

You must have a good knowledge in C++. Concerning the configuration files, they are written in XML.

What are the BinPkgs?

The BinPkgs (binary packages) contain all the extern libraries needed by fw4spl. For each BinPkg, a CMakeLists provides the OS specific instructions to build it . They can be downloaded on https://github.com/fw4spl-org/fw4spl-deps.

Is it difficult to compile an application with fw4spl?

No, it isn’t. You just have to compile all the bundles and libraries used by the application. Please follow the installation instructions for your platform.

Why does fw4spl provide a launcher?

The launcher is used to create the entry point of the application. It parses the profile and configuration xml file to build it.

How can I debug my program ?

First, you can watch the log of the application. Under Windows platform, log messages are saved on filesystem in SLM.log file, in the working directory. On other systems, they are displayed in the terminal.

You can increase or decrease the log level of a sub-project in the CMake configuration, by setting the advanced variable SPYLOG_LEVEL_<PROJECT> (thus you to press ‘t’ in ccmake or click of the ‘advanced’ checkbox in cmake-gui to show it).

The allowed values are : [‘trace’, ‘debug’, ‘info’, ‘error’, ‘fatal’, ‘warning’, ‘disable’]. the value ‘trace’ gives the maximum of log, whereas ‘disable’ disables log. The default value is ‘error’, which is enough in most cases. ‘warning’ can be useful in some cases. Lower levels are really designed to be activated punctually when debugging a specific piece of code.

Note

Printing many log messages ( by activating trace on all sub-projects for ex. ) can be very time consuming for the application.

Secondly, you can of course compile your application in Debug mode (set CMAKE_BUILD_TYPE to “Debug” ) and then debug it using gdb (Linux/Mac), QtCreator (Linux/Mac), Visual Studio (Windows) or Android Studio (Android).

Thirdly, you can manage the program complexity by reducing the number of activated components (in profile.xml) and the number of created services (in config.xml) to better localize errors.

Fourthly, verify that your profile.xml / plugin.xml and each bundle plugin.xml are well-formed, by using xmllint (command line tool provided by libxml2).

I have an assertion/fatal message when I launch my program, any idea to correct the problem ?

First, you can read the output message :) and try to solve the problem. In many cases, there are two kind of problems. The program fails to :

  • create the service given in configuration. In this case, four reasons are possibles :
    • the name of service implementation in config.xml contains mistakes
    • the bundle that contains this service is not activated in the profile
    • the bundle plugin.xml, that contains this service, does not declare the service or the declaration contains mistakes.
    • the service is not registered in the Service Factory (forget of macro fwServicesRegisterMacro(...) in file .cpp)
  • manage the configuration of service. In this case, the implementation code in .cpp file ( generally configuring() method of service ) does not correspond to description code in config.xml ( Missing arguments, or not well-formed, or mistakes string parameters ).

Do I need to convert my data object to a ::fwData::Object ?

Do you need to share this data between services ?

  • If the answer is no, then you don’t need to wrap your data.
  • Otherwise, you need to have an object that inherits of ::fwData::Object.

In this latter case, do you need to share this object between different services which use different third-party libraries, i.e. for ::fwData::Image, itk::Image vs vtkImage ?

  • If the answer is yes, then you need create a new object like fwData::Image and a wrapping with fwData::Image<=>itk::Image and fwData::Image<=>vtkImage.
  • Otherwise, you can just encapsulated an itk::Image in fwData::Image and create an accessor on it. ( however, this choice implies that all applications that use fwData::Image need ITK library for running. )

What is a sesh@ path ?

A sesh@ path is a path used to browse an object (and sub-object) using the introspection (see fwDataCamp and Serialization). The path begins with a ‘@’ or a ‘!’. - @ : the returned string is the fwID of the sub-object defined by the path. - ! : the returned string is the value of the sub-object, it works only on String, Integer, Float and Boolean object.

Sadly, we do not have yet a document giving the paths for all existing data. To know how an object can be accessed with a sesh@ path, you canal have a look at the corresponding fwDataCamp implementation of the object. For instance, the file fwDataCamp/Image.cpp shows :

fwCampImplementDataMacro((fwData)(Image))
{
    builder
    .tag("object_version", "2")
    .tag("lib_name", "fwData")
    .base< ::fwData::Object>()
    .property("size", &::fwData::Image::m_size)
    .property("type", &::fwData::Image::m_type)
    .property("spacing", &::fwData::Image::m_spacing)
    .property("origin", &::fwData::Image::m_origin)
    .property("array", &::fwData::Image::m_dataArray)
    .property("nb_components", &::fwData::Image::m_numberOfComponents)
    .property("window_center", &::fwData::Image::m_windowCenter)
    .property("window_width", &::fwData::Image::m_windowWidth)
    ;
}

Which means that each property is a possible sesh@ path. For instance the height of the image can be retrieved using:

@values.size.1

Other examples:

To get the fwID of an image contained in a Composite with the key “myImage”

@values.myImage

To get the fwID of the first reconstruction of a ModelSeries contained in a Composite with the key “myModel”

@values.myModel.reconstruction_db.0

How to use CMake with Fw4spl

CMake for fw4spl

Introduction

Fw4spl and it’s dependencies are based on CMake . Note that the minimal version of cmake to have is 3.1.

CMake files for dependencies

fw4spl dependencies are based on the ExternalProject concept from lastest versions of cmake.

The concept is to create custom targets to build projects in external trees. Each project has custom steps for download, update/patch, configure, build and install.

Here is a simple example from camp :

cmake_minimum_required(VERSION 2.8)

project(campBuilder)

include(ExternalProject)

set(CAMP_CMAKE_ARGS ${COMMON_CMAKE_ARGS}
                    -DBUILD_DOXYGEN:BOOL=OFF
                    -DBOOST_INCLUDEDIR:PATH=${CMAKE_INSTALL_PREFIX}/include/boost-1_57
)

getCachedUrl(https://github.com/greenjava/camp/archive/0.7.1.1.tar.gz CACHED_URL)

ExternalProject_Add(
    camp
    URL ${CACHED_URL}
    DOWNLOAD_DIR ${ARCHIVE_DIR}
    DEPENDS boost
    INSTALL_DIR ${CMAKE_INSTALL_PREFIX}
    CMAKE_ARGS ${CAMP_CMAKE_ARGS}
    STEP_TARGETS CopyConfigFileToInstall
)

ExternalProject_Add_Step(camp CopyConfigFileToInstall
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/cmake/findBinpkgs/FindCAMP.cmake ${CMAKE_INSTALL_PREFIX}/FindCAMP.cmake
    COMMENT "Install configuration file"

The important parts are in the ExternalProject_Add fonction:

  • URL: is the download link of the sources
  • DOWNLOAD_DIR: The folder where the sources will be stored (set globaly for all deps)
  • DEPENDS: The dependencies of the current library (will be compiled before)
  • INSTALL_DIR: The folder in which the library will be installed (set globaly for all deps)
  • CMAKE_ARGS: CMake options for library which have a cmake build system
  • STEP_TARGETS: Custom command (in this example it will copy a script in the install folder)

Note that in other script you can have much more options like:

  • PATCH_COMMAND
  • CONFIGURE_COMMAND
  • BUILD_COMMAND
  • INSTALL_COMMAND

Refer you to the documentation of ExternalProject for more informations.

CMake files for fw4spl

Each project (apps, bundles, libs) have two “CMake” files:

  • CMakeLists.txt
  • Properties.cmake
The CMakeLists.txt file

The CMakeLists.txt should contain at least the function fwLoadProperties() to load the Properties.cmake. But it can also contain others functions useful to link with external libraries.

Here is an example of CMakeLists.txt from guiQt Bundle :

fwLoadProperties()

find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)


fwForwardInclude(
    ${Qt5Core_INCLUDE_DIRS}
    ${Qt5Gui_INCLUDE_DIRS}
    ${Qt5Widgets_INCLUDE_DIRS}
)

fwLink(
       ${Qt5Core_LIBRARIES}
       ${Qt5Gui_LIBRARIES}
       ${Qt5Widgets_LIBRARIES}
)

set_target_properties(${FWPROJECT_NAME} PROPERTIES AUTOMOC TRUE)

The first line fwLoadProperties() will load the properties.cmake (see explanation in the next section).

The next lines are for the link with an external libraries (fw4spl-deps), in this example it is Qt.

The first thing to do is to call find_package(The_lib COMPONENTS The_component).

The use fwForwardInclude to add includes directories to the target, and fwLink to link the libraries with your target.

You can also add custom properties to your target with set_target_properties.

The Properties.cmake file

Properties.cmake should contain informations like name, version, dependencies and requirements of the current target.

Here is an example of Properties.cmake from fwData library:

set( NAME fwData )
set( VERSION 0.1 )
set( TYPE LIBRARY )
set( DEPENDENCIES fwCamp fwCom fwCore fwMath fwMemory fwTools )
set( REQUIREMENTS  )
  • NAME: Name of the target
  • VERSION: Version of the target
  • TYPE: Type of the target (can be library, bundle or executable)
  • DEPENDENCIES: Link the target with the given libraries (see target_link_libraries )
  • REQUIREMENTS: Ensure that the depends are build before target (see add_dependencies )

Tutorials

How can I add a new dependency

You may want to add a new dependency into fw4spl-deps or you may want to add your own folder of dependencies.

Tip

You need to know that the main CMakeLists.txt is in fw4spl-deps, and you can add as many additional folders as you want. Use the ADDITONNAL_DEPS option in cmake to set the path of your custom deps.

Add a new deps in fw4spl-deps

Adding a new deps is quite easy, the only things to do is to add a new folder myNewDeps and put a CMakeLists.txt file into it. The CMakeLists.txt should contain at least:

  • cmake_minimum_required()
  • project()
  • include(ExternalProject)
  • ExternalProject_Add(...)

For example:

cmake_minimum_required(VERSION 2.8)

project(myDepsBuilder)

include(ExternalProject)

getCachedUrl(http://myDeps.com/myDeps.zip CACHED_URL)

ExternalProject_Add(
    myDeps
    URL ${CACHED_URL}
    DOWNLOAD_DIR Path/To/Your/Download/dir
    PATCH_COMMAND your_patch_command (optional)
    CONFIGURE_COMMAND your_configure_command (optional)
    BUILD_COMMAND  your_build_command (optional)
    INSTALL_COMMAND your_install_command (optional)
    INSTALL_DIR your_install_dir
    CMAKE_ARGS cmake_arguments
)
Add a custom deps repository

You may want to add your own folder of dependencies (as fw4spl-ext-deps or fw4spl-ar-deps). In this case your main need to create a CMakeLists.txt in the root of your folder (myDepsFolder/CMakeLists.txt) in order to list the subdirectories of your deps.

cmake_minimum_required(VERSION 2.8)

project(CustomDeps)

list(APPEND SUBDIRECTORIES myDeps1)
list(APPEND SUBDIRECTORIES myDeps2)
...

Then when you do a ccmake or cmake-gui in the build of your deps, you need to add the path to your custom repository in the ADDITONNAL_DEPS option. Then cmake will automaticaly parsed your folder.

How can I add a custom bundle in fw4spl

You may want to add a new bundle/lib/app in an existing repository or you may want to add your custom repository to fw4spl.

Tip

You need to know that the main CMakeLists.txt is in fw4spl repository, and you can add as many additional repository as you want. Use the ADDITIONAL_PROJECTS option in cmake to add path of your custom folders.

Add a new bundle/lib/app in fw4spl

The only thing to do is to write a CMakeLists.txt and a Properties.cmake (see section Cmake for Fw4spl for more informations).

Add a custom repository to fw4spl

As the main CMakeLists.txt is in fw4spl repository,you need to add the path of your folder in ADDITIONAL_PROJECTS option when you launch ccmake of cmake-gui on the build folder of fw4spl. Then your folder will automaticaly be parsed by cmake.

Note

All your bundle/lib/application need to respect the fw4spl-cmake conventions and have a CMakeLists.txt and a Properties.cmake.

Contributors

Contributors

From 2004 to 2006, an advanced modular software for patient modeling (see publication page) has been designed and implemented by Guillaume Brocker, Johan Moreau, Jean-Baptiste Fasquel, Vincent Agnus and Nicolas Papier. This represented the basis of the component management system of FW4SPL, essentially conceived by Guillaume Brocker and Johan Moreau. This framework version (v0.1) was used to create 3 software tools in visualization and medical image processing in the Eureka project Odysseus (3DVPM, 3DDVP and MARNS software).

Throughout 2007, Vincent Agnus and Jean-Baptiste Fasquel conceived and implemented the main core mechanisms of this new version of FW4SPL. Jean-Baptiste Fasquel focused on the notion of roles coupled with the component management system, the inter-role communication system, as well as an appropriate XML formalism for the description of both roles embedded into components and description of software. Many basic software tools have been built to validate the architecture (see publication page). Vincent Agnus also focused on role design, and more specifically on data structures, a generic serialization mechanism and a powerful template dispatching technique. During his internship in 2007, Benjamin Gaillard has improved the communication system in FW4SPL. In parallel with the work on the pure FW4SPL system, Johan Moreau got involved in the construction/compilation system and, together with Arnaud Charnoz, in the management of external dependencies and some specific medical data structures. Their work also led to advanced visualization of medical images (free download). Early 2008, the framework was available in version 0.2.

During the period from mid-2008 to mid-2009, some advanced data structures and functionalities have been developed on the basis of the architecture to further evaluate it and make it more robust. A larger development team has been involved, including Emilie Harquel, Julien Waechter and Nicolas Philipps additionally to Vincent Agnus, Jean-Baptiste Fasquel, Johan Moreau and Arnaud Charnoz. Additional efforts have been made by Johan Moreau and Arnaud Charnoz on the management of external dependencies. Nicolas Philipps, Julien Waechter and Johan Moreau also improved the construction environment Sconspiracy, initially opened as an opensource project YAMS++ in 2007. The version 0.3 of the framework had been achieved by early summer 2009.

From mid-2009 to mid-2010, the main work on FW4SPL included: performing generic scenes for visualization (mainly developed by Nicolas Philipps, Julien Waechter and Vincent Agnus), a new communication system (mainly developed by Nicolas Philipps and Arnaud Charnoz), new UI components (mainly developed by Emilie Harquel and Julien Waechter), better log and assert system (by Arnaud Charnoz), new documentation (mainly done by Pascal Monnier, Alexandre Hostettler).

FW4SPL (version 0.4) has been opened late 2009 and was used to create several software in the European project Passport (VR-Render, VR-Render WLE, AR-Surg, VR-Planning and VR-Probe software). In December, we had switched to version 0.5 (with generic scene). The latest stable version is 0.6 (new communication system) and the current branch development is the 0.6.1 branch.

Version 0.7 adds a limited Qt support during summer 2010 (Hocine Chekatt’s internship) and limited support for Python, OpenNI and SOFA (these two last parts had been developed by Altran). During 2011, FW4SPL 0.8 adds a Qt based 2D scene (Ivan MATHIEU’s internship), new buffer for meshes and images, new memory dump mechanisms, a new set of applications (Apps/Examples), a new Dicom reader (Jordi ROMERA’s internship), new registration functionalities (Marc Schweitzer’s internship) an improved image origin management, etc. A new scenegraph design has been developed but not yet integrated (Loïc Velut’s internship).

Multithreading (fwThread), signal/slot (fwCom), dump management and data introspection (fwAtoms) mechanisms have been added during 2012 in version 0.9 (co-working between IRCAD and IHU). A new design to manage data and store data (Julien Weinzorn’s internship) has been prototyped.

This version supports msvc2010 and has also been used to evaluate the transition to Android and iOS (Adrien Bensaibi’s internship). Altran has added a connector towards the management tool of the MIDAS content developed by Kitware. Finally, a version management mechanism has been developed (fwAtomsPatch) (Clément Troesch’s internship) and new data has been created (fwMedData). This version has been used by the Visible Patient company within the framework of their ISO 13485 certification. A new repository has also been created (fw4spl-ext) with the aim of welcoming not yet stabilized functionalities or to host PoC. The CMake construction system is also supported.

Version 0.10.0 provides the notion of timeline to manage temporal data (IHU). The SConspiracy construction system has been removed.

IRCAD

Core, visualization, image processing, applications and tutorials

Team : Johan Moreau, Marc Schweitzer, Frédéric CHAMP,
Flavien Bridault-Louchez, Pascal Monnier
IHU

Core, visualization, image processing, applications and tutorials

Team : Julien Waechter, Emilie Harquel, Jessica GROMER

ALTRAN
Proof of concept on Kinect and Sofa integration

Proof of concept on MIDAS integration

VP Team : Nicolas Philipps, Valentin Martinet, Arnaud Charnoz, Julien Weinzorn
EHEALTH
This project has partly funded by the European Commission via PASSPORT project :

Libraries

DCMTK BOOST GNU CFITSIO
ITK VTK QT PYTHON
WIX ZLIB XML2  

FLOSS projects using FW4SPL

skuld-project Skuld project work on mobile port (iphone, android, meego/maemo, ...) of FW4SPL

Tutorials

[Tuto01Basic] Create an application

The first tutorial represents a basic application that launches a simple empty frame.

_images/tuto01Basic.png

Prerequisites

Before reading this tutorial, you should have seen :

Structure

An application is organized around three main files :
  • CMakeLists.txt
  • Properties.cmake
  • plugin.xml
CMakeLists.txt

The CMakeLists is parsed by CMake. For the aplication it should contain the line :

fwLoadProperties()

This line allows to load Properties.cmake file.

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto01Basic ) # Name of the application
set( VERSION 0.1 ) # Version of the application
set( TYPE APP ) # Type APP represent "Application"
set( DEPENDENCIES  ) # For an application we have no dependencies (libraries to link)
set( REQUIREMENTS # The bundles used by this application
    dataReg # to load the data registry
    servicesReg # to load the service registry
    gui # to load gui
    guiQt # to load qt implementation of gui
    fwlauncher # executable to run the application
    appXml2 # to parse the application configuration
)

# Set the configuration to use : 'tutoBasicConfig'
bundleParam(appXml2 PARAM_LIST config PARAM_VALUES tutoBasicConfig)

This file contains the minimal requirements to launch an application with a Qt user interface.

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is located in the rc/ directory of the application. It defines the services to run.

<!-- Application name and version (the version is automatically replaced by CMake
     using the version defined in the Properties.cmake) -->
<plugin id="Tuto01Basic" version="@DASH_VERSION@">

    <!-- The bundles in requirements are automatically started when this
         Application is launched. -->
    <requirement id="dataReg" />
    <requirement id="servicesReg" />
    <requirement id="guiQt" />

    <!-- Defines the App-config -->
    <extension implements="::fwServices::registry::AppConfig2">
        <id>tutoBasicConfig</id><!-- identifier of the configuration -->
        <config>

            <!-- Frame service -->
            <service uid="myFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>tutoBasicApplicationName</name>
                        <icon>@BUNDLE_PREFIX@/Tuto01Basic_0-1/tuto.ico</icon>
                        <minSize width="800" height="600" />
                    </frame>
                </gui>
            </service>

            <start uid="myFrame" /><!-- start the frame service -->

        </config>
    </extension>
</plugin>

The ::fwServices::registry::AppConfig2 extension defines the configuration of an application.

id:
The configuration identifier.
config:

Contains the list of objects and services used by the application.

For this tutorial, we have no object and only one service ::gui::frame::DefaultFrame.

There are others tags that will be described in the next tutorials.

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto01Basic_0-1/profile.xml

[Tuto02DataServiceBasic] Display an image

The second tutorial represents a basic application that display a medical 3D image.

_images/tuto02DataServiceBasic.png

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto02DataServiceBasic )
set( VERSION 0.1 )
set( TYPE APP )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io # contains the interface for reader and writer.
    ioVTK # contains the reader and writer for VTK files (image and mesh).
    visuVTK # loads VTK rendering library (fwRenderVTK).
    visuVTKQt # containsthe vtk Renderer window interactor manager using Qt.
    vtkSimpleNegato # contains a visualization service of medical image.
    fwlauncher
    appXml2
)

bundleParam(appXml2 PARAM_LIST config PARAM_VALUES tutoDataServiceBasicConfig)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<plugin id="Tuto02DataServiceBasic" version="@DASH_VERSION@">

    <!-- The bundles in requirements are automatically started when this
         Application is launched. -->
    <requirement id="dataReg" />
    <requirement id="servicesReg" />
    <requirement id="visuVTKQt" />

    <extension implements="::fwServices::registry::AppConfig2">
        <id>tutoDataServiceBasicConfig</id>
        <config>

            <!-- In tutoDataServiceBasic, the central data object is a ::fwData::Image. -->
            <object uid="imageData" type="::fwData::Image" />

            <!--
                Description service of the GUI:
                The ::gui::frame::SDefaultFrame service automatically positions the various
                containers in the application main window.
                Here, it declares a container for the 3D rendering service.
            -->
            <service uid="mainFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>tutoDataServiceBasic</name>
                        <icon>@BUNDLE_PREFIX@/Tuto02DataServiceBasic_0-1/tuto.ico</icon>
                        <minSize width="800" height="600" />
                    </frame>
                </gui>
                <registry>
                    <!-- Associate the container for the rendering service. -->
                    <view sid="myRendering" />
                </registry>
            </service>

            <!--
                Reading service:
                The <file> tag defines the path of the image to load. Here, it is a relative
                path from the repository in which you launch the application.
            -->
            <service uid="myReaderPathFile" type="::ioVTK::SImageReader">
                <inout key="image" uid="imageData" />
                <file>../../data/patient1.vtk</file>
            </service>

            <!--
                Visualization service of a 3D medical image:
                This service will render the 3D image.
            -->
            <service uid="myRendering" type="::vtkSimpleNegato::SRenderer">
                <in key="image" uid="imageData" />
            </service>

            <!--
                Definition of the starting order of the different services:
                The frame defines the 3D scene container, so it must be started first.
                The services will be stopped the reverse order compared to the starting one.
            -->
            <start uid="mainFrame" />
            <start uid="myReaderPathFile" />
            <start uid="myRendering" />

            <!--
                Definition of the service to update:
                The reading service load the data on the update.
                The render update must be called after the reading of the image.
            -->
            <update uid="myReaderPathFile" />
            <update uid="myRendering" />

        </config>
    </extension>

</plugin>
For this tutorial, we have only one object ::fwData::Image and three service:
  • ::gui::frame::DefaultFrame: frame service
  • ::ioVTK::ImageReaderService: reader for 3D VTK image
  • ::vtkSimpleNegato::SRenderer: render for 3D image
The order of the elements in the configuration is important:
  1. <object>
  2. <service>
  3. <connect> (see [Tuto04SignalSlot] Signal-slot communication)
  4. <start>
  5. <update>

Note

To avoid the <start uid="myRendering" />, the frame service can automatically start the rendering service: you just need to add the attribute start="yes" in the <view> tag.

Run

To run the application, you must call the following line in the install or build directory:

bin/fwlauncher Bundles/Tuto02DataServiceBasic_0-1/profile.xml

[Tuto03DataService] Display an image with menu

The third tutorial is similar to the previous application, but we add gui service like menus.

_images/tuto03DataService.png

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto03DataService )
set( VERSION 0.1 )
set( TYPE APP )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io
    ioVTK
    uiIO # contains services to show dialogs for reader/writer selection
    visuVTKQt
    vtkSimpleNegato
    launcher
    appXml2
)

bundleParam(appXml2 PARAM_LIST config PARAM_VALUES tutoDataServiceConfig)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<plugin id="Tuto03DataService" version="@DASH_VERSION@">
    <requirement id="servicesReg" />

    <extension implements="::fwServices::registry::AppConfig2">
        <id>tutoDataServiceConfig</id>
        <config>

            <!-- The root data object in tutoDataService is a ::fwData::Image. -->
            <object uid="image" type="::fwData::Image" />

            <!-- Frame service:
                The frame creates a container fot the rendering service and a menu bar.
                In this tutorial, the gui services will automatically start the services they register using the
                'start="yes"' attribute.
            -->
            <service uid="myFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>tutoDataService</name>
                        <icon>@BUNDLE_PREFIX@/Tuto03DataService_0-1/tuto.ico</icon>
                        <minSize width="800" height="600" />
                    </frame>
                    <menuBar />
                </gui>
                <registry>
                    <menuBar sid="myMenuBar" start="yes" />
                    <view sid="myRendering" start="yes" />
                </registry>
            </service>

            <!--
                Menu bar service:
                This service defines the list of the menus displayed in the menu bar.
                Here, we have only one menu: File
                Each <menu> declared into the <layout> tag, must have its associated <menu> into the <registry> tag.
                The <layout> tags defines the displayed information, whereas the <registry> tags defines the
                services information.
            -->
            <service uid="myMenuBar" type="::gui::aspect::SDefaultMenuBar">
                <gui>
                    <layout>
                        <menu name="File" />
                    </layout>
                </gui>
                <registry>
                    <menu sid="myMenu" start="yes" />
                </registry>
            </service>

            <!--
                Menu service:
                This service defines the actions displayed in the "File" menu.
                Here, it registers two actions: "Open file", and "Quit".
                As in the menu bar service, each <menuItem> declared into the <layout> tag, must have its
                associated <menuItem> into the <registry> tag.

                It's possible to associate specific attributes for <menuItem> to configure their style, shortcut...
                In this tutorial, the attribute 'specialAction' has the value "QUIT". On MS Windows, there's no
                impact, but on Mac OS, this value installs the menuItem in the system menu bar, and on Linux this
                value installs the default 'Quit' system icon in the menuItem.
            -->
            <service uid="myMenu" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Open file" shortcut="Ctrl+O" />
                        <separator />
                        <menuItem name="Quit" specialAction="QUIT" shortcut="Ctrl+Q" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionOpenFile" start="yes" />
                    <menuItem sid="actionQuit" start="yes" />
                </registry>
            </service>

            <!--
                Quit action:
                The action service (::gui::action::SQuit) is a generic action that will close the application
                when the user click on the menuItem "Quit".
            -->
            <service uid="actionQuit" type="::gui::action::SQuit" />

            <!--
                Open file action:
                This service (::gui::action::StarterActionService) is a generic action, it starts and update the
                services given in the configuration when the user clicks on the action.
                Here, the reader selector will be called when the actions is clicked.
            -->
            <service uid="actionOpenFile" type="::gui::action::SStarter">
                <start uid="myReaderPathFile" />
            </service>

            <!--
                Reader selector dialog:
                This is a generic service that show a dialog to display all the reader or writer available for its
                associated data. By default it is configured to show reader. (Note: if there is only one reading
                service, it is directly selected without dialog box.)
                Here, it the only reader available to read a ::fwData::Image is ::ioVTK::ImageReaderService (see
                Tuto02DataServiceBasic), so the selector will not be displayed.
                When the service was chosen, it is started, updated and stopped, so the data is read.
            -->
            <service uid="myReaderPathFile" type="::uiIO::editor::SIOSelector" >
                <inout key="target" uid="image" />
            </service>

            <!--
                3D visualization service of medical images:
                Here, the service attribute 'autoConnect="yes"' allows the rendering to listen the modification of
                the data image. So, when the image is loaded, the visualization will be updated.
            -->
            <service uid="myRendering" type="::vtkSimpleNegato::SRenderer" autoConnect="yes" >
                <in key="image" uid="image" />
            </service>

            <!--
                Here, we only start the frame because all the others services are managed by the gui service:
                - the frame starts the menu bar and the redering service
                - the menu bar starts the menu services
                - the menus starts the actions
            -->
            <start uid="myFrame" />

        </config>
    </extension>
</plugin>

The framework provides some gui services:

Frame (::gui::frame::DefaultFrame)
This service display a frame and creates menu bar, tool bar and container for views, rendering service, ...
View (::gui::view::DefaultView)
This service creates sub-container and tool bar.
Menu bar (::gui::aspect::DefaultMenuSrv)
A menu bar displays menus.
Tool bar (::gui::aspect::DefaultToolBarSrv)
A tool bar displays actions, menus and editors.
Menu (::gui::aspect::DefaultMenuSrv)
A menu displays actions and sub-menus.
Action (inherited from ::fwGui::IActionSrv )
An action is a service inherited from ::fwGui::IActionSrv. It is called when the user clicks on the associated tool bar or menu.
Editors (inherited from ::gui::editor::IEditor)
An editor is a service inherited from ::gui::editor::IEditor. It is used to creates your own gui container.

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto03DataService_0-1/profile.xml

[Tuto04SignalSlot] Signal-slot communication

The fourth tutorial explains the communication mechanism with signals and slots.

_images/tuto04SignalSlot.png

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto04SignalSlot )
set( VERSION 0.1 )
set( TYPE APP )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io
    ioVTK
    uiIO
    visuVTKQt
    vtkSimpleMesh # contains a visualization service of mesh.
    launcher
    appXml2
)

bundleParam(appXml2 PARAM_LIST config PARAM_VALUES tutoSignalSlotConfig)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<plugin id="Tuto04SignalSlot" version="@DASH_VERSION@">

    <requirement id="servicesReg" />

    <extension implements="::fwServices::registry::AppConfig2">
        <id>tutoSignalSlotConfig</id>
        <config>

            <!-- The main data object is ::fwData::Mesh. -->
            <object uid="mesh" type="::fwData::Mesh" />

            <service uid="myFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>tutoSignalSlot</name>
                        <icon>@BUNDLE_PREFIX@/Tuto04SignalSlot_0-1/tuto.ico</icon>
                        <minSize width="720" height="600" />
                    </frame>
                    <menuBar />
                </gui>
                <registry>
                    <menuBar sid="myMenuBar" start="yes" />
                    <view sid="myDefaultView" start="yes" />
                </registry>
            </service>

            <service uid="myMenuBar" type="::gui::aspect::SDefaultMenuBar">
                <gui>
                    <layout>
                        <menu name="File" />
                    </layout>
                </gui>
                <registry>
                    <menu sid="myMenuFile" start="yes" />
                </registry>
            </service>

            <!--
                Default view service:
                This service defines the view layout. The type '::fwGui::CardinalLayoutManager' represents a main
                central view and other views at the 'right', 'left', 'bottom' or 'top'.
                Here the application contains a central view at the right.

                Each <view> declared into the <layout> tag, must have its associated <view> into the <registry> tag.
                A minimum window height and a width are given to the two non-central views.
            -->
            <service uid="myDefaultView" type="::gui::view::SDefaultView">
                <gui>
                    <layout type="::fwGui::CardinalLayoutManager">
                        <view caption="Rendering 1" align="center" />
                        <view caption="Rendering 2" align="right" minWidth="400" minHeight="100" />
                        <view caption="Rendering 3" align="right" minWidth="400" minHeight="100" />
                    </layout>
                </gui>
                <registry>
                    <view sid="myRendering1" start="yes" />
                    <view sid="myRendering2" start="yes" />
                    <view sid="myRendering3" start="yes" />
                </registry>
            </service>

            <service uid="myMenuFile" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Open file" shortcut="Ctrl+O" />
                        <separator />
                        <menuItem name="Quit" specialAction="QUIT" shortcut="Ctrl+Q" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionOpenFile" start="yes" />
                    <menuItem sid="actionQuit" start="yes" />
                </registry>
            </service>

            <service uid="actionOpenFile" type="::gui::action::SStarter">
                <start uid="myReaderPathFile" />
            </service>

            <service uid="actionQuit" type="::gui::action::SQuit" />

            <service uid="myReaderPathFile" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="mesh" />
                <type mode="reader" /><!-- mode is optional (by default it is "reader") -->
            </service>

            <!--
                Visualization services:
                We have three rendering service representing a 3D scene displaying the loaded mesh. The scene are
                shown in the windows defines in 'view' service.
            -->
            <service uid="myRendering1" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="mesh" />
            </service>
            <service uid="myRendering2" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="mesh" />
            </service>
            <service uid="myRendering3" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="mesh" />
            </service>

            <!--
                Each 3D scene owns a 3D camera that can be moved by clicking in the scene.
                - When the camera move, a signal 'camUpdated' is emitted with the new camera information (position,
                focal, view up).
                - To update the camera without clicking, you could call the slot 'updateCamPosition'

                Here, we connect each rendering service signal 'camUpdated' to the others service slot
                'updateCamPosition', so the cameras are synchronized in each scene.
            -->
            <connect>
                <signal>myRendering1/camUpdated</signal>
                <slot>myRendering2/updateCamPosition</slot>
                <slot>myRendering3/updateCamPosition</slot>
            </connect>

            <connect>
                <signal>myRendering2/camUpdated</signal>
                <slot>myRendering1/updateCamPosition</slot>
                <slot>myRendering3/updateCamPosition</slot>
            </connect>

            <connect>
                <signal>myRendering3/camUpdated</signal>
                <slot>myRendering2/updateCamPosition</slot>
                <slot>myRendering1/updateCamPosition</slot>
            </connect>

            <start uid="myFrame" />

        </config>
    </extension>

</plugin>

You can also group the signals and all the slots together.

<connect>
    <signal>myRenderingTuto1/camUpdated</signal>
    <signal>myRenderingTuto2/camUpdated</signal>
    <signal>myRenderingTuto3/camUpdated</signal>

    <slot>myRenderingTuto1/updateCamPosition</slot>
    <slot>myRenderingTuto2/updateCamPosition</slot>
    <slot>myRenderingTuto3/updateCamPosition</slot>
</proxy>

Tip

You can remove a connection to see that a camera in the scene is no longer synchronized.

Signal and slot creation

RendererService.hpp
class VTKSIMPLEMESH_CLASS_API RendererService : public fwRender::IRender
{
public:
    // .....

    typedef ::boost::shared_array< double > SharedArray;

    typedef ::fwCom::Signal< void (SharedArray, SharedArray, SharedArray) > CamUpdatedSignalType;

    // .....

    /// This method is call when the VTK camera position is modified.
    /// It notifies the new camera position.
    void notifyCamPositionUpdated();

private:

    /// Slot: receives new camera information (position, focal, viewUp).
    /// Update camera with new information.
    void updateCamPosition(SharedArray positionValue,
                           SharedArray focalValue,
                           SharedArray viewUpValue);

    // ....

    /// Signal emitted when camera position is updated.
    CamUpdatedSignalType::sptr m_sigCamUpdated;
}
RendererService.cpp
RendererService::RendererService() throw()
{
    m_sigCamUpdated = newSignal<CamUpdatedSignalType>("camUpdated");

    newSlot("updateCamPosition", &RendererService::updateCamPosition, this);
}

//-----------------------------------------------------------------------------

void RendererService::updateCamPosition(SharedArray positionValue,
                                        SharedArray focalValue,
                                        SharedArray viewUpValue)
{
    vtkCamera* camera = m_render->GetActiveCamera();

    // Update the vtk camera
    camera->SetPosition(positionValue.get());
    camera->SetFocalPoint(focalValue.get());
    camera->SetViewUp(viewUpValue.get());
    camera->SetClippingRange(0.1, 1000000);

    // Render the scene
    m_interactorManager->getInteractor()->Render();
}


//-----------------------------------------------------------------------------

void RendererService::notifyCamPositionUpdated()
{
    vtkCamera* camera = m_render->GetActiveCamera();

    SharedArray position = SharedArray(new double[3]);
    SharedArray focal    = SharedArray(new double[3]);
    SharedArray viewUp   = SharedArray(new double[3]);

    std::copy(camera->GetPosition(), camera->GetPosition()+3, position.get());
    std::copy(camera->GetFocalPoint(), camera->GetFocalPoint()+3, focal.get());
    std::copy(camera->GetViewUp(), camera->GetViewUp()+3, viewUp.get());

    {
        // The Blocker blocks the connection between the "camUpdated" signal and the
        // "updateCamPosition" slot for this instance of service.
        // The block is release at the end of the scope.
        ::fwCom::Connection::Blocker block(
                            m_sigCamUpdated->getConnection(m_this->slot("updateCamPosition")));

        // Asynchronous emit of "camUpdated" signal
        m_sigCamUpdated->asyncEmit (position, focal, viewUp);
    }
}

//-----------------------------------------------------------------------------

// ......

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto04SignalSlot_0-1/profile.xml

[Tuto05Mesher] Create a mesh from an image

The fifth tutorial explains how to use several objects in an application. This application provides an action to creates a mesh from an image.

_images/tuto05Mesher.png

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto05Mesher )
set( VERSION 0.1 )
set( TYPE APP )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io
    ioVTK
    visuVTKQt
    uiIO
    vtkSimpleNegato
    vtkSimpleMesh
    opVTKMesh # provides services to generate a mesh from an image.
    launcher
    appXml2
)

bundleParam(appXml2 PARAM_LIST config PARAM_VALUES MesherConfig)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<plugin id="Tuto05Mesher" version="@DASH_VERSION@">

    <requirement id="dataReg" />
    <requirement id="servicesReg" />
    <requirement id="visuVTKQt" />

    <extension implements="::fwServices::registry::AppConfig2">
        <id>MesherConfig</id>
        <config>

            <!-- Mesh object associated to the uid 'myMesh' -->
            <object uid="myMesh" type="::fwData::Mesh" />

            <!-- Image object associated to the key 'myImage' -->
            <object uid="myImage" type="::fwData::Image" />

            <!-- Frame & View -->

            <service uid="myFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>Mesher</name>
                        <icon>@BUNDLE_PREFIX@/Tuto05Mesher_0-1/tuto.ico</icon>
                        <minSize width="800" height="600" />
                    </frame>
                    <menuBar />
                </gui>
                <registry>
                    <menuBar sid="myMenuBar" start="yes" />
                    <view sid="myDefaultView" start="yes" />
                </registry>
            </service>

            <!--
                Default view service:
                The type '::fwGui::LineLayoutManager' represents a layout where the view are aligned
                horizontally or vertically (set orientation value 'horizontal' or 'vertical').
                It is possible to add a 'proportion' attribute for the <view> to defined the proportion
                used by the view compared to the others.
            -->
            <service uid="myDefaultView" type="::gui::view::SDefaultView">
                <gui>
                    <layout type="::fwGui::LineLayoutManager">
                        <orientation value="horizontal" />
                        <view caption="Image view" />
                        <view caption="Mesh view" />
                    </layout>
                </gui>
                <registry>
                    <view sid="RenderingImage" start="yes" />
                    <view sid="RenderingMesh" start="yes" />
                </registry>
            </service>

            <!-- Menu Bar, Menus & Actions -->

            <service uid="myMenuBar" type="::gui::aspect::SDefaultMenuBar">
                <gui>
                    <layout>
                        <menu name="File" />
                        <menu name="Mesher" />
                    </layout>
                </gui>
                <registry>
                    <menu sid="menuFile" start="yes" />
                    <menu sid="menuMesher" start="yes" />
                </registry>
            </service>

            <service uid="menuFile" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Open image file" shortcut="Ctrl+O" />
                        <menuItem name="Save image" />
                        <separator />
                        <menuItem name="Open mesh file" shortcut="Ctrl+M" />
                        <menuItem name="Save mesh" />
                        <separator />
                        <menuItem name="Quit" specialAction="QUIT" shortcut="Ctrl+Q" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionOpenImageFile" start="yes" />
                    <menuItem sid="actionSaveImageFile" start="yes" />
                    <menuItem sid="actionOpenMeshFile" start="yes" />
                    <menuItem sid="actionSaveMeshFile" start="yes" />
                    <menuItem sid="actionQuit" start="yes" />
                </registry>
            </service>

            <service uid="menuMesher" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Compute Mesh (VTK)" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionCreateVTKMesh" start="yes" />
                </registry>
            </service>

            <service uid="actionQuit" type="::gui::action::SQuit" />

            <service uid="actionOpenImageFile" type="::gui::action::SStarter">
                <start uid="readerPathImageFile" />
            </service>

            <service uid="actionSaveImageFile" type="::gui::action::SStarter">
                <start uid="writerImageFile" />
            </service>

            <service uid="actionOpenMeshFile" type="::gui::action::SStarter">
                <start uid="readerPathMeshFile" />
            </service>

            <service uid="actionSaveMeshFile" type="::gui::action::SStarter">
                <start uid="writerMeshFile" />
            </service>

            <service uid="actionCreateVTKMesh" type="::opVTKMesh::action::SMeshCreation">
                <in key="image" uid="myImage" />
                <inout key="mesh" uid="myMesh" />
                <percentReduction value="0" />
            </service>

            <!--
                Services associated to the Image data :
                Visualization, reading and writing service creation.
            -->
            <service uid="RenderingImage" type="::vtkSimpleNegato::SRenderer" autoConnect="yes" >
                <in key="image" uid="myImage" />
            </service>

            <service uid="readerPathImageFile" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="myImage" />
                <type mode="reader" />
            </service>

            <service uid="writerImageFile" type="::uiIO::editor::SIOSelector">
                <in key="target" uid="myImage" />
                <type mode="writer" />
            </service>

            <!--
                Services associated to the Mesh data :
                Visualization, reading and writing service creation.
            -->
            <service uid="RenderingMesh" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="myMesh" />
            </service>

            <service uid="readerPathMeshFile" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="myMesh" />
                <type mode="reader" />
            </service>

            <service uid="writerMeshFile" type="::uiIO::editor::SIOSelector">
                <in key="target" uid="myMesh" />
                <type mode="writer" />
            </service>

            <start uid="myFrame" />

        </config>
    </extension>
</plugin>

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto05Mesher_0-1/profile.xml

[Tuto06Filter] Apply a filter on an image

This tutorial explains how to perform a filter on an image. Here, the filter applied on the image is a threshold.

_images/tuto06Filter.png

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto06Filter )
set( VERSION 0.1 )
set( TYPE APP )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io
    ioVTK
    uiIO
    visuVTKQt
    vtkSimpleNegato
    opImageFilter # bundle containing the action to performs a threshold
    launcher
    appXml2
)

bundleParam(appXml2 PARAM_LIST config PARAM_VALUES FilterConfig)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<plugin id="Tuto06Filter" version="@DASH_VERSION@">

    <requirement id="dataReg" />
    <requirement id="servicesReg" />
    <requirement id="visuVTKQt" />

    <extension implements="::fwServices::registry::AppConfig2">
        <id>FilterConfig</id>
        <config>

            <object uid="myImage1" type="::fwData::Image" />
            <object uid="myImage2" type="::fwData::Image" />

            <!-- Windows & Main Menu -->
            <service uid="myFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>Filter</name>
                        <icon>@BUNDLE_PREFIX@/Tuto06Filter_0-1/tuto.ico</icon>
                        <minSize width="720" height="600" />
                    </frame>
                    <menuBar />
                </gui>
                <registry>
                    <menuBar sid="myMenuBar" start="yes" />
                    <view sid="myDefaultView" start="yes" />
                </registry>
            </service>

            <service uid="myMenuBar" type="::gui::aspect::SDefaultMenuBar">
                <gui>
                    <layout>
                        <menu name="File" />
                        <menu name="Filter" />
                    </layout>
                </gui>
                <registry>
                    <menu sid="menuFile" start="yes" />
                    <menu sid="menuFilter" start="yes" />
                </registry>
            </service>

            <service uid="myDefaultView" type="::gui::view::SDefaultView">
                <gui>
                    <layout type="::fwGui::CardinalLayoutManager">
                        <view align="center" />
                        <view align="right" minWidth="500" minHeight="100" />
                    </layout>
                </gui>
                <registry>
                    <view sid="RenderingImage1" start="yes" />
                    <view sid="RenderingImage2" start="yes" />
                </registry>
            </service>

            <!-- Menus -->
            <service uid="menuFile" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Open image file" shortcut="Ctrl+O" />
                        <separator />
                        <menuItem name="Quit" specialAction="QUIT" shortcut="Ctrl+Q" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionOpenImageFile" start="yes" />
                    <menuItem sid="actionQuit" start="yes" />
                </registry>
            </service>

            <service uid="menuFilter" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Compute Image Filter" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionImageFilter" start="yes" />
                </registry>
            </service>

            <!-- Actions -->
            <service uid="actionQuit" type="::gui::action::SQuit" />
            <service uid="actionOpenImageFile" type="::gui::action::SStarter" >
                <start uid="readerPathImageFile" />
            </service>

            <!--
                Filter action:
                This action applies a threshold filter. The source image is 'myImage1' and the
                output image is 'myImage2'.
                The two images are declared below.
             -->
            <service uid="actionImageFilter" type="::opImageFilter::action::SThreshold">
                <in key="source" uid="myImage1" />
                <inout key="target" uid="myImage2" />
            </service>

            <!-- Image declaration: -->

            <!--
                1st Image of the composite:
                This is the source image for the filtering.
            -->
            <service uid="RenderingImage1" type="::vtkSimpleNegato::SRenderer" autoConnect="yes" >
                <in key="image" uid="myImage1" />
            </service>

            <service uid="readerPathImageFile" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="myImage1" />
                <type mode="reader" />
            </service>

            <!--
                2nd Image of the composite:
                This is the output image for the filtering.
            -->
            <service uid="RenderingImage2" type="::vtkSimpleNegato::SRenderer" autoConnect="yes" >
                <in key="image" uid="myImage2" />
            </service>

            <start uid="myFrame" />

        </config>
    </extension>
</plugin>
Filter service

Here, the filter service is inherited from ::fwGui::IActionSrv but you can inherit from another type (like ::arServices::IOperator in fw4spl-ar repository).

For an action, the updating() method is called by the click on the button. This method retrieves the two images and applies the threshold algorithm.

The ::fwData::Image contains a buffer for pixel values, it is stored as a void * to allows several types of pixel (uint8, int8, uint16, int16, double, float ...). To use image buffer, we need to cast it to the image pixel type. For that, we use the Dispatcher : it allows to invoke a template functor according to the image type.

void SThreshold::updating() throw ( ::fwTools::Failed )
{
    SLM_TRACE_FUNC();

    // threshold value: the pixel with the value less than 50 will be set to 0, else the value is set to the maximum
    // value of the image pixel type.
    const double threshold = 50.0;

    ThresholdFilter::Parameter param; // filter parameters: threshold value, image source, image target

    // Get source image
    param.imageIn = this->getInput< ::fwData::Image >("source");
    SLM_ASSERT("'source' key not found", param.imageIn);

    // Get target image
    param.imageOut = this->getInOut< ::fwData::Image >("target");
    SLM_ASSERT("'target' key not found", param.imageOut);

    param.thresholdValue = threshold;

    /*
     * The dispatcher allows to apply the filter on any type of image.
     * It invokes the template functor ThresholdFilter using the image type.
     */
    ::fwTools::DynamicType type = param.imageIn->getPixelType(); // image type

    // Invoke filter functor
    ::fwTools::Dispatcher< ::fwTools::IntrinsicTypes, ThresholdFilter >::invoke( type, param );

    // Notify that the image target is modified
    auto sig = param.imageOut->signal< ::fwData::Object::ModifiedSignalType >(::fwData::Object::s_MODIFIED_SIG);
    {
        ::fwCom::Connection::Blocker block(sig->getConnection(m_slotUpdate));
        sig->asyncEmit();
    }
}

The functor is a structure containing a sub-structure for the parameters (inputs and outputs) and a template method operator(parameters).

/**
 * Functor to apply a threshold filter.
 *
 * The pixel with the value less than the threshold value will be set to 0, else the value is set to the maximum
 * value of the image pixel type.
 *
 * The functor provides a template method operator(param) to apply the filter
 */
struct ThresholdFilter
{
    struct Parameter
    {
        double thresholdValue; ///< threshold value.
        ::fwData::Image::csptr imageIn; ///< image source
        ::fwData::Image::sptr imageOut; ///< image target: contains the result of the filter
    };

    /**
     * @brief Applies the filter
     * @tparam PIXELTYPE image pixel type (uint8, uint16, int8, int16, float, double, ....)
     */
    template<class PIXELTYPE>
    void operator()(Parameter &param)
    {
        const PIXELTYPE thresholdValue = static_cast<PIXELTYPE>(param.thresholdValue);
        ::fwData::Image::csptr imageIn = param.imageIn;
        ::fwData::Image::sptr imageOut = param.imageOut;
        SLM_ASSERT("Sorry, image must be 3D", imageIn->getNumberOfDimensions() == 3 );
        imageOut->copyInformation(imageIn); // Copy image size, type... without copying the buffer
        imageOut->allocate(); // Allocate the image buffer

        ::fwDataTools::helper::ImageGetter imageInHelper(imageIn); // helper used to access the image source buffer
        ::fwDataTools::helper::Image imageOutHelper(imageOut); // helper used to access the image target buffer

        // Get image buffers
        const PIXELTYPE* buffer1 = (PIXELTYPE*)imageInHelper.getBuffer();
        PIXELTYPE* buffer2       = (PIXELTYPE*)imageOutHelper.getBuffer();

        // Get number of pixels
        const size_t NbPixels = imageIn->getSize()[0] * imageIn->getSize()[1] * imageIn->getSize()[2];

        // Fill the target buffer considering the thresholding
        for( size_t i = 0; i<NbPixels; ++i, ++buffer1, ++buffer2 )
        {
            * buffer2 = ( *buffer1 < thresholdValue ) ? 0 : std::numeric_limits<PIXELTYPE>::max();
        }
    }
};

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto06Filter_0-1/profile.xml

[Tuto08GenericScene] Generic scene

This tutorial explains how to use the generic scene.

_images/tuto08GenericScene1.png

Image and mesh

_images/tuto08GenericScene2.png

Mesh with texture

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto08GenericScene )
set( VERSION 0.1 )
set( TYPE APP )
set( UNIQUE TRUE )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io
    ioData # contains reader/writer for mesh (.trian) or matrix (.trf)
    ioVTK
    uiIO
    uiVisuQt # contains several editors for visualization
    uiImageQt # contains several editors on image
    visuVTKQt
    visuVTKAdaptor # contains adaptors for the generic scene
    ctrlSelection # contains services to manage object selection (and associated services)
    launcher
    appXml2
)

bundleParam(appXml2 PARAM_LIST config PARAM_VALUES Tuto08GenericScene)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<!--
    This tutorial shows a VTK scene containing a 3D image and a textured mesh.
    To use this application, you should open a 3D image, a mesh and/or a 2D texture image.
-->
<plugin id="Tuto08GenericScene" version="@DASH_VERSION@">

    <requirement id="dataReg" />
    <requirement id="servicesReg" />
    <requirement id="visuVTKQt" />

    <extension implements="::fwServices::registry::AppConfig2">
        <id>Tuto08GenericScene</id>
        <config>

            <object uid="imageUID" type="::fwData::Image" />
            <object uid="meshUID" type="::fwData::Mesh" />
            <object uid="textureUID" type="::fwData::Image" />

            <service uid="ihm" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>Tuto08GenericScene</name>
                        <icon>@BUNDLE_PREFIX@/Tuto08GenericScene_0-1/tuto.ico</icon>
                    </frame>
                    <menuBar />
                </gui>
                <registry>
                    <menuBar sid="menuBar" start="yes" />
                    <view sid="mainView" start="yes" />
                </registry>
            </service>

            <!-- Status bar used to display the progress bar for reading -->
            <service uid="progressBar" type="::gui::editor::SJobBar" />

            <service uid="menuBar" type="::gui::aspect::SDefaultMenuBar">
                <gui>
                    <layout>
                        <menu name="File" />
                    </layout>
                </gui>
                <registry>
                    <menu sid="menuFile" start="yes" />
                </registry>
            </service>

            <service uid="menuFile" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Open image" shortcut="Ctrl+I" />
                        <menuItem name="Open mesh" shortcut="Ctrl+M" />
                        <menuItem name="Open texture" shortcut="Ctrl+T" />
                        <separator />
                        <menuItem name="Quit" specialAction="QUIT" shortcut="Ctrl+Q" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionOpenImage" start="yes" />
                    <menuItem sid="actionOpenMesh" start="yes" />
                    <menuItem sid="actionOpenTexture" start="yes" />
                    <menuItem sid="actionQuit" start="yes" />
                </registry>
            </service>

            <!-- Actions to call readers -->
            <service uid="actionOpenImage" type="::gui::action::SStarter">
                <start uid="imageReader" />
            </service>
            <service uid="actionOpenMesh" type="::gui::action::SStarter">
                <start uid="meshReader" />
            </service>
            <service uid="actionOpenTexture" type="::gui::action::SStarter">
                <start uid="textureReader" />
            </service>

            <!-- Quit action -->
            <service uid="actionQuit" type="::gui::action::SQuit" />

            <!-- main view -->
            <service uid="mainView" type="::gui::view::SDefaultView">
                <gui>
                    <layout type="::fwGui::CardinalLayoutManager">
                        <view align="center" />
                        <view align="bottom" minWidth="400" minHeight="30" resizable="no" />
                    </layout>
                </gui>
                <registry>
                    <view sid="genericScene" start="yes" />
                    <view sid="editorsView" start="yes" />
                </registry>
            </service>

            <!-- View for editors to update image visualization -->
            <service uid="editorsView" type="::gui::view::SDefaultView">
                <gui>
                    <layout type="::fwGui::LineLayoutManager">
                        <orientation value="horizontal" />
                        <view proportion="0" minWidth="30" />
                        <view proportion="0" minWidth="50" />
                        <view proportion="1" />
                        <view proportion="0" minWidth="30" />
                    </layout>
                </gui>
                <registry>
                    <view sid="sliceListEditor" start="yes" />
                    <view sid="showScanEditor" start="yes" />
                    <view sid="sliderIndexEditor" start="yes" />
                    <view sid="snapshotScene1Editor" start="yes" />
                </registry>
            </service>

            <!--
                Editor used for scene snapshot:
                It allows to select the snapshot filename and emits a "snapped" signal with this path.
            -->
            <service uid="snapshotScene1Editor" type="::uiVisu::SnapshotEditor" />

            <!--
                Generic scene:
                This scene display a 3D image and a textured mesh.
            -->
            <service uid="genericScene" type="::fwRenderVTK::SRender" autoConnect="yes">
                <inout key="image" uid="imageUID" />
                <inout key="mesh" uid="meshUID" />
                <inout key="textureImage" uid="textureUID" />
                <scene>
                    <!-- Image picker -->
                    <picker id="myPicker" vtkclass="fwVtkCellPicker" />
                    <!-- Renderer -->
                    <renderer id="default" background="0.0" />

                    <!-- Mesh adaptor -->
                    <adaptor id="meshAdaptor" class="::visuVTKAdaptor::Mesh" objectId="mesh">
                        <config renderer="default" picker="" uvgen="sphere" texture="textureAdaptor" />
                    </adaptor>

                    <!-- Texture adaptor, used by mesh adaptor -->
                    <adaptor id="textureAdaptor" class="::visuVTKAdaptor::Texture" objectId="textureImage">
                        <config renderer="default" picker="" filtering="linear" wrapping="repeat" />
                    </adaptor>

                    <!-- 3D image negatoscope adaptor -->
                    <adaptor id="imageAdaptor" uid="imageAdaptorUID" class="::visuVTKAdaptor::NegatoMPR" objectId="image">
                        <config renderer="default" picker="myPicker" mode="3d" slices="3" sliceIndex="axial" />
                    </adaptor>

                    <!-- Snapshot adaptor: create a snapshot of the scene. It has a slot "snap" that receives a path -->
                    <adaptor id="snapshotAdaptor" uid="snapshotUID" class="::visuVTKAdaptor::Snapshot" objectId="self">
                        <config renderer="default" />
                    </adaptor>

                    <!--
                        Connection for snapshot:
                        connect the editor signal "snapped" to the adaptor slot "snap"
                    -->
                    <connect>
                        <signal>snapshotScene1Editor/snapped</signal>
                        <slot>snapshotUID/snap</slot>
                    </connect>

                    <!--
                        Connection for 3D image slice:
                        Connect the button (showScanEditor) signal "toggled" to the image adaptor (MPRNegatoScene3D)
                        slot "showSlice", this signals/slots contains a boolean.
                        The image slices will be show or hide when the button is checked/unchecked.

                        The "waitForKey" attribut means that the signal and slot are connected only if the key
                        "image" is present in the scene composite. It is recommanded to used because the adaptors
                        exists only if the object is present.

                    -->
                    <connect>
                        <signal>showScanEditor/toggled</signal>
                        <slot>imageAdaptorUID/showSlice</slot>
                    </connect>

                    <!--
                        Connection for 3D image slice:
                        Connect the menu button (sliceListEditor) signal "selected" to the image adaptor
                        (MPRNegatoScene3D) slot "updateSliceMode", this signals/slots contains an integer.
                        This integer defines the number of slice to show (0, 1 or 3).
                    -->
                    <connect>
                        <signal>sliceListEditor/selected</signal>
                        <slot>imageAdaptorUID/updateSliceMode</slot>
                    </connect>

                </scene>
            </service>


            <!-- *************************************************
                                Displayed objects
                ************************************************* -->

            <!-- Image displayed in the scene -->
            <service uid="imageReader" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="imageUID" />
                <type mode="reader" />
            </service>

            <!--
                Generic editor representing a menu button.
                It send signal with the current selected item.
            -->
            <service uid="sliceListEditor" type="::guiQt::editor::SSelectionMenuButton">
                <toolTip>Manage slice visibility</toolTip><!-- button tooltip -->
                <selected>3</selected><!-- Default selection -->
                <items>
                    <item text="One slice" value="1" /><!-- first item, if selected the emitted value is "1" -->
                    <item text="three slices" value="3" /><!-- second item, if selected the emitted value is "1" -->
                </items>
            </service>
            <!--
                Generic editor representing a simple button with an icon.
                The button can be checkable. In this case it can have a second icon.
                - It emits a signal "clicked" when it is clicked.
                - It emits a signal "toggled" when it is checked/unchecked.

                Here, this editor is used to show or hide the image. It is connected to the image adaptor.
            -->
            <service uid="showScanEditor" type="::guiQt::editor::SSignalButton">
                <config>
                    <checkable>true</checkable>
                    <icon>@BUNDLE_PREFIX@/media_0-1/icons/sliceHide.png</icon>
                    <icon2>@BUNDLE_PREFIX@/media_0-1/icons/sliceShow.png</icon2>
                    <iconWidth>40</iconWidth>
                    <iconHeight>16</iconHeight>
                    <checked>true</checked>
                </config>
            </service>

            <!-- Editor representing a slider to navigate into image slices -->
            <service uid="sliderIndexEditor" type="::uiImage::SliceIndexPositionEditor" autoConnect="yes">
                <inout key="image" uid="imageUID" />
                <sliceIndex>axial</sliceIndex>
            </service>

            <!-- texture reader -->
            <service uid="textureReader" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="textureUID" />
                <type mode="reader" />
            </service>

            <!-- Mesh reader -->
            <service uid="meshReader" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="meshUID" />
                <type mode="reader" />
            </service>

            <!-- Connects readers to status bar service -->
            <connect>
                <signal>meshReader/jobCreated</signal>
                <slot>progressBar/showJob</slot>
            </connect>

            <connect>
                <signal>imageReader/jobCreated</signal>
                <slot>progressBar/showJob</slot>
            </connect>

            <connect>
                <signal>textureReader/jobCreated</signal>
                <slot>progressBar/showJob</slot>
            </connect>

            <!--
                Connects showScanEditor signal "toggled" to sliceListEditor slot "setEnable", this signal and slot
                contains a boolean, so the sliceListEditor can be disabled when the image is not displayed.
            -->
            <connect>
                <signal>showScanEditor/toggled</signal>
                <slot>sliceListEditor/setEnabled</slot>
            </connect>

            <start uid="ihm" />
            <start uid="progressBar" />

        </config>
    </extension>

</plugin>
GUI

This tutorials used multiple editors to manage the image rendering:

  • show/hide image slices
  • navigate between the image slices
  • snapshot
_images/tuto08GenericSceneGUI.png

The two editors (SSelectionMenuButton and SSignalButton) are generic, so we need to configure their behaviour in the xml file.

The editor aspect is defined in the service configuration. They emit signals that must be manually connected to the scene adaptor.

SSelectionMenuButton

This editor displays a menu when the user click on the button. Then the user can select one item.

_images/SSelectionMenuButton.png
<service uid="selectionMenuButton" impl="::uiImage::SSelectionMenuButton">
    <text>...</text>
    <toolTip>...</toolTip>
    <items>
        <item text="One" value="1" />
        <item text="Two" value="2" />
        <item text="Six" value="6" />
    </items>
    <selected>2</selected>
</service>
text (optional, default “>”)
Text displayed on the button
toolTip (optional)
Button tool tip
items
List of the menu items
item

One item

text
The text displayed in the menu
value
The value emitted when the item is selected
selected
The value of the item selected by default

When the user select an item, a signal is emitted: the signal is selected(int selection). It sends the value of the selected item.

In our case, we want to change the number of image slices displayed in the scene. So, we need to connect this signal to the image adaptor slot updateSliceMode(int nbSlice).

<connect>
    <signal>selectionMenuButton/selected</signal>
    <slot>imageAdaptor/updateSliceMode</slot>
</connect>
SSignalButton

This editor shows a simple button.

<service uid="signalButton" impl="::guiQt::editor::SSignalButton" >
    <config>
        <checkable>true|false</checkable>
        <text>...</text>
        <icon>...</icon>
        <text2>...</text2>
        <icon2>...</icon2>
        <checked>true|false</checked>
        <iconWidth>...</iconWidth>
        <iconHeight>...</iconHeight>
    </config>
</service>
text (optional)
Text displayed on the button
icon (optional)
Icon displayed on the button
checkable (optional, default: false)
If true, the button is checkable
text2 (optional)
Text displayed if the button is checked
icon2 (optional)
Icon displayed if the button is checked
checked (optional, default: false)
If true, the button is checked at start
iconWidth (optional)
Icon width
iconHeight (optional)
Icon height

This editor provides two signals:

clicked()
Emitted when the user click on the button.
toggled(bool checked)
Emitted when the button is checked or unchecked.

In our case, we want to show (or hide) the image slices when the button is checked (or unckecked). So, we need to connect the toogled signal to the image adaptor slot showSlice(bool show).

<connect>
    <signal>signalButton/toggled</signal>
    <slot>imageAdaptor/showSlice</slot>
</connect>

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto08GenericScene_0-1/profile.xml