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.

Main VRRender view.

MPR view of a medical 3D image.

3D view of surfacic meshes.

Volume rendering

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.

Mono camera intrinsic calibration.

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.
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¶
Installation for Windows¶
Prerequisites for Windows users¶
If not already installed:
- Install git
- Optionally you can install SourceTree to manage your repositories
- Install Visual Studio 2015 Community
- Install Python 2.7
- Install CMake
- Install jom
- 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.
- Add a sub folder for Debug and Release.
Set the environment for a x64 version. To compile BinPkgs and sources, you must use the ‘VS2015 x64 Native Tools Command Prompt’
Dependencies¶
Warning
Be sure to be in the ‘VS2015 x64 Native Tools Command Prompt’
Clone the following repository in the (BinPkgs) source folder:
Note
Optionnal: You can also clone this extension repository: fw4spl-ext-deps
- Update the cloned repositories to the 11.0.4 tag.
Note
Make sure that CMake executable location is present in your PATH environment variable.
- Call the cmake-gui
- Set the wanted Build directory (e.g. C:\Dev\BinPkgs\Build\Debug)
- 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 (e.g. C:\Dev\BinPkgs\Install\Debug).
- 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 using jom in the console:
- Use “jom all” to compile all the dependencies
- Use “jom name_of_target” to compile only the wanted target
Source¶
Warning
Be sure to be in the ‘VS2015 x64 Native Tools Command Prompt’
Note
- Optionnal: You can also clone these extension repositories:
- fw4spl-ar contains functionalities for augmented reality (video tracking for instance).
- fw4spl-ext contains experimental code.
- fw4spl-ogre contains a 3D backend using Ogre3D.
- Update the cloned repositories to the 11.0.4 tag.
Note
Make sure that CMake executable location is present in your PATH environment variable.
- Call the cmake-gui.
- Set the wanted Build directory (e.g. C:\Dev\Build\Debug)
- During configure step, choose the generator ‘Ninja’ to compile FW4SPL sources.
Note
Make sure that Ninja executable location is present in your PATH environment variable.
Set the following arguments:
- ADDITIONAL_PROJECTS: set the source location of fw4spl-ar, fw4spl-ext and fw4spl-ogre, separated by ”;”.
- CMAKE_INSTALL_PREFIX: set the install location (e.g. C:\Dev\Install\Debug).
- CMAKE_BUILD_TYPE: set to Debug or Release.
- EXTERNAL_LIBRARIES: set the install path of the dependencies install directory (e.g. C:\Dev\BinPkgs\Install\Debug).
- PROJECT_TO_BUILD: set the names of the applications to build (see DevSrcApps or DevSrcSamples, ex: VRRender, Tuto01Basic ...), each project should be separated by ”;”.
- ECLIPSE_PROJECT: check this box if you want to generate an Eclipse project.
- If you want to generate installers:
- PROJECT_TO_INSTALL: set the names of the applications you want 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
- Use “ninja” if you want to compile all the applications set in CMake.
- Use “ninja name_of_application” to compile only one of the applications set in CMake.
Launch an application¶
After a 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%).

Generate an installer¶
- After setting the applications for which you want to generate installers in the PROJECT_TO_INSTALL variable of CMake and generating the code, follow these two steps:
- Run ninja install application_to_install in the Build directory
- Run ninja package in the Build directory
The installer will be generated in the Build directory.
Recommended software¶
The following programs may be helpful for your developments:
- Eclipse CDT: Eclipse is a multi-OS Integrated Development Environment (IDE) for computer programming.
- Notepad++: Notepad++ is a free source code editor, which is designed with syntax highlighting functionality.
- ConsoleZ: ConsoleZ is an alternative command prompt for Windows, adding more capabilities to the default Windows command prompt. To compile FW4SPL with the console the windows command prompt has to be set in the tab settings.
Installation for Linux¶
Prerequisites for Linux users¶
If not already installed:
- Install git
- Install gcc The minimal version required is 4.8 or clang The minimal version required is 3.5
- Install Python 2.7
- 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.
- 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.
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
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.

$ 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 ReleaseEXTERNAL_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

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
fw4spl-ogre: another extension of fw4spl, contains a 3D backend using Ogre3D.
$ cd Dev/Src $ git clone https://github.com/fw4spl-org/fw4spl-ogre.git fw4spl-ogre $ cd fw4spl-ogre $ 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/
Recommended software¶
The following programs may be helpful for your developments:
Installation for MacOSX¶
Prerequisites for MacOSX users¶
If not already installed:
- Install Xcode
- Install git
- Install Python 2.7
- 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.
- 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.
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
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.

$ 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 ReleaseEXTERNAL_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

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
fw4spl-ogre: another extension of fw4spl, contains a 3D backend using Ogre3D.
$ cd Dev/Src $ git clone https://github.com/fw4spl-org/fw4spl-ogre.git fw4spl-ogre $ cd fw4spl-ogre $ 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/
Recommended software¶
The following programs may be helpful for your developments:
- IDE:
- Versioning tools:
Installation for Android¶

This page summarizes the steps to build FW4SPL on Android.
Warning
Work in progress
Android environnement¶
The following tools are necessary:
- JDK >= 6 (JRE only is not sufficient)
- Gradle 2.13 ou more recent
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

- 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.

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.

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”.

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 ReleaseEXTERNAL_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 installCROSS_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

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, thestart
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 thestart
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
andfwService::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 ?
- 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.
- 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()
, orupdate()
or elsewhere, the data is allocated, filled and registered in the OSR :
- 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
::IService::setOutput(const KeyType& key, const ::fwData::Object::sptr& object);
//..
::fwData::Image::sptr image = ::fwData::Image::New();
this->setOutput("outputImage", image);
- 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", &ThisClassHasSlots::sum, this);
newSlot("getValue", &ThisClassHasSlots::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. Unfortunately, this is limited to applications based on one service and one data, and thus it would be impossible to apply to applications containing multiple services and objects.
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) ¤tData ) const;
Wizard¶
Services are available to create/launch activities :
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.
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:

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 keyswapObj(object)
: swap the given object in the composite with the configured keyaddOrSwap(object)
: if the configured key exists in the composote, the object is swapped, else it is addedremove()
: remove the object with the configured key from the compositeremoveIfPresent()
: 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 a 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 diverse visualization tasks.
As used in FW4SPL, the generic scene 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 needs to implement 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 defined 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 slots 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¶
Contents
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 withfwAtoms
, right before converting said data to::fwData::Objects
.- In
::ioAtoms::SWriter
after data is converted tofwAtoms::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:

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.

- 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 aNew()
and converted tofwAtoms
. - 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 thefwAtoms::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 folderinclude/
. 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 insrc/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 forstd
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 ofgetObject()
.
- 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 aI
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
andprivate
are not indented, they should be aligned with the keywordclass
.class Sample { public: ... private: ... };
Class declaration¶
- Recommendation 7 : Only three scope sections
- When possible, use only one section of each scope type
public
,protected
andprivate
. 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()
andsetXXX()
. Getters are alwaysconst
.
- 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
andreinterpret_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.
![]() |
Core, visualization, image processing, applications and tutorials
|
![]() |
Core, visualization, image processing, applications and tutorials Team : Julien Waechter, Emilie Harquel, Jessica GROMER |
![]() |
Proof of concept on MIDAS integration |
![]() |
Team : Nicolas Philipps, Valentin Martinet, Arnaud Charnoz, Julien Weinzorn |
![]() |
|
Libraries¶
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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.
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.
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:
- <object>
- <service>
- <connect> (see [Tuto04SignalSlot] Signal-slot communication)
- <start>
- <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
[Tuto04SignalSlot] Signal-slot communication¶
The fourth tutorial explains the communication mechanism with signals and slots.
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.
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.
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 ¶m)
{
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.
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
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.
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