changeset 565:109963050112 am-dev

Merged default into am-dev
author Alain Mazy <am@osimis.io>
date Thu, 18 Apr 2019 10:43:17 +0200
parents 9ea4b57a9ed8 (current diff) 37e396ae08a3 (diff)
children ccd1a1ede305
files Applications/Commands/BaseCommandBuilder.cpp Applications/Commands/BaseCommandBuilder.h Applications/Commands/ICommand.h Applications/Commands/ICommandBuilder.h Applications/Commands/ICommandExecutor.h Resources/CMake/OrthancStoneConfiguration.cmake Resources/CodeGeneration/template.in.h Resources/CodeGeneration/template.in.ts
diffstat 145 files changed, 5901 insertions(+), 1837 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Apr 18 10:42:01 2019 +0200
+++ b/.hgignore	Thu Apr 18 10:43:17 2019 +0200
@@ -1,7 +1,13 @@
 CMakeLists.txt.user
 Platforms/Generic/ThirdPartyDownloads/
+Applications/build-*
 Applications/Qt/archive/
 Applications/Samples/ThirdPartyDownloads/
+Applications/Samples/rt-viewer-demo/build-sdl-msvc15/
+Applications/Samples/rt-viewer-demo/build-tsc-output/
+Applications/Samples/rt-viewer-demo/build-wasm/
+Applications/Samples/rt-viewer-demo/build-web/
+Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/
 Applications/Samples/build-wasm/
 Applications/Samples/build-web/
 Applications/Samples/node_modules/
@@ -19,3 +25,4 @@
 Resources/CodeGeneration/testWasmIntegrated/build-wasm/
 Resources/CodeGeneration/testWasmIntegrated/build-tsc/
 Resources/CodeGeneration/testWasmIntegrated/build-final/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,1 @@
+90f3a60576a9f08dcf783752a7f67ce0615a5371 rtviewer19
--- a/Applications/Commands/BaseCommandBuilder.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#include "BaseCommandBuilder.h"
-#include "Core/OrthancException.h"
-#include <iostream>
-#include "Framework/StoneException.h"
-
-namespace OrthancStone
-{
-  ICommand* BaseCommandBuilder::CreateFromJson(const Json::Value& commandJson)
-  {
-    if (!commandJson.isObject() || !commandJson["command"].isString())
-    {
-      throw StoneException(ErrorCode_CommandJsonInvalidFormat);
-    }
-
-    if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "generic-no-arg-command")
-    {
-        printf("creating a simple command\n");
-        return new GenericNoArgCommand(commandJson["command"].asString().c_str());
-    }
-    else if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "generic-one-string-arg-command")
-    {
-        printf("creating a simple command\n");
-        return new GenericNoArgCommand(commandJson["command"].asString().c_str());
-    }
-
-    return NULL;
-  }
-
-}
--- a/Applications/Commands/BaseCommandBuilder.h	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include <map>
-#include <memory>
-
-#include "ICommand.h"
-#include "../../Applications/Commands/ICommandBuilder.h"
-
-// TODO: must be reworked completely (check trello)
-
-namespace OrthancStone
-{
-  class BaseCommandBuilder : public ICommandBuilder
-  {
-  public:
-    virtual ICommand* CreateFromJson(const Json::Value& commandJson);
-  };
-}
--- a/Applications/Commands/ICommand.h	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <json/json.h>
-
-// TODO: must be reworked completely (check trello)
-
-namespace OrthancStone
-{
-  class ICommand  // TODO noncopyable
-  {
-  protected:
-    std::string name_;
-    ICommand(const std::string& name)
-      : name_(name)
-    {}
-  public:
-    virtual ~ICommand() 
-    {}
-    virtual void Execute() = 0;
-//    virtual void Configure(const Json::Value& arguments) = 0;
-    const std::string& GetName() const
-    {
-      return name_;
-    }
-  };
-
-
-  template <typename TCommand>
-  class BaseCommand : public ICommand
-  {
-  protected:
-    BaseCommand(const std::string& name)
-      : ICommand(name)
-    {}
-
-  public:
-    static ICommand* Create() {
-      return new TCommand();
-    }
-
-    virtual void Configure(const Json::Value& arguments) {
-    }
-  };
-
-  class NoopCommand : public BaseCommand<NoopCommand>
-  {
-  public:
-    NoopCommand()
-      : BaseCommand("noop")
-    {}
-    virtual void Execute() {}
-  };
-
-  class GenericNoArgCommand : public BaseCommand<GenericNoArgCommand>
-  {
-  public:
-    GenericNoArgCommand(const std::string& name)
-      : BaseCommand(name)
-    {}
-    virtual void Execute() {} // TODO currently not used but this is not nice at all !
-  };
-
-  class GenericOneStringArgCommand : public BaseCommand<GenericOneStringArgCommand>
-  {
-    std::string argument_;
-  public:
-    GenericOneStringArgCommand(const std::string& name, const std::string& argument)
-      : BaseCommand(name)
-    {}
-
-    const std::string& GetArgument() const {return argument_;}
-    virtual void Execute() {} // TODO currently not used but this is not nice at all !
-  };
-
-}
--- a/Applications/Commands/ICommandBuilder.h	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <boost/noncopyable.hpp>
-#include <json/json.h>
-
-#include "ICommand.h"
-
-namespace OrthancStone
-{
-
-  class ICommandBuilder : public boost::noncopyable
-  {
-  public:
-    virtual ICommand* CreateFromJson(const Json::Value& commandJson) = 0;
-  };
-}
--- a/Applications/Commands/ICommandExecutor.h	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <boost/noncopyable.hpp>
-
-namespace OrthancStone
-{
-  class ICommandExecutor : public boost::noncopyable
-  {
-
-  };
-}
--- a/Applications/Generic/NativeStoneApplicationContext.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Generic/NativeStoneApplicationContext.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -51,7 +51,7 @@
     stopped_(true),
     updateDelayInMs_(100)   // By default, 100ms between each refresh of the content
   {
-    srand(time(NULL)); 
+    srand(static_cast<unsigned int>(time(NULL))); 
   }
 
 
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Generic/NativeStoneApplicationRunner.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -141,6 +141,13 @@
     if (parameters.count("verbose"))
     {
       Orthanc::Logging::EnableInfoLevel(true);
+      LOG(INFO) << "Verbose logs are enabled";
+    }
+
+    if (parameters.count("trace"))
+    {
+      Orthanc::Logging::EnableTraceLevel(true);
+      VLOG(1) << "Trace logs are enabled";
     }
 
     ParseCommandLineOptions(parameters);
--- a/Applications/IStoneApplication.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/IStoneApplication.h	Thu Apr 18 10:43:17 2019 +0200
@@ -25,9 +25,6 @@
 #include <boost/program_options.hpp>
 #include "../Framework/Viewport/WidgetViewport.h"
 #include "json/json.h"
-#include "Commands/ICommand.h"
-#include "Commands/BaseCommandBuilder.h"
-
 
 namespace OrthancStone
 {
@@ -52,6 +49,12 @@
     virtual void Initialize(StoneApplicationContext* context,
                             IStatusBar& statusBar,
                             const boost::program_options::variables_map& parameters) = 0;
+
+    /**
+      This method is meant to process messages received from the outside world (i.e. GUI)
+    */
+    virtual void HandleSerializedMessage(const char* data) = 0;
+
 #if ORTHANC_ENABLE_WASM==1
     virtual void InitializeWasm() {}  // specific initialization when the app is running in WebAssembly.  This is called after the other Initialize()
 #endif
@@ -61,14 +64,6 @@
 
     virtual std::string GetTitle() const = 0;
     virtual IWidget* GetCentralWidget() = 0;
-
     virtual void Finalize() = 0;
-
-    virtual BaseCommandBuilder& GetCommandBuilder() = 0;
-
-    virtual void ExecuteCommand(ICommand& command)
-    {
-    }
   };
-
 }
--- a/Applications/Qt/QCairoWidget.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Qt/QCairoWidget.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -125,7 +125,7 @@
 
   {
     OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_);
-    locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers);
+    locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers, std::vector<OrthancStone::Touch>());
   }
 }
 
@@ -140,7 +140,7 @@
 void QCairoWidget::mouseMoveEvent(QMouseEvent* event)
 {
   OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_);
-  locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y());
+  locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y(), std::vector<OrthancStone::Touch>());
 }
 
 
--- a/Applications/Samples/CMakeLists.txt	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/CMakeLists.txt	Thu Apr 18 10:43:17 2019 +0200
@@ -1,53 +1,70 @@
-# Usage: see README file
+# Usage (Linux):
+# to build the WASM samples
+# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON
+# to build the Qt samples
 
 cmake_minimum_required(VERSION 2.8.3)
-
-# Automatically link Qt executables to qtmain target on Windows
-# ("OLD" == do not link)
-if(POLICY CMP0020)
-  cmake_policy(SET CMP0020 OLD)
-endif()
-
-# Only interpret if() arguments as variables or keywords when unquoted.
-# NEW = do NOT dereference *quoted* variables
-if(POLICY CMP0054)
-  cmake_policy(SET CMP0054 NEW)
-endif()
-
 project(OrthancStone)
 
 include(../../Resources/CMake/OrthancStoneParameters.cmake)
 
+if (OPENSSL_NO_CAPIENG)
+add_definitions(-DOPENSSL_NO_CAPIENG=1)
+endif()
+
+
+# the following block has been borrowed from orthanc/**/Compiler.cmake
+if (MSVC_MULTIPLE_PROCESSES)
+# "If you omit the processMax argument in the /MP option, the
+# compiler obtains the number of effective processors from the
+# operating system, and then creates one process per effective
+# processor"
+# https://blog.kitware.com/cmake-building-with-all-your-cores/
+# https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
+endif()
+
+
 #set(ENABLE_DCMTK ON)
 
 set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application")
 set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application")
 set(ENABLE_WASM OFF CACHE BOOL "Target WASM application")
 
-# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE
-
 if (ENABLE_WASM)
   #####################################################################
   ## Configuration of the Emscripten compiler for WebAssembly target
   #####################################################################
 
-  set(WASM_FLAGS "-s WASM=1 -O0 -g0")
-  message("*****************************************************************************")
-  message("WARNING: optimizations are disabled in emcc!!! Enable them for production use")
-  message("*****************************************************************************")
+  set(WASM_FLAGS "-s WASM=1")
+  set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options
+  set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined
+  set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching 
+  set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1")
+
+  if (CMAKE_BUILD_TYPE MATCHES DEBUG)
+    set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information
+    set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks
+  else()
+    set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size)
+  endif()
+
   set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module")
+
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js  -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
 
-  # Handling of memory
-  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")  # Resize
-  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")  # 512MB
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000")  # 512MB + resize
-  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824")  # 1GB + resize
-
-  # To debug exceptions
-  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}")  # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too
+  # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000")
 
   add_definitions(-DORTHANC_ENABLE_WASM=1)
   set(ORTHANC_SANDBOXED ON)
@@ -80,10 +97,12 @@
 set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
 set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
 
+
 #####################################################################
 ## Build a static library containing the Orthanc Stone framework
 #####################################################################
 
+
 LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options)
 
 include(../../Resources/CMake/OrthancStoneConfiguration.cmake)
@@ -145,16 +164,6 @@
     )
   set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
   target_link_libraries(${Target} OrthancStone)
-  
-  if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows"))
-    message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true")
-    add_custom_command(
-      TARGET ${Target} POST_BUILD
-      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:${Target}>
-      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:${Target}>
-      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:${Target}>
-    )
-  endif()
 endmacro()
 
 #BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1)
@@ -171,12 +180,6 @@
 
 if (ENABLE_QT OR ENABLE_WASM)
 
-      # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs")
-
-      list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES})
-      message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}")
-      message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}")
-
     if (ENABLE_QT)
       list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES
         ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp
@@ -196,16 +199,20 @@
         list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES
             ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp
             ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp
+            ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h
             ${STONE_WASM_SOURCES}
           )
     endif()
 
     add_executable(OrthancStoneSimpleViewer
-      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp
-      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp
-      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.h
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.h
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.h
       ${SIMPLE_VIEWER_APPLICATION_SOURCES}
       )
     target_link_libraries(OrthancStoneSimpleViewer OrthancStone)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/CMakeLists.txt.old	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,248 @@
+# Usage: see README file
+
+cmake_minimum_required(VERSION 2.8.3)
+
+# Automatically link Qt executables to qtmain target on Windows
+# ("OLD" == do not link)
+if(POLICY CMP0020)
+  cmake_policy(SET CMP0020 OLD)
+endif()
+
+# Only interpret if() arguments as variables or keywords when unquoted.
+# NEW = do NOT dereference *quoted* variables
+if(POLICY CMP0054)
+  cmake_policy(SET CMP0054 NEW)
+endif()
+
+project(OrthancStone)
+
+include(../../Resources/CMake/OrthancStoneParameters.cmake)
+
+#set(ENABLE_DCMTK ON)
+
+set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application")
+set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application")
+set(ENABLE_WASM OFF CACHE BOOL "Target WASM application")
+
+# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE
+
+if (ENABLE_WASM)
+  #####################################################################
+  ## Configuration of the Emscripten compiler for WebAssembly target
+  #####################################################################
+
+  set(WASM_FLAGS "-s WASM=1 -O0 -g0")
+  message("*****************************************************************************")
+  message("WARNING: optimizations are disabled in emcc!!! Enable them for production use")
+  message("*****************************************************************************")
+  set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module")
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js  -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+
+  # Handling of memory
+  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")  # Resize
+  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")  # 512MB
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000")  # 512MB + resize
+  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824")  # 1GB + resize
+
+  # To debug exceptions
+  #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2")
+
+  add_definitions(-DORTHANC_ENABLE_WASM=1)
+  set(ORTHANC_SANDBOXED ON)
+
+elseif (ENABLE_QT OR ENABLE_SDL)
+
+  set(ENABLE_NATIVE ON)
+  set(ORTHANC_SANDBOXED OFF)
+  set(ENABLE_CRYPTO_OPTIONS ON)
+  set(ENABLE_GOOGLE_TEST ON)
+  set(ENABLE_WEB_CLIENT ON)
+
+endif()
+
+#####################################################################
+## Configuration for Orthanc
+#####################################################################
+
+# include(../../Resources/CMake/Version.cmake)
+
+if (ORTHANC_STONE_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_VERSION "1.4.1")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
+
+#####################################################################
+## Build a static library containing the Orthanc Stone framework
+#####################################################################
+
+LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options)
+
+include(../../Resources/CMake/OrthancStoneConfiguration.cmake)
+
+add_library(OrthancStone STATIC
+  ${ORTHANC_STONE_SOURCES}
+  )
+
+#####################################################################
+## Build all the sample applications
+#####################################################################
+
+include_directories(${ORTHANC_STONE_ROOT})
+
+# files common to all samples
+list(APPEND SAMPLE_APPLICATIONS_SOURCES
+  ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h
+  ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h
+  )
+
+if (ENABLE_QT)
+  list(APPEND SAMPLE_APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp
+    )
+
+  ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.ui
+    )
+
+  ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h
+    ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.h
+    )
+endif()
+
+if (ENABLE_NATIVE)
+  list(APPEND SAMPLE_APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp
+    )
+
+elseif (ENABLE_WASM)
+
+  list(APPEND SAMPLE_APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp
+    ${STONE_WASM_SOURCES}
+    )
+endif()
+
+
+macro(BuildSingleFileSample Target Header Sample)
+  add_executable(${Target}
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header}
+    ${SAMPLE_APPLICATIONS_SOURCES}
+    )
+  set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
+  target_link_libraries(${Target} OrthancStone)
+  
+  if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows"))
+    message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true")
+    add_custom_command(
+      TARGET ${Target} POST_BUILD
+      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:${Target}>
+      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:${Target}>
+      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:${Target}>
+    )
+  endif()
+endmacro()
+
+#BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1)
+#BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2)
+BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3)
+#BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4)
+#BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5)
+#BuildSingleFileSample(OrthancStoneSynchronizedSeries 6)
+#BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7)
+BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8)  # we keep that one just as a sample before we convert another sample to this pattern
+BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9)
+
+##### SimpleViewer sample (Qt and WASM only) #######
+
+if (ENABLE_QT OR ENABLE_WASM)
+
+      # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs")
+
+      list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES})
+      message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}")
+      message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}")
+
+    if (ENABLE_QT)
+      list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES
+        ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp
+        ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui
+        ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/mainQt.cpp
+        )
+
+      ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES
+        ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui
+        )
+
+      ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES
+        ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h
+        )
+
+elseif (ENABLE_WASM)
+        list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES
+            ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp
+            ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp
+            ${STONE_WASM_SOURCES}
+          )
+    endif()
+
+    add_executable(OrthancStoneSimpleViewer
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h
+      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h
+      ${SIMPLE_VIEWER_APPLICATION_SOURCES}
+      )
+    target_link_libraries(OrthancStoneSimpleViewer OrthancStone)
+
+endif()
+
+#####################################################################
+## Build the unit tests
+#####################################################################
+
+if (ENABLE_NATIVE)
+  add_executable(UnitTests
+    ${GOOGLE_TEST_SOURCES}
+    ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp
+    ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp
+    ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
+    ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
+    )
+
+  target_link_libraries(UnitTests OrthancStone)
+endif()
+
+#####################################################################
+## Generate the documentation if Doxygen is present
+#####################################################################
+
+find_package(Doxygen)
+if (DOXYGEN_FOUND)
+  configure_file(
+    ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen
+    ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen
+    @ONLY)
+
+  add_custom_target(doc
+    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen
+    COMMENT "Generating documentation with Doxygen" VERBATIM
+    )
+else()
+  message("Doxygen not found. The documentation will not be built.")
+endif()
--- a/Applications/Samples/SampleApplicationBase.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SampleApplicationBase.h	Thu Apr 18 10:43:17 2019 +0200
@@ -41,30 +41,35 @@
     class SampleApplicationBase : public IStoneApplication
     {
     protected:
-      BaseCommandBuilder commandBuilder_;
-      WorldSceneWidget*  mainWidget_;   // ownership is transfered to the application context
+      // ownership is transferred to the application context
+      WorldSceneWidget*  mainWidget_;
 
     public:
       virtual void Initialize(StoneApplicationContext* context,
                               IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
+                              const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE
       {
       }
 
-      virtual std::string GetTitle() const
+      virtual std::string GetTitle() const ORTHANC_OVERRIDE
       {
         return "Stone of Orthanc - Sample";
       }
 
-      virtual BaseCommandBuilder& GetCommandBuilder() {return commandBuilder_;}
+      /**
+       * In the basic samples, the commands are handled by the platform adapter and NOT
+       * by the application handler
+      */
+      virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {};
 
-      virtual void Finalize() {}
-      virtual IWidget* GetCentralWidget() {return mainWidget_;}
+
+      virtual void Finalize() ORTHANC_OVERRIDE {}
+      virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;}
 
 #if ORTHANC_ENABLE_WASM==1
       // default implementations for a single canvas named "canvas" in the HTML and an emtpy WasmApplicationAdapter
 
-      virtual void InitializeWasm()
+      virtual void InitializeWasm() ORTHANC_OVERRIDE
       {
         AttachWidgetToWasmViewport("canvas", mainWidget_);
       }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Samples-status.md	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,103 @@
+Executable versions
+================
+Generic options
+----------------------
+```
+("help", "Display this help and exit")
+("verbose", "Be verbose in logs")
+("orthanc", boost::program_options::value<std::string>()
+  ->default_value("http://localhost:8042/"),
+  "URL to the Orthanc server")
+("username", "Username for the Orthanc server")
+("password", "Password for the Orthanc server")
+("https-verify", boost::program_options::value<bool>()
+  ->default_value(true), "Check HTTPS certificates")
+```
+OrthancStoneSimpleViewer
+-------------------------------------
+- Options:
+    ```
+    - "studyId", std::string, "Orthanc ID of the study"
+    ```
+- study loading works OK
+- Invert does not work:
+```
+void SimpleViewerApplication::ExecuteAction(SimpleViewerApplication::Actions action)
+  {
+    // TODO
+  }
+```
+
+OrthancStoneSimpleViewerSingleFile
+-------------------------------------
+- Options:
+    ```
+    - "studyId", std::string, "Orthanc ID of the study"
+    ```
+
+Study loading works.
+
+The `line` and `circle` buttons work and call this:
+```
+virtual void OnTool1Clicked()
+{
+  currentTool_ = Tools_LineMeasure;
+}
+
+virtual void OnTool2Clicked()
+{
+  currentTool_ = Tools_CircleMeasure;
+}
+```
+The `action1` and `action2` buttons are not connected
+
+The following is displayed in the console at launch time:
+```
+W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "s" to reinitialize the layout
+W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "n" to go to next image in the main viewport
+```
+However, when looking at `MainWidgetInteractor::KeyPressed` (`SimpleViewerApplicationSingleFile.h:169`), only the following is processed:
+- 's': reset layout
+- 'l': select line tool
+- 'c': select circle tool
+
+OrthancStoneSingleFrame
+-------------------------------------
+```
+generic.add_options()
+("instance", boost::program_options::value<std::string>(), 
+"Orthanc ID of the instance")
+("frame", boost::program_options::value<unsigned int>()
+  ->default_value(0),
+"Number of the frame, for multi-frame DICOM instances")
+("smooth", boost::program_options::value<bool>()
+  ->default_value(true), 
+"Enable bilinear interpolation to smooth the image");
+```
+only key handled in `KeyPressed` is `s` to call `widget.FitContent()`
+
+
+OrthancStoneSingleFrameEditor
+-------------------------------------
+```
+generic.add_options()
+("instance", boost::program_options::value<std::string>(),
+"Orthanc ID of the instance")
+("frame", boost::program_options::value<unsigned int>()
+  ->default_value(0),
+"Number of the frame, for multi-frame DICOM instances");
+```
+Available commands in `KeyPressed` (`SingleFrameEditorApplication.h:280`): 
+- 'a' widget.FitContent()
+- 'c' Crop tool
+- 'm' Mask tool
+- 'd' dump to json and diplay result (?)
+- 'e' export current view to Dicom with dummy tags (?)
+- 'i' wdiget.SwitchInvert
+- 't' Move tool
+- 'n' switch between nearest and bilinear interpolation
+- 'r' Rotate tool
+- 's' Resize tool
+- 'w' Windowing tool
+- 'ctrl+y' redo
+- 'ctrl+z' undo
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -32,37 +32,37 @@
                                                                     int viewportY,
                                                                     double x,
                                                                     double y,
-                                                                    IStatusBar* statusBar)
+                                                                    IStatusBar* statusBar,
+                                                                    const std::vector<Touch>& displayTouches)
   {
     if (button == MouseButton_Left)
     {
-      if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_LineMeasure)
+      if (application_.GetCurrentTool() == Tool_LineMeasure)
       {
         return new LineMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                       x, y, 255, 0, 0, application_.GetFont());
       }
-      else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_CircleMeasure)
+      else if (application_.GetCurrentTool() == Tool_CircleMeasure)
       {
         return new CircleMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                         x, y, 255, 0, 0, application_.GetFont());
       }
-      else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Crop)
+      else if (application_.GetCurrentTool() == Tool_Crop)
       {
         // TODO
       }
-      else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Windowing)
+      else if (application_.GetCurrentTool() == Tool_Windowing)
       {
         // TODO
       }
-      else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Zoom)
+      else if (application_.GetCurrentTool() == Tool_Zoom)
       {
         // TODO
       }
-      else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Pan)
+      else if (application_.GetCurrentTool() == Tool_Pan)
       {
         // TODO
       }
-
     }
     return NULL;
   }
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h	Thu Apr 18 10:43:17 2019 +0200
@@ -50,7 +50,8 @@
                                                         int viewportY,
                                                         double x,
                                                         double y,
-                                                        IStatusBar* statusBar);
+                                                        IStatusBar* statusBar,
+                                                        const std::vector<Touch>& displayTouches);
 
     virtual void MouseOver(CairoContext& context,
                            WorldSceneWidget& widget,
--- a/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -27,8 +27,15 @@
 #include <ui_SimpleViewerMainWindow.h>
 #include "../SimpleViewerApplication.h"
 
+
 namespace SimpleViewer
 {
+  template<typename T, typename U>
+  bool ExecuteCommand(U* handler, const T& command)
+  {
+    std::string serializedCommand = StoneSerialize(command);
+    StoneDispatchToHandler(serializedCommand, handler);
+  }
 
   SimpleViewerMainWindow::SimpleViewerMainWindow(
     OrthancStone::NativeStoneApplicationContext& context,
@@ -67,43 +74,36 @@
 
   void SimpleViewerMainWindow::cropClicked()
   {
-    GenericNoArgCommand command("selectTool:crop");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(SelectTool(Tool_Crop));
   }
 
   void SimpleViewerMainWindow::undoCropClicked()
   {
-    GenericNoArgCommand command("action:undo-crop");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(Action(ActionType_UndoCrop));
   }
 
   void SimpleViewerMainWindow::lineClicked()
   {
-    GenericNoArgCommand command("selectTool:line-measure");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(SelectTool(Tool_LineMeasure));
   }
 
   void SimpleViewerMainWindow::circleClicked()
   {
-    GenericNoArgCommand command("selectTool:circle-measure");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(SelectTool(Tool_CircleMeasure));
   }
 
   void SimpleViewerMainWindow::windowingClicked()
   {
-    GenericNoArgCommand command("selectTool:windowing");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(SelectTool(Tool_Windowing));
   }
 
   void SimpleViewerMainWindow::rotateClicked()
   {
-    GenericNoArgCommand command("action:rotate");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(Action(ActionType_Rotate));
   }
 
   void SimpleViewerMainWindow::invertClicked()
   {
-    GenericNoArgCommand command("action:invert");
-    stoneApplication_.ExecuteCommand(command);
+    stoneApplication_.ExecuteCommand(Action(ActionType_Invert));
   }
 }
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -153,13 +153,16 @@
   void SimpleViewerApplication::LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
   {
     LOG(INFO) << "Loading thumbnail for series " << seriesId;
+    
     SliceViewerWidget* thumbnailWidget = 
       new SliceViewerWidget(IObserver::GetBroker(), "thumbnail-series-" + seriesId);
     thumbnails_.push_back(thumbnailWidget);
     thumbnailsLayout_->AddWidget(thumbnailWidget);
+    
     thumbnailWidget->RegisterObserverCallback(
       new Callable<SimpleViewerApplication, SliceViewerWidget::GeometryChangedMessage>
       (*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
+    
     smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0);
     thumbnailWidget->SetInteractor(*thumbnailInteractor_);
   }
@@ -180,53 +183,29 @@
     smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
   }
 
-
-
-  void SimpleViewerApplication::ExecuteCommand(ICommand& command)
+  bool SimpleViewerApplication::Handle(const StoneSampleCommands::SelectTool& value)
   {
-    statusBar_->SetMessage("received command: " + std::string(command.GetName()));
-    if (command.GetName() == "selectTool:circle-measure")
-    {
-      SelectTool(Tools_CircleMeasure);
-    }
-    else if (command.GetName() == "selectTool:line-measure")
-    {
-      SelectTool(Tools_LineMeasure);
-    }
-    else if (command.GetName() == "selectTool:crop")
-    {
-      SelectTool(Tools_Crop);
-    }
-    else if (command.GetName() == "selectTool:windowing")
-    {
-      SelectTool(Tools_Windowing);
-    }
-    else if (command.GetName() == "action:rotate")
-    {
-      ExecuteAction(Actions_Rotate);
-    }
-    else if (command.GetName() == "action:undo-crop")
-    {
-      ExecuteAction(Actions_UndoCrop);
-    }
-    else if (command.GetName() == "action:invert")
-    {
-      ExecuteAction(Actions_Invert);
-    }
-    else
-    {
-      command.Execute();
-    }
+    currentTool_ = value.tool;
+    return true;
   }
 
-  void SimpleViewerApplication::ExecuteAction(SimpleViewerApplication::Actions action)
+  bool SimpleViewerApplication::Handle(const StoneSampleCommands::Action& value)
   {
-    // TODO
-  }
-
-  void SimpleViewerApplication::SelectTool(SimpleViewerApplication::Tools tool)
-  {
-    currentTool_ = tool;
+    switch (value.type)
+    {
+    case ActionType_Invert:
+      // TODO
+      break;
+    case ActionType_UndoCrop:
+      // TODO
+      break;
+    case ActionType_Rotate:
+      // TODO
+      break;
+    default:
+      throw std::runtime_error("Action type not supported");
+    }
+    return true;
   }
 
 #if ORTHANC_ENABLE_QT==1
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Thu Apr 18 10:43:17 2019 +0200
@@ -21,6 +21,12 @@
 
 #pragma once
 
+ /*
+ This header contains the command definitions for the sample applications
+ */
+#include "Applications/Samples/StoneSampleCommands_generated.hpp"
+using namespace StoneSampleCommands;
+
 #include "Applications/IStoneApplication.h"
 
 #include "Framework/Layers/CircleMeasureTracker.h"
@@ -53,10 +59,11 @@
 namespace SimpleViewer
 {
 
-  class SimpleViewerApplication :
-      public IStoneApplication,
-      public IObserver,
-      public IObservable
+  class SimpleViewerApplication
+    : public IStoneApplication
+    , public IObserver
+    , public IObservable
+    , public StoneSampleCommands::IHandler
   {
   public:
 
@@ -71,23 +78,9 @@
       }
     };
 
-    enum Tools {
-      Tools_LineMeasure,
-      Tools_CircleMeasure,
-      Tools_Crop,
-      Tools_Windowing,
-      Tools_Zoom,
-      Tools_Pan
-    };
+  private:
+    Tool                                currentTool_;
 
-    enum Actions {
-      Actions_Rotate,
-      Actions_Invert,
-      Actions_UndoCrop
-    };
-
-  private:
-    Tools                               currentTool_;
     std::auto_ptr<MainWidgetInteractor> mainWidgetInteractor_;
     std::auto_ptr<ThumbnailInteractor>  thumbnailInteractor_;
     LayoutWidget*                       mainLayout_;
@@ -96,8 +89,6 @@
     std::vector<SliceViewerWidget*>     thumbnails_;
     std::map<std::string, std::vector<std::string> > instancesIdsPerSeriesId_;
     std::map<std::string, Json::Value>  seriesTags_;
-    BaseCommandBuilder                  commandBuilder_;
-
     unsigned int                        currentInstanceIndex_;
     OrthancStone::WidgetViewport*       wasmViewport1_;
     OrthancStone::WidgetViewport*       wasmViewport2_;
@@ -111,7 +102,7 @@
     SimpleViewerApplication(MessageBroker& broker) :
       IObserver(broker),
       IObservable(broker),
-      currentTool_(Tools_LineMeasure),
+      currentTool_(StoneSampleCommands::Tool_LineMeasure),
       mainLayout_(NULL),
       currentInstanceIndex_(0),
       wasmViewport1_(NULL),
@@ -120,13 +111,13 @@
       font_.LoadFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
     }
 
-    virtual void Finalize() {}
-    virtual IWidget* GetCentralWidget() {return mainLayout_;}
+    virtual void Finalize() ORTHANC_OVERRIDE {}
+    virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainLayout_;}
 
-    virtual void DeclareStartupOptions(boost::program_options::options_description& options);
+    virtual void DeclareStartupOptions(boost::program_options::options_description& options) ORTHANC_OVERRIDE;
     virtual void Initialize(StoneApplicationContext* context,
                             IStatusBar& statusBar,
-                            const boost::program_options::variables_map& parameters);
+                            const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE;
 
     void OnStudyListReceived(const OrthancApiClient::JsonResponseReadyMessage& message);
 
@@ -142,9 +133,8 @@
 
     void SelectSeriesInMainViewport(const std::string& seriesId);
 
-    void SelectTool(Tools tool);
-    
-    Tools GetCurrentTool() const
+
+    Tool GetCurrentTool() const
     {
       return currentTool_;
     }
@@ -154,15 +144,26 @@
       return font_;
     }
 
-    void ExecuteAction(Actions action);
+    // ExecuteAction method was empty (its body was a single "TODO" comment)
+    virtual bool Handle(const SelectTool& value) ORTHANC_OVERRIDE;
+    virtual bool Handle(const Action& value) ORTHANC_OVERRIDE;
 
-    virtual std::string GetTitle() const {return "SimpleViewer";}
-    virtual void ExecuteCommand(ICommand& command);
-    virtual BaseCommandBuilder& GetCommandBuilder() {return commandBuilder_;}
+    template<typename T>
+    bool ExecuteCommand(const T& cmd)
+    {
+      std::string cmdStr = StoneSampleCommands::StoneSerialize(cmd);
+      return StoneSampleCommands::StoneDispatchToHandler(cmdStr, this);
+    }
 
+    virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE
+    {
+      StoneSampleCommands::StoneDispatchToHandler(data, this);
+    }
+
+    virtual std::string GetTitle() const ORTHANC_OVERRIDE {return "SimpleViewer";}
 
 #if ORTHANC_ENABLE_WASM==1
-    virtual void InitializeWasm();
+    virtual void InitializeWasm() ORTHANC_OVERRIDE;
 #endif
 
 #if ORTHANC_ENABLE_QT==1
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -32,7 +32,8 @@
                                                                    int viewportY,
                                                                    double x,
                                                                    double y,
-                                                                   IStatusBar* statusBar)
+                                                                   IStatusBar* statusBar,
+                                                                   const std::vector<Touch>& displayTouches)
   {
     if (button == MouseButton_Left)
     {
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.h	Thu Apr 18 10:43:17 2019 +0200
@@ -47,7 +47,8 @@
                                                         int viewportY,
                                                         double x,
                                                         double y,
-                                                        IStatusBar* statusBar);
+                                                        IStatusBar* statusBar,
+                                                        const std::vector<Touch>& displayTouches);
 
     virtual void MouseOver(CairoContext& context,
                            WorldSceneWidget& widget,
--- a/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -45,7 +45,7 @@
 
     writer->write(event, &outputStr);
 
-    NotifyStatusUpdateFromCppToWeb(outputStr.str());
+    NotifyStatusUpdateFromCppToWebWithString(outputStr.str());
   }
 
 } // namespace SimpleViewer
\ No newline at end of file
--- a/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -9,7 +9,7 @@
     args: {
     }                                                                                                                       
   };
-  wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command));
+  wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command));
 }
 
 function PerformAction(actionName: string) {
@@ -19,7 +19,7 @@
     args: {
     }
   };
-  wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command));
+  wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command));
 }
 
 class SimpleViewerUI {
@@ -59,8 +59,8 @@
 
 // this method is called "from the C++ code" when the StoneApplication is updated.
 // it can be used to update the UI of the application
-function UpdateWebApplication(statusUpdateMessageString: string) {
-  console.log("updating web application: ", statusUpdateMessageString);
+function UpdateWebApplicationWithString(statusUpdateMessageString: string) {
+  console.log("updating web application with string: ", statusUpdateMessageString);
   let statusUpdateMessage = JSON.parse(statusUpdateMessageString);
 
   if ("event" in statusUpdateMessage) {
@@ -70,3 +70,12 @@
     }
   }
 }
+
+function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) {
+  console.log("updating web application with serialized message: ", statusUpdateMessageString);
+  console.log("<not supported in the simple viewer!>");
+}
+
+// make it available to other js scripts in the application
+(<any> window).UpdateWebApplicationWithString = UpdateWebApplicationWithString;
+(<any> window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage;
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h	Thu Apr 18 10:43:17 2019 +0200
@@ -66,7 +66,8 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar)
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& displayTouches)
         {
           if (button == MouseButton_Left)
           {
@@ -121,16 +122,17 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar)
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& displayTouches)
         {
           if (button == MouseButton_Left)
           {
-            if (application_.currentTool_ == Tools_LineMeasure)
+            if (application_.currentTool_ == Tool_LineMeasure)
             {
               return new LineMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                             x, y, 255, 0, 0, application_.GetFont());
             }
-            else if (application_.currentTool_ == Tools_CircleMeasure)
+            else if (application_.currentTool_ == Tool_CircleMeasure)
             {
               return new CircleMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                               x, y, 255, 0, 0, application_.GetFont());
@@ -177,11 +179,11 @@
               break;
 
             case 'l':
-              application_.currentTool_ = Tools_LineMeasure;
+              application_.currentTool_ = Tool_LineMeasure;
               break;
 
             case 'c':
-              application_.currentTool_ = Tools_CircleMeasure;
+              application_.currentTool_ = Tool_CircleMeasure;
               break;
 
             default:
@@ -203,35 +205,40 @@
         {
         }
 
-        virtual void HandleMessageFromWeb(std::string& output, const std::string& input) 
+        virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input) 
         {
           if (input == "select-tool:line-measure")
           {
-            viewerApplication_.currentTool_ = Tools_LineMeasure;
-            NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
+            viewerApplication_.currentTool_ = Tool_LineMeasure;
+            NotifyStatusUpdateFromCppToWebWithString("currentTool=line-measure");
           }
           else if (input == "select-tool:circle-measure")
           {
-            viewerApplication_.currentTool_ = Tools_CircleMeasure;
-            NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
+            viewerApplication_.currentTool_ = Tool_CircleMeasure;
+            NotifyStatusUpdateFromCppToWebWithString("currentTool=circle-measure");
           }
 
           output = "ok";
         }
 
-        virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) 
+        virtual void NotifySerializedMessageFromCppToWeb(const std::string& statusUpdateMessage) 
         {
-          UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
+          UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str());
+        }
+
+        virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage) 
+        {
+          UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str());
         }
 
       };
 #endif
-      enum Tools {
-        Tools_LineMeasure,
-        Tools_CircleMeasure
+      enum Tool {
+        Tool_LineMeasure,
+        Tool_CircleMeasure
       };
 
-      Tools                                currentTool_;
+      Tool                                 currentTool_;
       std::auto_ptr<MainWidgetInteractor>  mainWidgetInteractor_;
       std::auto_ptr<ThumbnailInteractor>   thumbnailInteractor_;
       LayoutWidget*                        mainLayout_;
@@ -253,7 +260,7 @@
     public:
       SimpleViewerApplication(MessageBroker& broker) :
         IObserver(broker),
-        currentTool_(Tools_LineMeasure),
+        currentTool_(Tool_LineMeasure),
         mainLayout_(NULL),
         currentInstanceIndex_(0),
         wasmViewport1_(NULL),
@@ -426,8 +433,8 @@
       
       virtual void OnPushButton1Clicked() {}
       virtual void OnPushButton2Clicked() {}
-      virtual void OnTool1Clicked() { currentTool_ = Tools_LineMeasure;}
-      virtual void OnTool2Clicked() { currentTool_ = Tools_CircleMeasure;}
+      virtual void OnTool1Clicked() { currentTool_ = Tool_LineMeasure;}
+      virtual void OnTool2Clicked() { currentTool_ = Tool_CircleMeasure;}
 
       virtual void GetButtonNames(std::string& pushButton1,
                                   std::string& pushButton2,
--- a/Applications/Samples/SingleFrameApplication.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SingleFrameApplication.h	Thu Apr 18 10:43:17 2019 +0200
@@ -60,7 +60,8 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar)
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& displayTouches)
         {
           return NULL;
         }
@@ -137,7 +138,7 @@
 
           if (slice >= static_cast<int>(source_->GetSliceCount()))
           {
-            slice = source_->GetSliceCount() - 1;
+            slice = static_cast<int>(source_->GetSliceCount()) - 1;
           }
 
           if (slice != static_cast<int>(slice_)) 
@@ -159,7 +160,7 @@
         if (source_ != NULL &&
             index < source_->GetSliceCount())
         {
-          slice_ = index;
+          slice_ = static_cast<unsigned int>(index);
           
 #if 1
           GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry());
--- a/Applications/Samples/SingleFrameEditorApplication.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Thu Apr 18 10:43:17 2019 +0200
@@ -24,6 +24,7 @@
 #include "SampleApplicationBase.h"
 
 #include "../../Framework/Radiography/RadiographyLayerCropTracker.h"
+#include "../../Framework/Radiography/RadiographyLayerMaskTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerMoveTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerResizeTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerRotateTracker.h"
@@ -33,6 +34,7 @@
 #include "../../Framework/Radiography/RadiographyWindowingTracker.h"
 #include "../../Framework/Radiography/RadiographySceneWriter.h"
 #include "../../Framework/Radiography/RadiographySceneReader.h"
+#include "../../Framework/Radiography/RadiographyMaskLayer.h"
 
 #include <Core/HttpClient.h>
 #include <Core/Images/FontRegistry.h>
@@ -60,6 +62,7 @@
         Tool_Rotate,
         Tool_Crop,
         Tool_Resize,
+        Tool_Mask,
         Tool_Windowing
       };
 
@@ -67,6 +70,7 @@
       StoneApplicationContext*  context_;
       UndoRedoStack             undoRedoStack_;
       Tool                      tool_;
+      RadiographyMaskLayer*     maskLayer_;
 
 
       static double GetHandleSize()
@@ -79,7 +83,8 @@
       RadiographyEditorInteractor(MessageBroker& broker) :
         IObserver(broker),
         context_(NULL),
-        tool_(Tool_Move)
+        tool_(Tool_Move),
+        maskLayer_(NULL)
       {
       }
 
@@ -88,6 +93,10 @@
         context_ = &context;
       }
 
+      void SetMaskLayer(RadiographyMaskLayer* maskLayer)
+      {
+        maskLayer_ = maskLayer;
+      }
       virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget,
                                                           const ViewportGeometry& view,
                                                           MouseButton button,
@@ -96,7 +105,8 @@
                                                           int viewportY,
                                                           double x,
                                                           double y,
-                                                          IStatusBar* statusBar)
+                                                          IStatusBar* statusBar,
+                                                          const std::vector<Touch>& displayTouches)
       {
         RadiographyWidget& widget = dynamic_cast<RadiographyWidget&>(worldWidget);
 
@@ -126,22 +136,27 @@
             return NULL;
           }
           else if (tool_ == Tool_Crop ||
-                   tool_ == Tool_Resize)
+                   tool_ == Tool_Resize ||
+                   tool_ == Tool_Mask)
           {
             RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
             
-            Corner corner;
-            if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+            ControlPoint controlPoint;
+            if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize()))
             {
               switch (tool_)
               {
               case Tool_Crop:
                 return new RadiographyLayerCropTracker
-                    (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner);
+                    (undoRedoStack_, widget.GetScene(), view, selected, controlPoint);
+
+              case Tool_Mask:
+                return new RadiographyLayerMaskTracker
+                    (undoRedoStack_, widget.GetScene(), view, selected, controlPoint);
 
               case Tool_Resize:
                 return new RadiographyLayerResizeTracker
-                    (undoRedoStack_, widget.GetScene(), selected, x, y, corner,
+                    (undoRedoStack_, widget.GetScene(), selected, controlPoint,
                      (modifiers & KeyboardModifiers_Shift));
 
               default:
@@ -207,6 +222,7 @@
         {
           return NULL;
         }
+        return NULL;
       }
 
       virtual void MouseOver(CairoContext& context,
@@ -231,25 +247,24 @@
 
         if (widget.LookupSelectedLayer(selected) &&
             (tool_ == Tool_Crop ||
-             tool_ == Tool_Resize))
+             tool_ == Tool_Resize ||
+             tool_ == Tool_Mask))
         {
           RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
 
-          Corner corner;
-          if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+          ControlPoint controlPoint;
+          if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize()))
           {
-            accessor.GetLayer().GetCorner(x, y, corner);
-
             double z = 1.0 / view.GetZoom();
 
             context.SetSourceColor(255, 0, 0);
             cairo_t* cr = context.GetObject();
             cairo_set_line_width(cr, 2.0 * z);
-            cairo_move_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
-            cairo_line_to(cr, x + GetHandleSize() * z, y - GetHandleSize() * z);
-            cairo_line_to(cr, x + GetHandleSize() * z, y + GetHandleSize() * z);
-            cairo_line_to(cr, x - GetHandleSize() * z, y + GetHandleSize() * z);
-            cairo_line_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
+            cairo_move_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y - GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y + GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y + GetHandleSize() * z);
+            cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z);
             cairo_stroke(cr);
           }
         }
@@ -280,6 +295,11 @@
           tool_ = Tool_Crop;
           break;
 
+        case 'm':
+          tool_ = Tool_Mask;
+          widget.Select(1);
+          break;
+
         case 'd':
         {
           // dump to json and reload
@@ -338,7 +358,7 @@
           widget.SwitchInvert();
           break;
 
-        case 'm':
+        case 't':
           tool_ = Tool_Move;
           break;
 
@@ -407,6 +427,7 @@
       boost::shared_ptr<RadiographyScene>   scene_;
       RadiographyEditorInteractor           interactor_;
       Orthanc::FontRegistry                 fontRegistry_;
+      RadiographyMaskLayer*                 maskLayer_;
 
     public:
       SingleFrameEditorApplication(MessageBroker& broker) :
@@ -447,10 +468,11 @@
         statusBar.SetMessage("Use the key \"e\" to export DICOM to the Orthanc server");
         statusBar.SetMessage("Use the key \"f\" to switch full screen");
         statusBar.SetMessage("Use the key \"i\" to invert contrast");
-        statusBar.SetMessage("Use the key \"m\" to move objects");
+        statusBar.SetMessage("Use the key \"m\" to modify the mask");
         statusBar.SetMessage("Use the key \"n\" to switch between nearest neighbor and bilinear interpolation");
         statusBar.SetMessage("Use the key \"r\" to rotate objects");
         statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM layers)");
+        statusBar.SetMessage("Use the key \"t\" to move (translate) objects");
         statusBar.SetMessage("Use the key \"w\" to change windowing");
         
         statusBar.SetMessage("Use the key \"ctrl-z\" to undo action");
@@ -468,7 +490,10 @@
         fontRegistry_.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
         
         scene_.reset(new RadiographyScene(GetBroker()));
-        scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL);
+        
+        RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL);
+        //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0);
+        // = scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL);
 
 #if !defined(ORTHANC_ENABLE_WASM) || ORTHANC_ENABLE_WASM != 1
         Orthanc::HttpClient::ConfigureSsl(true, "/etc/ssl/certs/ca-certificates.crt");
@@ -476,6 +501,15 @@
         
         //scene_->LoadDicomWebFrame(context->GetWebService());
         
+        std::vector<Orthanc::ImageProcessing::ImagePoint> mask;
+        mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 100));
+        mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 1000));
+        mask.push_back(Orthanc::ImageProcessing::ImagePoint(2000, 1000));
+        mask.push_back(Orthanc::ImageProcessing::ImagePoint(2200, 150));
+        mask.push_back(Orthanc::ImageProcessing::ImagePoint(1500, 550));
+        maskLayer_ = dynamic_cast<RadiographyMaskLayer*>(&(scene_->LoadMask(mask, dynamic_cast<RadiographyDicomLayer&>(dicomLayer), 128.0f, NULL)));
+        interactor_.SetMaskLayer(maskLayer_);
+
         {
           RadiographyLayer& layer = scene_->LoadText(fontRegistry_.GetFont(0), "Hello\nworld", NULL);
           layer.SetResizeable(true);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/StoneSampleCommands.yml	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,35 @@
+#
+#        1         2         3         4         5         6         7         8
+# 345678901234567890123456789012345678901234567890123456789012345678901234567890
+#
+rootName: StoneSampleCommands
+
+# +---------------------------------+
+# | Messages from TypeScript to C++ |
+# +---------------------------------+
+
+enum Tool:
+  - LineMeasure
+  - CircleMeasure
+  - Crop
+  - Windowing
+  - Zoom
+  - Pan
+  - Move
+  - Rotate
+  - Resize
+  - Mask
+
+struct SelectTool:
+  __handler: cpp
+  tool: Tool
+
+enum ActionType:
+  - UndoCrop
+  - Rotate
+  - Invert
+
+struct Action:
+  __handler: cpp
+  type: ActionType
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/StoneSampleCommands_generate.py	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,16 @@
+import sys
+import os
+
+# add the generation script location to the search paths
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'Resources', 'CodeGeneration'))
+
+# import the code generation tooling script
+import stonegentool
+
+schemaFile = os.path.join(os.path.dirname(__file__), 'StoneSampleCommands.yml')
+outDir = os.path.dirname(__file__)
+
+# ignition!
+stonegentool.Process(schemaFile, outDir)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/StoneSampleCommands_generated.hpp	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,703 @@
+/*
+         1         2         3         4         5         6         7
+12345678901234567890123456789012345678901234567890123456789012345678901234567890
+
+Generated on 2019-03-18 12:07:42.696093 by stonegentool
+
+*/
+#pragma once
+
+#include <exception>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <assert.h>
+#include <memory>
+#include <json/json.h>
+
+//#define STONEGEN_NO_CPP11 1
+
+#ifdef STONEGEN_NO_CPP11
+#define StoneSmartPtr std::auto_ptr
+#else 
+#define StoneSmartPtr std::unique_ptr
+#endif 
+
+namespace StoneSampleCommands
+{
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asInt();
+  }
+
+  inline Json::Value _StoneSerializeValue(int32_t value)
+  {
+    Json::Value result(value);
+    return result;
+  }
+
+  inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue;
+  }
+
+  inline Json::Value _StoneSerializeValue(Json::Value value)
+  {
+    return value;
+  }
+
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asDouble();
+  }
+
+  inline Json::Value _StoneSerializeValue(double value)
+  {
+    Json::Value result(value);
+    return result;
+  }
+
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asBool();
+  }
+
+  inline Json::Value _StoneSerializeValue(bool value)
+  {
+    Json::Value result(value);
+    return result;
+  }
+
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(
+       std::string& destValue
+     , const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asString();
+  }
+
+  inline Json::Value _StoneSerializeValue(const std::string& value)
+  {
+    // the following is better than 
+    Json::Value result(value.data(),value.data()+value.size());
+    return result;
+  }
+
+  inline std::string MakeIndent(size_t indent)
+  {
+    char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!!
+    for(size_t i = 0; i < indent; ++i)
+      txt[i] = ' ';
+    txt[indent] = 0;
+    std::string retVal(txt);
+    free(txt); // NO EXCEPTION ABOVE !!!!!!!!!!
+    return retVal;
+  }
+
+  // generic dumper
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent)
+  {
+    out << MakeIndent(indent) << value;
+    return out;
+  }
+
+  // string dumper
+  inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "\"" << value  << "\"";
+    return out;
+  }
+
+  /** Throws in case of problem */
+  template<typename T>
+  void _StoneDeserializeValue(
+    std::map<std::string, T>& destValue, const Json::Value& jsonValue)
+  {
+    destValue.clear();
+    for (
+      Json::Value::const_iterator itr = jsonValue.begin();
+      itr != jsonValue.end();
+      itr++)
+    {
+      std::string key;
+      _StoneDeserializeValue(key, itr.key());
+
+      T innerDestValue;
+      _StoneDeserializeValue(innerDestValue, *itr);
+
+      destValue[key] = innerDestValue;
+    }
+  }
+
+  template<typename T>
+  Json::Value _StoneSerializeValue(const std::map<std::string,T>& value)
+  {
+    Json::Value result(Json::objectValue);
+
+    for (typename std::map<std::string, T>::const_iterator it = value.cbegin();
+      it != value.cend(); ++it)
+    {
+      // it->first it->second
+      result[it->first] = _StoneSerializeValue(it->second);
+    }
+    return result;
+  }
+
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "{\n";
+    for (typename std::map<std::string, T>::const_iterator it = value.cbegin();
+      it != value.cend(); ++it)
+    {
+      out << MakeIndent(indent+2) << "\"" << it->first << "\" : ";
+      StoneDumpValue(out, it->second, indent+2);
+    }
+    out << MakeIndent(indent) << "}\n";
+    return out;
+  }
+
+  /** Throws in case of problem */
+  template<typename T>
+  void _StoneDeserializeValue(
+    std::vector<T>& destValue, const Json::Value& jsonValue)
+  {
+    destValue.clear();
+    destValue.reserve(jsonValue.size());
+    for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++)
+    {
+      T innerDestValue;
+      _StoneDeserializeValue(innerDestValue, jsonValue[i]);
+      destValue.push_back(innerDestValue);
+    }
+  }
+
+  template<typename T>
+  Json::Value _StoneSerializeValue(const std::vector<T>& value)
+  {
+    Json::Value result(Json::arrayValue);
+    for (size_t i = 0; i < value.size(); ++i)
+    {
+      result.append(_StoneSerializeValue(value[i]));
+    }
+    return result;
+  }
+
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "[\n";
+    for (size_t i = 0; i < value.size(); ++i)
+    {
+      StoneDumpValue(out, value[i], indent+2);
+    }
+    out << MakeIndent(indent) << "]\n";
+    return out;
+  }
+
+  inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value)
+  {
+    if ((!value.isMember("type")) || (!value["type"].isString()))
+    {
+      std::stringstream ss;
+      ss << "Cannot deserialize value ('type' key invalid)";
+      throw std::runtime_error(ss.str());
+    }
+  }
+
+  inline void StoneCheckSerializedValueType(
+    const Json::Value& value, std::string typeStr)
+  {
+    StoneCheckSerializedValueTypeGeneric(value);
+
+    std::string actTypeStr = value["type"].asString();
+    if (actTypeStr != typeStr)
+    {
+      std::stringstream ss;
+      ss << "Cannot deserialize type" << actTypeStr
+        << "into " << typeStr;
+      throw std::runtime_error(ss.str());
+    }
+  }
+
+  // end of generic methods
+
+// end of generic methods
+
+  enum Tool {
+    Tool_LineMeasure,
+    Tool_CircleMeasure,
+    Tool_Crop,
+    Tool_Windowing,
+    Tool_Zoom,
+    Tool_Pan,
+    Tool_Move,
+    Tool_Rotate,
+    Tool_Resize,
+    Tool_Mask,
+  };
+
+  inline std::string ToString(const Tool& value)
+  {
+    if( value == Tool_LineMeasure)
+    {
+      return std::string("LineMeasure");
+    }
+    if( value == Tool_CircleMeasure)
+    {
+      return std::string("CircleMeasure");
+    }
+    if( value == Tool_Crop)
+    {
+      return std::string("Crop");
+    }
+    if( value == Tool_Windowing)
+    {
+      return std::string("Windowing");
+    }
+    if( value == Tool_Zoom)
+    {
+      return std::string("Zoom");
+    }
+    if( value == Tool_Pan)
+    {
+      return std::string("Pan");
+    }
+    if( value == Tool_Move)
+    {
+      return std::string("Move");
+    }
+    if( value == Tool_Rotate)
+    {
+      return std::string("Rotate");
+    }
+    if( value == Tool_Resize)
+    {
+      return std::string("Resize");
+    }
+    if( value == Tool_Mask)
+    {
+      return std::string("Mask");
+    }
+    std::stringstream ss;
+    ss << "Value \"" << value << "\" cannot be converted to Tool. Possible values are: "
+        << " LineMeasure = " << static_cast<int64_t>(Tool_LineMeasure)  << ", " 
+        << " CircleMeasure = " << static_cast<int64_t>(Tool_CircleMeasure)  << ", " 
+        << " Crop = " << static_cast<int64_t>(Tool_Crop)  << ", " 
+        << " Windowing = " << static_cast<int64_t>(Tool_Windowing)  << ", " 
+        << " Zoom = " << static_cast<int64_t>(Tool_Zoom)  << ", " 
+        << " Pan = " << static_cast<int64_t>(Tool_Pan)  << ", " 
+        << " Move = " << static_cast<int64_t>(Tool_Move)  << ", " 
+        << " Rotate = " << static_cast<int64_t>(Tool_Rotate)  << ", " 
+        << " Resize = " << static_cast<int64_t>(Tool_Resize)  << ", " 
+        << " Mask = " << static_cast<int64_t>(Tool_Mask)  << ", " 
+        << std::endl;
+    std::string msg = ss.str();
+    throw std::runtime_error(msg);
+  }
+
+  inline void FromString(Tool& value, std::string strValue)
+  {
+    if( strValue == std::string("LineMeasure") )
+    {
+      value = Tool_LineMeasure;
+      return;
+    }
+    if( strValue == std::string("CircleMeasure") )
+    {
+      value = Tool_CircleMeasure;
+      return;
+    }
+    if( strValue == std::string("Crop") )
+    {
+      value = Tool_Crop;
+      return;
+    }
+    if( strValue == std::string("Windowing") )
+    {
+      value = Tool_Windowing;
+      return;
+    }
+    if( strValue == std::string("Zoom") )
+    {
+      value = Tool_Zoom;
+      return;
+    }
+    if( strValue == std::string("Pan") )
+    {
+      value = Tool_Pan;
+      return;
+    }
+    if( strValue == std::string("Move") )
+    {
+      value = Tool_Move;
+      return;
+    }
+    if( strValue == std::string("Rotate") )
+    {
+      value = Tool_Rotate;
+      return;
+    }
+    if( strValue == std::string("Resize") )
+    {
+      value = Tool_Resize;
+      return;
+    }
+    if( strValue == std::string("Mask") )
+    {
+      value = Tool_Mask;
+      return;
+    }
+
+    std::stringstream ss;
+    ss << "String \"" << strValue << "\" cannot be converted to Tool. Possible values are: LineMeasure CircleMeasure Crop Windowing Zoom Pan Move Rotate Resize Mask ";
+    std::string msg = ss.str();
+    throw std::runtime_error(msg);
+  }
+
+
+  inline void _StoneDeserializeValue(
+    Tool& destValue, const Json::Value& jsonValue)
+  {
+    FromString(destValue, jsonValue.asString());
+  }
+
+  inline Json::Value _StoneSerializeValue(const Tool& value)
+  {
+    std::string strValue = ToString(value);
+    return Json::Value(strValue);
+  }
+
+  inline std::ostream& StoneDumpValue(std::ostream& out, const Tool& value, size_t indent = 0)
+  {
+    if( value == Tool_LineMeasure)
+    {
+      out << MakeIndent(indent) << "LineMeasure" << std::endl;
+    }
+    if( value == Tool_CircleMeasure)
+    {
+      out << MakeIndent(indent) << "CircleMeasure" << std::endl;
+    }
+    if( value == Tool_Crop)
+    {
+      out << MakeIndent(indent) << "Crop" << std::endl;
+    }
+    if( value == Tool_Windowing)
+    {
+      out << MakeIndent(indent) << "Windowing" << std::endl;
+    }
+    if( value == Tool_Zoom)
+    {
+      out << MakeIndent(indent) << "Zoom" << std::endl;
+    }
+    if( value == Tool_Pan)
+    {
+      out << MakeIndent(indent) << "Pan" << std::endl;
+    }
+    if( value == Tool_Move)
+    {
+      out << MakeIndent(indent) << "Move" << std::endl;
+    }
+    if( value == Tool_Rotate)
+    {
+      out << MakeIndent(indent) << "Rotate" << std::endl;
+    }
+    if( value == Tool_Resize)
+    {
+      out << MakeIndent(indent) << "Resize" << std::endl;
+    }
+    if( value == Tool_Mask)
+    {
+      out << MakeIndent(indent) << "Mask" << std::endl;
+    }
+    return out;
+  }
+
+
+  enum ActionType {
+    ActionType_UndoCrop,
+    ActionType_Rotate,
+    ActionType_Invert,
+  };
+
+  inline std::string ToString(const ActionType& value)
+  {
+    if( value == ActionType_UndoCrop)
+    {
+      return std::string("UndoCrop");
+    }
+    if( value == ActionType_Rotate)
+    {
+      return std::string("Rotate");
+    }
+    if( value == ActionType_Invert)
+    {
+      return std::string("Invert");
+    }
+    std::stringstream ss;
+    ss << "Value \"" << value << "\" cannot be converted to ActionType. Possible values are: "
+        << " UndoCrop = " << static_cast<int64_t>(ActionType_UndoCrop)  << ", " 
+        << " Rotate = " << static_cast<int64_t>(ActionType_Rotate)  << ", " 
+        << " Invert = " << static_cast<int64_t>(ActionType_Invert)  << ", " 
+        << std::endl;
+    std::string msg = ss.str();
+    throw std::runtime_error(msg);
+  }
+
+  inline void FromString(ActionType& value, std::string strValue)
+  {
+    if( strValue == std::string("UndoCrop") )
+    {
+      value = ActionType_UndoCrop;
+      return;
+    }
+    if( strValue == std::string("Rotate") )
+    {
+      value = ActionType_Rotate;
+      return;
+    }
+    if( strValue == std::string("Invert") )
+    {
+      value = ActionType_Invert;
+      return;
+    }
+
+    std::stringstream ss;
+    ss << "String \"" << strValue << "\" cannot be converted to ActionType. Possible values are: UndoCrop Rotate Invert ";
+    std::string msg = ss.str();
+    throw std::runtime_error(msg);
+  }
+
+
+  inline void _StoneDeserializeValue(
+    ActionType& destValue, const Json::Value& jsonValue)
+  {
+    FromString(destValue, jsonValue.asString());
+  }
+
+  inline Json::Value _StoneSerializeValue(const ActionType& value)
+  {
+    std::string strValue = ToString(value);
+    return Json::Value(strValue);
+  }
+
+  inline std::ostream& StoneDumpValue(std::ostream& out, const ActionType& value, size_t indent = 0)
+  {
+    if( value == ActionType_UndoCrop)
+    {
+      out << MakeIndent(indent) << "UndoCrop" << std::endl;
+    }
+    if( value == ActionType_Rotate)
+    {
+      out << MakeIndent(indent) << "Rotate" << std::endl;
+    }
+    if( value == ActionType_Invert)
+    {
+      out << MakeIndent(indent) << "Invert" << std::endl;
+    }
+    return out;
+  }
+
+
+
+#ifdef _MSC_VER
+#pragma region SelectTool
+#endif //_MSC_VER
+
+  struct SelectTool
+  {
+    Tool tool;
+
+    SelectTool(Tool tool = Tool())
+    {
+      this->tool = tool;
+    }
+  };
+
+  inline void _StoneDeserializeValue(SelectTool& destValue, const Json::Value& value)
+  {
+    _StoneDeserializeValue(destValue.tool, value["tool"]);
+    }
+
+  inline Json::Value _StoneSerializeValue(const SelectTool& value)
+  {
+    Json::Value result(Json::objectValue);
+    result["tool"] = _StoneSerializeValue(value.tool);
+
+    return result;
+  }
+
+  inline std::ostream& StoneDumpValue(std::ostream& out, const SelectTool& value, size_t indent = 0)
+  {
+    out << MakeIndent(indent) << "{\n";
+    out << MakeIndent(indent) << "tool:\n";
+    StoneDumpValue(out, value.tool,indent+2);
+    out << "\n";
+
+    out << MakeIndent(indent) << "}\n";
+    return out;
+  }
+
+  inline void StoneDeserialize(SelectTool& destValue, const Json::Value& value)
+  {
+    StoneCheckSerializedValueType(value, "StoneSampleCommands.SelectTool");
+    _StoneDeserializeValue(destValue, value["value"]);
+  }
+
+  inline Json::Value StoneSerializeToJson(const SelectTool& value)
+  {
+    Json::Value result(Json::objectValue);
+    result["type"] = "StoneSampleCommands.SelectTool";
+    result["value"] = _StoneSerializeValue(value);
+    return result;
+  }
+
+  inline std::string StoneSerialize(const SelectTool& value)
+  {
+    Json::Value resultJson = StoneSerializeToJson(value);
+    std::string resultStr = resultJson.toStyledString();
+    return resultStr;
+  }
+
+#ifdef _MSC_VER
+#pragma endregion SelectTool
+#endif //_MSC_VER
+
+#ifdef _MSC_VER
+#pragma region Action
+#endif //_MSC_VER
+
+  struct Action
+  {
+    ActionType type;
+
+    Action(ActionType type = ActionType())
+    {
+      this->type = type;
+    }
+  };
+
+  inline void _StoneDeserializeValue(Action& destValue, const Json::Value& value)
+  {
+    _StoneDeserializeValue(destValue.type, value["type"]);
+    }
+
+  inline Json::Value _StoneSerializeValue(const Action& value)
+  {
+    Json::Value result(Json::objectValue);
+    result["type"] = _StoneSerializeValue(value.type);
+
+    return result;
+  }
+
+  inline std::ostream& StoneDumpValue(std::ostream& out, const Action& value, size_t indent = 0)
+  {
+    out << MakeIndent(indent) << "{\n";
+    out << MakeIndent(indent) << "type:\n";
+    StoneDumpValue(out, value.type,indent+2);
+    out << "\n";
+
+    out << MakeIndent(indent) << "}\n";
+    return out;
+  }
+
+  inline void StoneDeserialize(Action& destValue, const Json::Value& value)
+  {
+    StoneCheckSerializedValueType(value, "StoneSampleCommands.Action");
+    _StoneDeserializeValue(destValue, value["value"]);
+  }
+
+  inline Json::Value StoneSerializeToJson(const Action& value)
+  {
+    Json::Value result(Json::objectValue);
+    result["type"] = "StoneSampleCommands.Action";
+    result["value"] = _StoneSerializeValue(value);
+    return result;
+  }
+
+  inline std::string StoneSerialize(const Action& value)
+  {
+    Json::Value resultJson = StoneSerializeToJson(value);
+    std::string resultStr = resultJson.toStyledString();
+    return resultStr;
+  }
+
+#ifdef _MSC_VER
+#pragma endregion Action
+#endif //_MSC_VER
+
+#ifdef _MSC_VER
+#pragma region Dispatching code
+#endif //_MSC_VER
+
+  class IHandler
+  {
+  public:
+    virtual bool Handle(const SelectTool& value) = 0;
+    virtual bool Handle(const Action& value) = 0;
+  };
+
+  /** Service function for StoneDispatchToHandler */
+  inline bool StoneDispatchJsonToHandler(
+    const Json::Value& jsonValue, IHandler* handler)
+  {
+    StoneCheckSerializedValueTypeGeneric(jsonValue);
+    std::string type = jsonValue["type"].asString();
+    if (type == "")
+    {
+      // this should never ever happen
+      throw std::runtime_error("Caught empty type while dispatching");
+    }
+    else if (type == "StoneSampleCommands.SelectTool")
+    {
+      SelectTool value;
+      _StoneDeserializeValue(value, jsonValue["value"]);
+      return handler->Handle(value);
+    }
+    else if (type == "StoneSampleCommands.Action")
+    {
+      Action value;
+      _StoneDeserializeValue(value, jsonValue["value"]);
+      return handler->Handle(value);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  /** Takes a serialized type and passes this to the handler */
+  inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler)
+  {
+    Json::Value readValue;
+
+    Json::CharReaderBuilder builder;
+    Json::CharReader* reader = builder.newCharReader();
+
+    StoneSmartPtr<Json::CharReader> ptr(reader);
+
+    std::string errors;
+
+    bool ok = reader->parse(
+      strValue.c_str(),
+      strValue.c_str() + strValue.size(),
+      &readValue,
+      &errors
+    );
+    if (!ok)
+    {
+      std::stringstream ss;
+      ss << "Jsoncpp parsing error: " << errors;
+      throw std::runtime_error(ss.str());
+    }
+    return StoneDispatchJsonToHandler(readValue, handler);
+  }
+
+#ifdef _MSC_VER
+#pragma endregion Dispatching code
+#endif //_MSC_VER
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/StoneSampleCommands_generated.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,333 @@
+/*
+         1         2         3         4         5         6         7
+12345678901234567890123456789012345678901234567890123456789012345678901234567890
+
+Generated on 2019-03-18 12:07:42.696093 by stonegentool
+
+*/
+
+function StoneCheckSerializedValueType(value: any, typeStr: string)
+{
+  StoneCheckSerializedValueTypeGeneric(value);
+
+  if (value['type'] != typeStr)
+  {
+    throw new Error(
+      `Cannot deserialize type ${value['type']} into ${typeStr}`);
+  }
+}
+
+function isString(val: any) :boolean
+{
+  return ((typeof val === 'string') || (val instanceof String));
+}
+
+function StoneCheckSerializedValueTypeGeneric(value: any)
+{
+  // console.//log("+-------------------------------------------------+");
+  // console.//log("|            StoneCheckSerializedValueTypeGeneric |");
+  // console.//log("+-------------------------------------------------+");
+  // console.//log("value = ");
+  // console.//log(value);
+  if ( (!('type' in value)) || (!isString(value.type)) )
+  {
+    throw new Error(
+      "Cannot deserialize value ('type' key invalid)");
+  }
+}
+
+// end of generic methods
+
+export enum Tool {
+  LineMeasure = "LineMeasure",
+  CircleMeasure = "CircleMeasure",
+  Crop = "Crop",
+  Windowing = "Windowing",
+  Zoom = "Zoom",
+  Pan = "Pan",
+  Move = "Move",
+  Rotate = "Rotate",
+  Resize = "Resize",
+  Mask = "Mask"
+};
+
+export function Tool_FromString(strValue:string) : Tool
+{
+  if( strValue == "LineMeasure" )
+  {
+    return Tool.LineMeasure;
+  }
+  if( strValue == "CircleMeasure" )
+  {
+    return Tool.CircleMeasure;
+  }
+  if( strValue == "Crop" )
+  {
+    return Tool.Crop;
+  }
+  if( strValue == "Windowing" )
+  {
+    return Tool.Windowing;
+  }
+  if( strValue == "Zoom" )
+  {
+    return Tool.Zoom;
+  }
+  if( strValue == "Pan" )
+  {
+    return Tool.Pan;
+  }
+  if( strValue == "Move" )
+  {
+    return Tool.Move;
+  }
+  if( strValue == "Rotate" )
+  {
+    return Tool.Rotate;
+  }
+  if( strValue == "Resize" )
+  {
+    return Tool.Resize;
+  }
+  if( strValue == "Mask" )
+  {
+    return Tool.Mask;
+  }
+
+  let msg : string =  `String ${strValue} cannot be converted to Tool. Possible values are: LineMeasure, CircleMeasure, Crop, Windowing, Zoom, Pan, Move, Rotate, Resize, Mask`;
+  throw new Error(msg);
+}
+
+export function Tool_ToString(value:Tool) : string
+{
+  if( value == Tool.LineMeasure )
+  {
+    return "LineMeasure";
+  }
+  if( value == Tool.CircleMeasure )
+  {
+    return "CircleMeasure";
+  }
+  if( value == Tool.Crop )
+  {
+    return "Crop";
+  }
+  if( value == Tool.Windowing )
+  {
+    return "Windowing";
+  }
+  if( value == Tool.Zoom )
+  {
+    return "Zoom";
+  }
+  if( value == Tool.Pan )
+  {
+    return "Pan";
+  }
+  if( value == Tool.Move )
+  {
+    return "Move";
+  }
+  if( value == Tool.Rotate )
+  {
+    return "Rotate";
+  }
+  if( value == Tool.Resize )
+  {
+    return "Resize";
+  }
+  if( value == Tool.Mask )
+  {
+    return "Mask";
+  }
+
+  let msg : string = `Value ${value} cannot be converted to Tool. Possible values are: `;
+  {
+    let _LineMeasure_enumValue : string = Tool.LineMeasure; // enums are strings in stonecodegen, so this will work.
+    let msg_LineMeasure : string = `LineMeasure (${_LineMeasure_enumValue}), `;
+    msg = msg + msg_LineMeasure;
+  }
+  {
+    let _CircleMeasure_enumValue : string = Tool.CircleMeasure; // enums are strings in stonecodegen, so this will work.
+    let msg_CircleMeasure : string = `CircleMeasure (${_CircleMeasure_enumValue}), `;
+    msg = msg + msg_CircleMeasure;
+  }
+  {
+    let _Crop_enumValue : string = Tool.Crop; // enums are strings in stonecodegen, so this will work.
+    let msg_Crop : string = `Crop (${_Crop_enumValue}), `;
+    msg = msg + msg_Crop;
+  }
+  {
+    let _Windowing_enumValue : string = Tool.Windowing; // enums are strings in stonecodegen, so this will work.
+    let msg_Windowing : string = `Windowing (${_Windowing_enumValue}), `;
+    msg = msg + msg_Windowing;
+  }
+  {
+    let _Zoom_enumValue : string = Tool.Zoom; // enums are strings in stonecodegen, so this will work.
+    let msg_Zoom : string = `Zoom (${_Zoom_enumValue}), `;
+    msg = msg + msg_Zoom;
+  }
+  {
+    let _Pan_enumValue : string = Tool.Pan; // enums are strings in stonecodegen, so this will work.
+    let msg_Pan : string = `Pan (${_Pan_enumValue}), `;
+    msg = msg + msg_Pan;
+  }
+  {
+    let _Move_enumValue : string = Tool.Move; // enums are strings in stonecodegen, so this will work.
+    let msg_Move : string = `Move (${_Move_enumValue}), `;
+    msg = msg + msg_Move;
+  }
+  {
+    let _Rotate_enumValue : string = Tool.Rotate; // enums are strings in stonecodegen, so this will work.
+    let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `;
+    msg = msg + msg_Rotate;
+  }
+  {
+    let _Resize_enumValue : string = Tool.Resize; // enums are strings in stonecodegen, so this will work.
+    let msg_Resize : string = `Resize (${_Resize_enumValue}), `;
+    msg = msg + msg_Resize;
+  }
+  {
+    let _Mask_enumValue : string = Tool.Mask; // enums are strings in stonecodegen, so this will work.
+    let msg_Mask : string = `Mask (${_Mask_enumValue})`;
+    msg = msg + msg_Mask;
+  }
+  throw new Error(msg);
+}
+
+export enum ActionType {
+  UndoCrop = "UndoCrop",
+  Rotate = "Rotate",
+  Invert = "Invert"
+};
+
+export function ActionType_FromString(strValue:string) : ActionType
+{
+  if( strValue == "UndoCrop" )
+  {
+    return ActionType.UndoCrop;
+  }
+  if( strValue == "Rotate" )
+  {
+    return ActionType.Rotate;
+  }
+  if( strValue == "Invert" )
+  {
+    return ActionType.Invert;
+  }
+
+  let msg : string =  `String ${strValue} cannot be converted to ActionType. Possible values are: UndoCrop, Rotate, Invert`;
+  throw new Error(msg);
+}
+
+export function ActionType_ToString(value:ActionType) : string
+{
+  if( value == ActionType.UndoCrop )
+  {
+    return "UndoCrop";
+  }
+  if( value == ActionType.Rotate )
+  {
+    return "Rotate";
+  }
+  if( value == ActionType.Invert )
+  {
+    return "Invert";
+  }
+
+  let msg : string = `Value ${value} cannot be converted to ActionType. Possible values are: `;
+  {
+    let _UndoCrop_enumValue : string = ActionType.UndoCrop; // enums are strings in stonecodegen, so this will work.
+    let msg_UndoCrop : string = `UndoCrop (${_UndoCrop_enumValue}), `;
+    msg = msg + msg_UndoCrop;
+  }
+  {
+    let _Rotate_enumValue : string = ActionType.Rotate; // enums are strings in stonecodegen, so this will work.
+    let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `;
+    msg = msg + msg_Rotate;
+  }
+  {
+    let _Invert_enumValue : string = ActionType.Invert; // enums are strings in stonecodegen, so this will work.
+    let msg_Invert : string = `Invert (${_Invert_enumValue})`;
+    msg = msg + msg_Invert;
+  }
+  throw new Error(msg);
+}
+
+
+
+export class SelectTool {
+  tool:Tool;
+
+  constructor() {
+  }
+
+  public StoneSerialize(): string {
+    let container: object = {};
+    container['type'] = 'StoneSampleCommands.SelectTool';
+    container['value'] = this;
+    return JSON.stringify(container);
+  }
+
+  public static StoneDeserialize(valueStr: string) : SelectTool
+  {
+    let value: any = JSON.parse(valueStr);
+    StoneCheckSerializedValueType(value, 'StoneSampleCommands.SelectTool');
+    let result: SelectTool = value['value'] as SelectTool;
+    return result;
+  }
+}
+export class Action {
+  type:ActionType;
+
+  constructor() {
+  }
+
+  public StoneSerialize(): string {
+    let container: object = {};
+    container['type'] = 'StoneSampleCommands.Action';
+    container['value'] = this;
+    return JSON.stringify(container);
+  }
+
+  public static StoneDeserialize(valueStr: string) : Action
+  {
+    let value: any = JSON.parse(valueStr);
+    StoneCheckSerializedValueType(value, 'StoneSampleCommands.Action');
+    let result: Action = value['value'] as Action;
+    return result;
+  }
+}
+
+export interface IHandler {
+};
+
+/** Service function for StoneDispatchToHandler */
+export function StoneDispatchJsonToHandler(
+  jsonValue: any, handler: IHandler): boolean
+{
+  StoneCheckSerializedValueTypeGeneric(jsonValue);
+  let type: string = jsonValue["type"];
+  if (type == "")
+  {
+    // this should never ever happen
+    throw new Error("Caught empty type while dispatching");
+  }
+  else
+  {
+    return false;
+  }
+}
+
+/** Takes a serialized type and passes this to the handler */
+export function StoneDispatchToHandler(
+  strValue: string, handler: IHandler): boolean
+{
+  // console.//log("+------------------------------------------------+");
+  // console.//log("|            StoneDispatchToHandler              |");
+  // console.//log("+------------------------------------------------+");
+  // console.//log("strValue = ");
+  // console.//log(strValue);
+  let jsonValue: any = JSON.parse(strValue)
+  return StoneDispatchJsonToHandler(jsonValue, handler);
+}
\ No newline at end of file
--- a/Applications/Samples/Web/simple-viewer-single-file.ts	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/Web/simple-viewer-single-file.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -9,7 +9,7 @@
             toolName: toolName
         }
     };
-    wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command));
+    wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command));
 
 }
 
@@ -19,7 +19,7 @@
         commandType: "simple",
         args: {}
     };
-    wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command));
+    wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command));
 }
 
 //initializes the buttons
@@ -42,10 +42,20 @@
 
 // this method is called "from the C++ code" when the StoneApplication is updated.
 // it can be used to update the UI of the application
-function UpdateWebApplication(statusUpdateMessage: string) {
+function UpdateWebApplicationWithString(statusUpdateMessage: string) {
   console.log(statusUpdateMessage);
   
   if (statusUpdateMessage.startsWith("series-description=")) {
       document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1];
   }
 }
+
+function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) {
+  console.log("updating web application with serialized message: ", statusUpdateMessageString);
+  console.log("<not supported in the simple viewer (single file)!>");
+}
+
+// make it available to other js scripts in the application
+(<any> window).UpdateWebApplicationWithString = UpdateWebApplicationWithString;
+
+(<any> window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage;
--- a/Applications/Samples/build-wasm.sh	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/build-wasm.sh	Thu Apr 18 10:43:17 2019 +0200
@@ -1,15 +1,16 @@
 #!/bin/bash
 #
 # usage:
-# to build all targets:
+# to build all targets in Debug:
 # ./build-wasm.sh
 #
-# to build a single target:
-# ./build-wasm.sh OrthancStoneSingleFrameEditor
+# to build a single target in release:
+# ./build-wasm.sh OrthancStoneSingleFrameEditor Release
 
 set -e
 
 target=${1:-all}
+buildType=${2:-Debug}
 
 currentDir=$(pwd)
 samplesRootDir=$(pwd)
@@ -18,16 +19,9 @@
 cd $samplesRootDir/build-wasm
 
 source ~/apps/emsdk/emsdk_env.sh
-cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \
-  -DCMAKE_BUILD_TYPE=Release \
-  -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone \
-  -DORTHANC_FRAMEWORK_SOURCE=path \
-  -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \
-  -DALLOW_DOWNLOADS=ON .. \
-  -DENABLE_WASM=ON
-
+cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON
 ninja $target
 
 echo "-- building the web application -- "
 cd $currentDir
-./build-web.sh
+./build-web.sh
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/build-wasm.sh.old	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# usage:
+# to build all targets:
+# ./build-wasm.sh
+#
+# to build a single target:
+# ./build-wasm.sh OrthancStoneSingleFrameEditor
+
+set -e
+
+target=${1:-all}
+
+currentDir=$(pwd)
+samplesRootDir=$(pwd)
+
+mkdir -p $samplesRootDir/build-wasm
+cd $samplesRootDir/build-wasm
+
+source ~/apps/emsdk/emsdk_env.sh
+cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \
+  -DCMAKE_BUILD_TYPE=Release \
+  -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone \
+  -DORTHANC_FRAMEWORK_SOURCE=path \
+  -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \
+  -DALLOW_DOWNLOADS=ON .. \
+  -DENABLE_WASM=ON
+
+ninja $target
+
+echo "-- building the web application -- "
+cd $currentDir
+./build-web.sh
--- a/Applications/Samples/nginx.local.conf	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Samples/nginx.local.conf	Thu Apr 18 10:43:17 2019 +0200
@@ -30,7 +30,7 @@
     # reverse proxy orthanc
 	location /orthanc/ {
 		rewrite /orthanc(.*) $1 break;
-		proxy_pass http://127.0.0.1:8044;
+		proxy_pass http://127.0.0.1:8042;
 		proxy_set_header Host $http_host;
 		proxy_set_header my-auth-header good-token;
 		proxy_request_buffering off;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/CMakeLists.txt	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,137 @@
+cmake_minimum_required(VERSION 2.8.3)
+project(RtViewerDemo)
+
+if(MSVC)
+  add_definitions(/MP)
+  if (CMAKE_BUILD_TYPE MATCHES DEBUG)
+    add_definitions(/JMC)
+  endif()
+endif()
+
+message("-------------------------------------------------------------------------------------------------------------------")
+message("ORTHANC_FRAMEWORK_ROOT is set to ${ORTHANC_FRAMEWORK_ROOT}")
+message("-------------------------------------------------------------------------------------------------------------------")
+
+if(NOT DEFINED ORTHANC_FRAMEWORK_ROOT)
+  message(FATAL_ERROR "The location of the Orthanc source repository must be set in the ORTHANC_FRAMEWORK_ROOT CMake variable")
+endif()
+
+message("-------------------------------------------------------------------------------------------------------------------")
+message("STONE_SOURCES_DIR is set to ${STONE_SOURCES_DIR}")
+message("-------------------------------------------------------------------------------------------------------------------")
+
+if(NOT DEFINED STONE_SOURCES_DIR)
+  message(FATAL_ERROR "The location of the Stone of Orthanc source repository must be set in the STONE_SOURCES_DIR CMake variable")
+endif()
+
+include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneParameters.cmake)
+
+if (OPENSSL_NO_CAPIENG)
+add_definitions(-DOPENSSL_NO_CAPIENG=1)
+endif()
+
+set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application")
+set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application")
+set(ENABLE_WASM OFF CACHE BOOL "Target WASM application")
+
+if (ENABLE_WASM)
+  #####################################################################
+  ## Configuration of the Emscripten compiler for WebAssembly target
+  #####################################################################
+
+  set(WASM_FLAGS "-s WASM=1")
+  set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options
+  set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined
+  set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching 
+  set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1")
+
+  if (CMAKE_BUILD_TYPE MATCHES DEBUG)
+    set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information
+    set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks
+  else()
+    set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size)
+  endif()
+
+  set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module")
+
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}")  # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too
+  # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000")
+
+  add_definitions(-DORTHANC_ENABLE_WASM=1)
+  set(ORTHANC_SANDBOXED ON)
+
+elseif (ENABLE_QT OR ENABLE_SDL)
+
+  set(ENABLE_NATIVE ON)
+  set(ORTHANC_SANDBOXED OFF)
+  set(ENABLE_CRYPTO_OPTIONS ON)
+  set(ENABLE_GOOGLE_TEST ON)
+  set(ENABLE_WEB_CLIENT ON)
+
+endif()
+
+#####################################################################
+## Configuration for Orthanc
+#####################################################################
+
+if (ORTHANC_STONE_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_VERSION "1.4.1")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
+
+
+#####################################################################
+## Build a static library containing the Orthanc Stone framework
+#####################################################################
+
+LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options)
+
+include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+add_library(OrthancStone STATIC
+  ${ORTHANC_STONE_SOURCES}
+  )
+
+#####################################################################
+## Build all the sample applications
+#####################################################################
+
+include_directories(${ORTHANC_STONE_ROOT})
+
+list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES
+  ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h
+  ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h
+  )
+
+if (ENABLE_WASM)
+  list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES
+    ${STONE_WASM_SOURCES}
+    )
+endif()
+
+add_executable(RtViewerDemo
+  main.cpp
+  ${RTVIEWERDEMO_APPLICATION_SOURCES}
+)
+set_target_properties(RtViewerDemo PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=3)
+target_include_directories(RtViewerDemo PRIVATE ${ORTHANC_STONE_ROOT})
+target_link_libraries(RtViewerDemo OrthancStone)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/build-sdl-msvc15.ps1	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,24 @@
+if (-not (Test-Path "build-sdl-msvc15")) {
+  mkdir -p "build-sdl-msvc15"
+}
+
+cd build-sdl-msvc15
+
+cmake -G "Visual Studio 15 2017 Win64" -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DSTONE_SOURCES_DIR="$($pwd)\..\..\..\.." -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\..\..\..\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON .. 
+
+if (!$?) {
+	Write-Error 'cmake configuration failed' -ErrorAction Stop
+}
+
+cmake --build . --target RtViewerDemo --config Debug
+
+if (!$?) {
+	Write-Error 'cmake build failed' -ErrorAction Stop
+}
+
+cd Debug
+
+.\RtViewerDemo.exe --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/build-wasm.sh	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# usage:
+# build-wasm BUILD_TYPE
+# where BUILD_TYPE is Debug, RelWithDebInfo or Release
+
+set -e
+
+buildType=${1:-Debug}
+
+currentDir=$(pwd)
+currentDirAbs=$(realpath $currentDir)
+
+mkdir -p build-wasm
+cd build-wasm
+
+source ~/apps/emsdk/emsdk_env.sh
+cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \
+-DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDirAbs/../../../../orthanc-stone \
+-DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDirAbs/../../../../orthanc \
+-DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON
+
+ninja $target
+
+echo "-- building the web application -- "
+cd $currentDir
+./build-web.sh
+
+echo "Launch start-serving-files.sh to access the web sample application locally"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/build-web.sh	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+set -e
+
+target=${1:-all}
+# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/
+
+currentDir=$(pwd)
+samplesRootDir=$(pwd)
+
+tscOutput=$samplesRootDir/build-tsc-output/
+outputDir=$samplesRootDir/build-web/
+mkdir -p "$outputDir"
+
+# files used by all single files samples
+cp "$samplesRootDir/index.html" "$outputDir"
+cp "$samplesRootDir/samples-styles.css" "$outputDir"
+
+# build rt-viewer-demo
+cp $samplesRootDir/rt-viewer-demo.html $outputDir
+tsc --project $samplesRootDir/rt-viewer-demo.tsconfig.json --outDir "$tscOutput"
+browserify \
+    "$tscOutput/orthanc-stone/Platforms/Wasm/logger.js" \
+    "$tscOutput/orthanc-stone/Platforms/Wasm/stone-framework-loader.js" \
+    "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-application-runner.js" \
+    "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-viewport.js" \
+    "$tscOutput/rt-viewer-sample/rt-viewer-demo.js" \
+    -o "$outputDir/app-rt-viewer-demo.js"
+cp "$currentDir/build-wasm/RtViewerDemo.js"  $outputDir
+cp "$currentDir/build-wasm/RtViewerDemo.wasm"  $outputDir
+
+cd $currentDir
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/index.html	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,20 @@
+<!doctype html>
+
+<html lang="us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+
+    <title>Wasm Samples</title>
+
+<body>
+    <ul>
+      <li><a href="rt-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9">RTSTRUCT + CT + RTDOSE viewer demo. Pplease replace the url arguments with suitable IDs (you can find those in the Orthanc Explorer, for instance)</a></li>
+    </ul>
+</body>
+
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/main.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,853 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "Applications/IStoneApplication.h"
+#include "Framework/Widgets/WorldSceneWidget.h"
+#include "Framework/Widgets/LayoutWidget.h"
+
+#if ORTHANC_ENABLE_WASM==1
+  #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h"
+  #include "Platforms/Wasm/Defaults.h"
+  #include "Platforms/Wasm/WasmViewport.h"
+#endif
+
+#if ORTHANC_ENABLE_QT==1
+  #include "Qt/SampleMainWindow.h"
+  #include "Qt/SampleMainWindowWithButtons.h"
+#endif
+
+#include "Framework/Layers/DicomSeriesVolumeSlicer.h"
+#include "Framework/Widgets/SliceViewerWidget.h"
+#include "Framework/Volumes/StructureSetLoader.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageTraits.h>
+
+#include <boost/math/constants/constants.hpp>
+#include "Framework/dev.h"
+#include "Framework/Widgets/LayoutWidget.h"
+#include "Framework/Layers/DicomStructureSetSlicer.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class RtViewerDemoBaseApplication : public IStoneApplication
+    {
+    protected:
+      // ownership is transferred to the application context
+#ifndef RESTORE_NON_RTVIEWERDEMO_BEHAVIOR
+      LayoutWidget*          mainWidget_;
+#else
+      WorldSceneWidget*  mainWidget_;
+#endif
+
+    public:
+      virtual void Initialize(StoneApplicationContext* context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE
+      {
+      }
+
+      virtual std::string GetTitle() const ORTHANC_OVERRIDE
+      {
+        return "Stone of Orthanc - Sample";
+      }
+
+      /**
+       * In the basic samples, the commands are handled by the platform adapter and NOT
+       * by the application handler
+      */
+      virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {};
+
+
+      virtual void Finalize() ORTHANC_OVERRIDE {}
+      virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;}
+
+#if ORTHANC_ENABLE_WASM==1
+      // default implementations for a single canvas named "canvas" in the HTML and an empty WasmApplicationAdapter
+
+      virtual void InitializeWasm() ORTHANC_OVERRIDE
+      {
+        AttachWidgetToWasmViewport("canvas", mainWidget_);
+      }
+
+      virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker)
+      {
+        return new WasmPlatformApplicationAdapter(broker, *this);
+      }
+#endif
+
+    };
+
+    // this application actually works in Qt and WASM
+    class RtViewerDemoBaseSingleCanvasWithButtonsApplication : public RtViewerDemoBaseApplication
+    {
+public:
+      virtual void OnPushButton1Clicked() {}
+      virtual void OnPushButton2Clicked() {}
+      virtual void OnTool1Clicked() {}
+      virtual void OnTool2Clicked() {}
+
+      virtual void GetButtonNames(std::string& pushButton1,
+                                  std::string& pushButton2,
+                                  std::string& tool1,
+                                  std::string& tool2
+                                  ) {
+        pushButton1 = "action1";
+        pushButton2 = "action2";
+        tool1 = "tool1";
+        tool2 = "tool2";
+      }
+
+#if ORTHANC_ENABLE_QT==1
+      virtual QStoneMainWindow* CreateQtMainWindow() {
+        return new SampleMainWindowWithButtons(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this);
+      }
+#endif
+
+    };
+
+    // this application actually works in SDL and WASM
+    class RtViewerDemoBaseApplicationSingleCanvas : public RtViewerDemoBaseApplication
+    {
+public:
+
+#if ORTHANC_ENABLE_QT==1
+      virtual QStoneMainWindow* CreateQtMainWindow() {
+        return new SampleMainWindow(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this);
+      }
+#endif
+    };
+  }
+}
+
+
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    template <Orthanc::PixelFormat T>
+    void ReadDistributionInternal(std::vector<float>& distribution,
+                                  const Orthanc::ImageAccessor& image)
+    {
+      const unsigned int width = image.GetWidth();
+      const unsigned int height = image.GetHeight();
+      
+      distribution.resize(width * height);
+      size_t pos = 0;
+
+      for (unsigned int y = 0; y < height; y++)
+      {
+        for (unsigned int x = 0; x < width; x++, pos++)
+        {
+          distribution[pos] = Orthanc::ImageTraits<T>::GetFloatPixel(image, x, y);
+        }
+      }
+    }
+
+    void ReadDistribution(std::vector<float>& distribution,
+                          const Orthanc::ImageAccessor& image)
+    {
+      switch (image.GetFormat())
+      {
+        case Orthanc::PixelFormat_Grayscale8:
+          ReadDistributionInternal<Orthanc::PixelFormat_Grayscale8>(distribution, image);
+          break;
+
+        case Orthanc::PixelFormat_Grayscale16:
+          ReadDistributionInternal<Orthanc::PixelFormat_Grayscale16>(distribution, image);
+          break;
+
+        case Orthanc::PixelFormat_SignedGrayscale16:
+          ReadDistributionInternal<Orthanc::PixelFormat_SignedGrayscale16>(distribution, image);
+          break;
+
+        case Orthanc::PixelFormat_Grayscale32:
+          ReadDistributionInternal<Orthanc::PixelFormat_Grayscale32>(distribution, image);
+          break;
+
+        case Orthanc::PixelFormat_Grayscale64:
+          ReadDistributionInternal<Orthanc::PixelFormat_Grayscale64>(distribution, image);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+
+
+    class DoseInteractor : public VolumeImageInteractor
+    {
+    private:
+      SliceViewerWidget&         widget_;
+      size_t               layer_;
+      DicomFrameConverter  converter_;
+
+
+      
+    protected:
+      virtual void NotifySliceChange(const ISlicedVolume& slicedVolume,
+                                    const size_t& sliceIndex,
+                                    const Slice& slice)
+      {
+        converter_ = slice.GetConverter();
+        
+  #if 0
+        const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume);
+
+        RenderStyle s = widget_.GetLayerStyle(layer_);
+
+        if (volume.FitWindowingToRange(s, slice.GetConverter()))
+        {
+          printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);
+          widget_.SetLayerStyle(layer_, s);
+        }
+  #endif
+      }
+
+      virtual void NotifyVolumeReady(const ISlicedVolume& slicedVolume)
+      {
+        const float percentile = 0.01f;
+        const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume);
+
+        std::vector<float> distribution;
+        ReadDistribution(distribution, volume.GetImage().GetInternalImage());
+        std::sort(distribution.begin(), distribution.end());
+
+        int start = static_cast<int>(std::ceil(distribution.size() * percentile));
+        int end = static_cast<int>(std::floor(distribution.size() * (1.0f - percentile)));
+
+        float a = 0;
+        float b = 0;
+        
+        if (start < end &&
+            start >= 0 &&
+            end < static_cast<int>(distribution.size()))
+        {
+          a = distribution[start];
+          b = distribution[end];
+        }
+        else if (!distribution.empty())
+        {
+          // Too small distribution: Use full range
+          a = distribution.front();
+          b = distribution.back();
+        }
+
+        //printf("%f %f\n", a, b);
+
+        RenderStyle s = widget_.GetLayerStyle(layer_);
+        s.windowing_ = ImageWindowing_Custom;
+        s.customWindowCenter_ = static_cast<float>(converter_.Apply((a + b) / 2.0f));
+        s.customWindowWidth_ = static_cast<float>(converter_.Apply(b - a));
+        
+        // 96.210556 => 192.421112
+        widget_.SetLayerStyle(layer_, s);
+        printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);      
+      }
+
+    public:
+      DoseInteractor(MessageBroker& broker, OrthancVolumeImage& volume,
+                    SliceViewerWidget& widget,
+                    VolumeProjection projection,
+                    size_t layer) :
+        VolumeImageInteractor(broker, volume, widget, projection),
+        widget_(widget),
+        layer_(layer)
+      {
+      }
+    };
+
+    class RtViewerDemoApplication :
+      public RtViewerDemoBaseApplicationSingleCanvas,
+      public IObserver
+    {
+    public:
+      std::vector<std::pair<SliceViewerWidget*, size_t> > doseCtWidgetLayerPairs_;
+      std::list<OrthancStone::IWorldSceneInteractor*>    interactors_;
+
+      class Interactor : public IWorldSceneInteractor
+      {
+      private:
+        RtViewerDemoApplication&  application_;
+
+      public:
+        Interactor(RtViewerDemoApplication&  application) :
+          application_(application)
+        {
+        }
+
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+          const ViewportGeometry& view,
+          MouseButton button,
+          KeyboardModifiers modifiers,
+          int viewportX,
+          int viewportY,
+          double x,
+          double y,
+          IStatusBar* statusBar,
+          const std::vector<Touch>& displayTouches)
+        {
+          return NULL;
+        }
+
+        virtual void MouseOver(CairoContext& context,
+          WorldSceneWidget& widget,
+          const ViewportGeometry& view,
+          double x,
+          double y,
+          IStatusBar* statusBar)
+        {
+          if (statusBar != NULL)
+          {
+            Vector p = dynamic_cast<SliceViewerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+
+            char buf[64];
+            sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
+              p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+            statusBar->SetMessage(buf);
+          }
+        }
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+          MouseWheelDirection direction,
+          KeyboardModifiers modifiers,
+          IStatusBar* statusBar)
+        {
+          int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1);
+
+          switch (direction)
+          {
+          case MouseWheelDirection_Up:
+            application_.OffsetSlice(-scale);
+            break;
+
+          case MouseWheelDirection_Down:
+            application_.OffsetSlice(scale);
+            break;
+
+          default:
+            break;
+          }
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+          KeyboardKeys key,
+          char keyChar,
+          KeyboardModifiers modifiers,
+          IStatusBar* statusBar)
+        {
+          switch (keyChar)
+          {
+          case 's':
+            // TODO: recursively traverse children
+            widget.FitContent();
+            break;
+
+          default:
+            break;
+          }
+        }
+      };
+
+      void OffsetSlice(int offset)
+      {
+        if (source_ != NULL)
+        {
+          int slice = static_cast<int>(slice_) + offset;
+
+          if (slice < 0)
+          {
+            slice = 0;
+          }
+
+          if (slice >= static_cast<int>(source_->GetSliceCount()))
+          {
+            slice = static_cast<int>(source_->GetSliceCount()) - 1;
+          }
+
+          if (slice != static_cast<int>(slice_))
+          {
+            SetSlice(slice);
+          }
+        }
+      }
+
+
+      SliceViewerWidget& GetMainWidget()
+      {
+        return *dynamic_cast<SliceViewerWidget*>(mainWidget_);
+      }
+
+
+      void SetSlice(size_t index)
+      {
+        if (source_ != NULL &&
+          index < source_->GetSliceCount())
+        {
+          slice_ = static_cast<unsigned int>(index);
+
+#if 1
+          GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry());
+#else
+          // TEST for scene extents - Rotate the axes
+          double a = 15.0 / 180.0 * boost::math::constants::pi<double>();
+
+#if 1
+          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+          Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0);
+#else
+          // Flip the normal
+          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+          Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0);
+#endif
+
+          SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y);
+          widget_->SetSlice(s);
+#endif
+        }
+      }
+
+
+      void OnMainWidgetGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message)
+      {
+        // Once the geometry of the series is downloaded from Orthanc,
+        // display its middle slice, and adapt the viewport to fit this
+        // slice
+        if (source_ == &message.GetOrigin())
+        {
+          SetSlice(source_->GetSliceCount() / 2);
+        }
+
+        GetMainWidget().FitContent();
+      }
+
+    DicomFrameConverter                                converter_;
+
+    void OnSliceContentChangedMessage(const ISlicedVolume::SliceContentChangedMessage&   message)
+    {
+      converter_ = message.GetSlice().GetConverter();
+    }
+
+    void OnVolumeReadyMessage(const ISlicedVolume::VolumeReadyMessage& message)
+    {
+      const float percentile = 0.01f;
+
+      auto& slicedVolume = message.GetOrigin();
+      const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume);
+
+      std::vector<float> distribution;
+      ReadDistribution(distribution, volume.GetImage().GetInternalImage());
+      std::sort(distribution.begin(), distribution.end());
+
+      int start = static_cast<int>(std::ceil(distribution.size() * percentile));
+      int end = static_cast<int>(std::floor(distribution.size() * (1.0f - percentile)));
+
+      float a = 0;
+      float b = 0;
+
+      if (start < end &&
+        start >= 0 &&
+        end < static_cast<int>(distribution.size()))
+      {
+        a = distribution[start];
+        b = distribution[end];
+      }
+      else if (!distribution.empty())
+      {
+        // Too small distribution: Use full range
+        a = distribution.front();
+        b = distribution.back();
+      }
+
+      //printf("WINDOWING %f %f\n", a, b);
+
+      for (const auto& pair : doseCtWidgetLayerPairs_)
+      {
+        auto widget = pair.first;
+        auto layer = pair.second;
+        RenderStyle s = widget->GetLayerStyle(layer);
+        s.windowing_ = ImageWindowing_Custom;
+        s.customWindowCenter_ = static_cast<float>(converter_.Apply((a + b) / 2.0f));
+        s.customWindowWidth_ = static_cast<float>(converter_.Apply(b - a));
+
+        // 96.210556 => 192.421112
+        widget->SetLayerStyle(layer, s);
+        printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);
+      }
+    }
+      
+
+
+      size_t AddDoseLayer(SliceViewerWidget& widget,
+        OrthancVolumeImage& volume, VolumeProjection projection);
+
+      void AddStructLayer(
+        SliceViewerWidget& widget, StructureSetLoader& loader);
+
+      SliceViewerWidget* CreateDoseCtWidget(
+        std::auto_ptr<OrthancVolumeImage>& ct,
+        std::auto_ptr<OrthancVolumeImage>& dose,
+        std::auto_ptr<StructureSetLoader>& structLoader,
+        VolumeProjection projection);
+
+      void AddCtLayer(SliceViewerWidget& widget, OrthancVolumeImage& volume);
+
+      std::auto_ptr<Interactor>         mainWidgetInteractor_;
+      const DicomSeriesVolumeSlicer*    source_;
+      unsigned int                      slice_;
+
+      std::string                                        ctSeries_;
+      std::string                                        doseInstance_;
+      std::string                                        doseSeries_;
+      std::string                                        structInstance_;
+      std::auto_ptr<OrthancStone::OrthancVolumeImage>    dose_;
+      std::auto_ptr<OrthancStone::OrthancVolumeImage>    ct_;
+      std::auto_ptr<OrthancStone::StructureSetLoader>    struct_;
+
+    public:
+      RtViewerDemoApplication(MessageBroker& broker) :
+        IObserver(broker),
+        source_(NULL),
+        slice_(0)
+      {
+      }
+
+      /*
+      dev options on bgo xps15
+
+      COMMAND LINE
+      --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+
+      URL PARAMETERS
+      ?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+
+      */
+
+      void ParseParameters(const boost::program_options::variables_map&  parameters)
+      {
+        // CT series
+        {
+
+          if (parameters.count("ct-series") != 1)
+          {
+            LOG(ERROR) << "There must be exactly one CT series specified";
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          }
+          ctSeries_ = parameters["ct-series"].as<std::string>();
+        }
+
+        // RTDOSE 
+        {
+          if (parameters.count("dose-instance") == 1)
+          {
+            doseInstance_ = parameters["dose-instance"].as<std::string>();
+          }
+          else
+          {
+#ifdef BGO_NOT_IMPLEMENTED_YET
+            // Dose series
+            if (parameters.count("dose-series") != 1)
+            {
+              LOG(ERROR) << "the RTDOSE series is missing";
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+            }
+            doseSeries_ = parameters["ct"].as<std::string>();
+#endif
+            LOG(ERROR) << "the RTSTRUCT instance is missing";
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          }
+        }
+        
+        // RTSTRUCT 
+        {
+          if (parameters.count("struct-instance") == 1)
+          {
+            structInstance_ = parameters["struct-instance"].as<std::string>();
+          }
+          else
+          {
+#ifdef BGO_NOT_IMPLEMENTED_YET
+            // Struct series
+            if (parameters.count("struct-series") != 1)
+            {
+              LOG(ERROR) << "the RTSTRUCT series is missing";
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+            }
+            structSeries_ = parametersstruct - series"].as<std::string>();
+#endif
+            LOG(ERROR) << "the RTSTRUCT instance is missing";
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          }
+        }
+      }
+
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("RtViewerDemo options. Please note that some of these options are mutually exclusive");
+        generic.add_options()
+          ("ct-series", boost::program_options::value<std::string>(),"Orthanc ID of the CT series")
+          ("dose-instance", boost::program_options::value<std::string>(), "Orthanc ID of the RTDOSE instance (incompatible with dose-series)")
+          ("dose-series", boost::program_options::value<std::string>(), "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible with dose-instance)")
+          ("struct-instance", boost::program_options::value<std::string>(), "Orthanc ID of the RTSTRUCT instance (incompatible with struct-series)")
+          ("struct-series", boost::program_options::value<std::string>(), "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with struct-instance)")
+          ("smooth", boost::program_options::value<bool>()->default_value(true),"Enable bilinear image smoothing")
+          ;
+
+        options.add(generic);
+      }
+
+      virtual void Initialize(
+        StoneApplicationContext*                      context,
+        IStatusBar&                                   statusBar,
+        const boost::program_options::variables_map&  parameters)
+      {
+        using namespace OrthancStone;
+
+        ParseParameters(parameters);
+
+        context_ = context;
+
+        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
+
+        if (!ctSeries_.empty())
+        {
+          printf("CT = [%s]\n", ctSeries_.c_str());
+
+          ct_.reset(new OrthancStone::OrthancVolumeImage(
+            IObserver::GetBroker(), context->GetOrthancApiClient(), false));
+          ct_->ScheduleLoadSeries(ctSeries_);
+          //ct_->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // IBA
+          //ct_->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0");  // 0522c0001 TCIA
+        }
+
+        if (!doseSeries_.empty() ||
+          !doseInstance_.empty())
+        {
+          dose_.reset(new OrthancStone::OrthancVolumeImage(
+            IObserver::GetBroker(), context->GetOrthancApiClient(), true));
+
+
+          dose_->RegisterObserverCallback(
+            new Callable<RtViewerDemoApplication, ISlicedVolume::VolumeReadyMessage>
+            (*this, &RtViewerDemoApplication::OnVolumeReadyMessage));
+
+          dose_->RegisterObserverCallback(
+            new Callable<RtViewerDemoApplication, ISlicedVolume::SliceContentChangedMessage>
+            (*this, &RtViewerDemoApplication::OnSliceContentChangedMessage));
+
+          if (doseInstance_.empty())
+          {
+            dose_->ScheduleLoadSeries(doseSeries_);
+          }
+          else
+          {
+            dose_->ScheduleLoadInstance(doseInstance_);
+          }
+
+          //dose_->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb");  // IBA 1
+          //dose_->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c");  // 0522c0001 TCIA
+        }
+
+        if (!structInstance_.empty())
+        {
+          struct_.reset(new OrthancStone::StructureSetLoader(
+            IObserver::GetBroker(), context->GetOrthancApiClient()));
+
+          struct_->ScheduleLoadInstance(structInstance_);
+
+          //struct_->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // IBA
+          //struct_->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20");  // 0522c0001 TCIA
+        }
+
+        mainWidget_ = new LayoutWidget("main-layout");
+        mainWidget_->SetBackgroundColor(0, 0, 0);
+        mainWidget_->SetBackgroundCleared(true);
+        mainWidget_->SetPadding(0);
+
+        auto axialWidget = CreateDoseCtWidget
+        (ct_, dose_, struct_, OrthancStone::VolumeProjection_Axial);
+        mainWidget_->AddWidget(axialWidget);
+               
+        std::auto_ptr<OrthancStone::LayoutWidget> subLayout(
+          new OrthancStone::LayoutWidget("main-layout"));
+        subLayout->SetVertical();
+        subLayout->SetPadding(5);
+
+        auto coronalWidget = CreateDoseCtWidget
+        (ct_, dose_, struct_, OrthancStone::VolumeProjection_Coronal);
+        subLayout->AddWidget(coronalWidget);
+
+        auto sagittalWidget = CreateDoseCtWidget
+        (ct_, dose_, struct_, OrthancStone::VolumeProjection_Sagittal);
+        subLayout->AddWidget(sagittalWidget);
+        
+        mainWidget_->AddWidget(subLayout.release());
+      }
+    };
+
+
+    size_t RtViewerDemoApplication::AddDoseLayer(
+      SliceViewerWidget& widget,
+      OrthancVolumeImage& volume, VolumeProjection projection)
+    {
+      size_t layer = widget.AddLayer(
+        new VolumeImageMPRSlicer(IObserver::GetBroker(), volume));
+
+      RenderStyle s;
+      //s.drawGrid_ = true;
+      s.SetColor(255, 0, 0);  // Draw missing PET layer in red
+      s.alpha_ = 0.3f;
+      s.applyLut_ = true;
+      s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
+      s.interpolation_ = ImageInterpolation_Bilinear;
+      widget.SetLayerStyle(layer, s);
+
+      return layer;
+    }
+
+    void RtViewerDemoApplication::AddStructLayer(
+      SliceViewerWidget& widget, StructureSetLoader& loader)
+    {
+      widget.AddLayer(new DicomStructureSetSlicer(
+        IObserver::GetBroker(), loader));
+    }
+
+    SliceViewerWidget* RtViewerDemoApplication::CreateDoseCtWidget(
+      std::auto_ptr<OrthancVolumeImage>& ct,
+      std::auto_ptr<OrthancVolumeImage>& dose,
+      std::auto_ptr<StructureSetLoader>& structLoader,
+      VolumeProjection projection)
+    {
+      std::auto_ptr<OrthancStone::SliceViewerWidget> widget(
+        new OrthancStone::SliceViewerWidget(IObserver::GetBroker(),
+          "ct-dose-widget"));
+
+      if (ct.get() != NULL)
+      {
+        AddCtLayer(*widget, *ct);
+      }
+
+      if (dose.get() != NULL)
+      {
+        size_t layer = AddDoseLayer(*widget, *dose, projection);
+
+        // we need to store the dose rendering widget because we'll update them
+        // according to various asynchronous events
+        doseCtWidgetLayerPairs_.push_back(std::make_pair(widget.get(), layer));
+#if 0
+        interactors_.push_back(new VolumeImageInteractor(
+          IObserver::GetBroker(), *dose, *widget, projection));
+#else
+        interactors_.push_back(new DoseInteractor(
+          IObserver::GetBroker(), *dose, *widget, projection, layer));
+#endif
+      }
+      else if (ct.get() != NULL)
+      {
+        interactors_.push_back(
+          new VolumeImageInteractor(
+            IObserver::GetBroker(), *ct, *widget, projection));
+      }
+
+      if (structLoader.get() != NULL)
+      {
+        AddStructLayer(*widget, *structLoader);
+      }
+
+      return widget.release();
+    }
+
+    void RtViewerDemoApplication::AddCtLayer(
+      SliceViewerWidget& widget,
+      OrthancVolumeImage& volume)
+    {
+      size_t layer = widget.AddLayer(
+        new VolumeImageMPRSlicer(IObserver::GetBroker(), volume));
+
+      RenderStyle s;
+      //s.drawGrid_ = true;
+      s.alpha_ = 1;
+      s.windowing_ = ImageWindowing_Bone;
+      widget.SetLayerStyle(layer, s);
+    }
+  }
+}
+
+
+
+#if ORTHANC_ENABLE_WASM==1
+
+#include "Platforms/Wasm/WasmWebService.h"
+#include "Platforms/Wasm/WasmViewport.h"
+
+#include <emscripten/emscripten.h>
+
+//#include "SampleList.h"
+
+
+OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker)
+{
+  return new OrthancStone::Samples::RtViewerDemoApplication(broker);
+}
+
+OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application)
+{
+  return dynamic_cast<OrthancStone::Samples::RtViewerDemoApplication*>(application)->CreateWasmApplicationAdapter(broker);
+}
+
+#else
+
+//#include "SampleList.h"
+#if ORTHANC_ENABLE_SDL==1
+#include "Applications/Sdl/SdlStoneApplicationRunner.h"
+#endif
+#if ORTHANC_ENABLE_QT==1
+#include "Applications/Qt/SampleQtApplicationRunner.h"
+#endif
+#include "Framework/Messages/MessageBroker.h"
+
+int main(int argc, char* argv[])
+{
+  OrthancStone::MessageBroker broker;
+  OrthancStone::Samples::RtViewerDemoApplication sampleStoneApplication(broker);
+
+#if ORTHANC_ENABLE_SDL==1
+  OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication);
+  return sdlApplicationRunner.Execute(argc, argv);
+#endif
+#if ORTHANC_ENABLE_QT==1
+  OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication);
+  return qtAppRunner.Execute(argc, argv);
+#endif
+}
+
+
+#endif
+
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/nginx.local.conf	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,44 @@
+# Local config to serve the WASM samples static files and reverse proxy Orthanc.
+# Uses port 9977 instead of 80.
+
+# `events` section is mandatory
+events {
+  worker_connections 1024; # Default: 1024
+}
+
+http {
+
+  # prevent nginx sync issues on OSX
+  proxy_buffering off;
+
+  server {
+    listen 9977 default_server;
+    client_max_body_size 4G;
+
+    # location may have to be adjusted depending on your OS and nginx install
+    include /etc/nginx/mime.types;
+    # if not in your system mime.types, add this line to support WASM:
+    # types {
+    #    application/wasm                      wasm; 
+    # }
+
+    # serve WASM static files
+    root build-web/;
+    location / {
+	}
+
+    # reverse proxy orthanc
+	location /orthanc/ {
+		rewrite /orthanc(.*) $1 break;
+		proxy_pass http://127.0.0.1:8042;
+		proxy_set_header Host $http_host;
+		proxy_set_header my-auth-header good-token;
+		proxy_request_buffering off;
+		proxy_max_temp_file_size 0;
+		client_max_body_size 0;
+	}
+
+
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/rt-viewer-demo.html	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,25 @@
+<!doctype html>
+
+<html lang="us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+
+    <title>Simple Viewer</title>
+    <link href="samples-styles.css" rel="stylesheet" />
+
+<body>
+  <div style="width: 100%; height: 5%">
+    <p>RTSTRUCT viewer demonstration</p>
+  </div>
+  <div style="width: 100%; height: 95%">
+    <canvas id="canvas"></canvas>
+  </div>
+  <script type="text/javascript" src="app-rt-viewer-demo.js"></script>
+</body>
+
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/rt-viewer-demo.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,5 @@
+import { InitializeWasmApplication } from '../../../Platforms/Wasm/wasm-application-runner';
+
+
+InitializeWasmApplication("RtViewerDemo", "/orthanc");
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/rt-viewer-demo.tsconfig.json	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,8 @@
+{
+    "extends" : "./tsconfig-samples",
+    "compilerOptions": {
+    },
+    "include" : [
+        "rt-viewer-demo.ts"
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/samples-styles.css	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,16 @@
+html, body {
+  width: 100%;
+  height: 100%;
+  margin: 0px;
+  border: 0;
+  overflow: hidden; /*  Disable scrollbars */
+  display: block;  /* No floating content on sides */
+  background-color: black;
+  color: white;
+  font-family: Arial, Helvetica, sans-serif;
+}
+
+canvas {
+  left:0px;
+  top:0px;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/start-serving-files.sh	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+sudo nginx -p $(pwd) -c nginx.local.conf
+
+echo "Please browse to :"
+
+echo "http://localhost:9977/rt-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"
+
+echo "(This requires you have uploaded the correct files to your local Orthanc instance)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/stop-serving-files.sh	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+sudo nginx -s stop
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/rt-viewer-demo/tsconfig-samples.json	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,11 @@
+{
+    "extends" : "../../../Platforms/Wasm/tsconfig-stone.json",
+    "compilerOptions": {
+        "sourceMap": false,
+        "lib" : [
+            "es2017",
+            "dom",
+            "dom.iterable"
+        ]
+    }
+}
--- a/Applications/Sdl/SdlEngine.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/Sdl/SdlEngine.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -146,15 +146,15 @@
           switch (event.button.button)
           {
           case SDL_BUTTON_LEFT:
-            locker.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers);
+            locker.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers, std::vector<Touch>());
             break;
             
           case SDL_BUTTON_RIGHT:
-            locker.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers);
+            locker.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers, std::vector<Touch>());
             break;
             
           case SDL_BUTTON_MIDDLE:
-            locker.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
+            locker.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers, std::vector<Touch>());
             break;
 
           default:
@@ -163,7 +163,7 @@
         }
         else if (event.type == SDL_MOUSEMOTION)
         {
-          locker.GetCentralViewport().MouseMove(event.button.x, event.button.y);
+          locker.GetCentralViewport().MouseMove(event.button.x, event.button.y, std::vector<Touch>());
         }
         else if (event.type == SDL_MOUSEBUTTONUP)
         {
--- a/Applications/StoneApplicationContext.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Applications/StoneApplicationContext.h	Thu Apr 18 10:43:17 2019 +0200
@@ -27,6 +27,7 @@
 #include "../Framework/Viewport/WidgetViewport.h"
 
 
+  #else
 #include <list>
 
 namespace OrthancStone
--- a/Framework/Layers/CircleMeasureTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Layers/CircleMeasureTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -91,7 +91,9 @@
   void CircleMeasureTracker::MouseMove(int displayX,
                                        int displayY,
                                        double x,
-                                       double y)
+                                       double y,
+                                       const std::vector<Touch>& displayTouches,
+                                       const std::vector<Touch>& sceneTouches)
   {
     x2_ = x;
     y2_ = y;
--- a/Framework/Layers/CircleMeasureTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Layers/CircleMeasureTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -72,6 +72,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Layers/DicomSeriesVolumeSlicer.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -93,10 +93,21 @@
     loader_(broker, orthanc),
     quality_(SliceImageQuality_FullPng)
   {
-    loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady));
-    loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError));
-    loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceImageReady));
-    loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceImageError));
+    loader_.RegisterObserverCallback(
+      new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage>
+        (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady));
+
+    loader_.RegisterObserverCallback(
+      new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage>
+      (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError));
+
+    loader_.RegisterObserverCallback(
+      new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage>
+        (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady));
+
+    loader_.RegisterObserverCallback(
+      new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage>
+      (*this, &DicomSeriesVolumeSlicer::OnSliceImageError));
   }
 
   
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Layers/GrayscaleFrameRenderer.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -121,8 +121,8 @@
                                                  bool isFullQuality) :
     FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality),
     frame_(Orthanc::Image::Clone(frame)),
-    defaultWindowCenter_(converter.GetDefaultWindowCenter()),
-    defaultWindowWidth_(converter.GetDefaultWindowWidth()),
+    defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())),
+    defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())),
     photometric_(converter.GetPhotometricInterpretation())
   {
     if (frame_.get() == NULL)
--- a/Framework/Layers/LineMeasureTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Layers/LineMeasureTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -87,7 +87,9 @@
   void LineMeasureTracker::MouseMove(int displayX,
                                      int displayY,
                                      double x,
-                                     double y)
+                                     double y,
+                                     const std::vector<Touch>& displayTouches,
+                                     const std::vector<Touch>& sceneTouches)
   {
     x2_ = x;
     y2_ = y;
--- a/Framework/Layers/LineMeasureTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Layers/LineMeasureTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -71,6 +71,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyAlphaLayer.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyAlphaLayer.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -45,6 +45,8 @@
 
     SetSize(image->GetWidth(), image->GetHeight());
     alpha_ = raii;
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
   void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer,
@@ -84,7 +86,7 @@
     if (useWindowing_)
     {
       float center, width;
-      if (scene_.GetWindowing(center, width))
+      if (GetScene().GetWindowing(center, width))
       {
         value = center + width / 2.0f;  // set it to the maximum pixel value of the image
       }
--- a/Framework/Radiography/RadiographyAlphaLayer.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyAlphaLayer.h	Thu Apr 18 10:43:17 2019 +0200
@@ -33,14 +33,13 @@
   class RadiographyAlphaLayer : public RadiographyLayer
   {
   private:
-    const RadiographyScene&                scene_;
     std::auto_ptr<Orthanc::ImageAccessor>  alpha_;      // Grayscale8
     bool                                   useWindowing_;
     float                                  foreground_;
 
   public:
-    RadiographyAlphaLayer(const RadiographyScene& scene) :
-      scene_(scene),
+    RadiographyAlphaLayer(MessageBroker& broker, const RadiographyScene& scene) :
+      RadiographyLayer(broker, scene),
       useWindowing_(true),
       foreground_(0)
     {
--- a/Framework/Radiography/RadiographyDicomLayer.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyDicomLayer.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -102,6 +102,8 @@
 
     source_ = raii;
     ApplyConverter();
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
   void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
--- a/Framework/Radiography/RadiographyDicomLayer.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyDicomLayer.h	Thu Apr 18 10:43:17 2019 +0200
@@ -41,6 +41,11 @@
     void ApplyConverter();
 
   public:
+    RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene)
+      : RadiographyLayer(broker, scene)
+    {
+    }
+
     void SetInstance(const std::string& instanceId, unsigned int frame)
     {
       instanceId_ = instanceId;
@@ -61,6 +66,11 @@
 
     void SetSourceImage(Orthanc::ImageAccessor* image);   // Takes ownership
 
+    const Orthanc::ImageAccessor* GetSourceImage() const {return source_.get();}  // currently need this access to serialize scene in plain old data to send to a WASM worker
+
+    const DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker
+    void SetDicomFrameConverter(DicomFrameConverter* converter) {converter_.reset(converter);} // Takes ownership
+
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
                         ImageInterpolation interpolation) const;
--- a/Framework/Radiography/RadiographyLayer.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayer.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -76,58 +76,14 @@
                                      double x,
                                      double y) const
   {
-    transform_.Apply(x, y);
+    GetTransform().Apply(x, y);
     extent.AddPoint(x, y);
   }
 
-
-  void RadiographyLayer::GetCornerInternal(double& x,
-                                           double& y,
-                                           Corner corner,
-                                           unsigned int cropX,
-                                           unsigned int cropY,
-                                           unsigned int cropWidth,
-                                           unsigned int cropHeight) const
-  {
-    double dx = static_cast<double>(cropX);
-    double dy = static_cast<double>(cropY);
-    double dwidth = static_cast<double>(cropWidth);
-    double dheight = static_cast<double>(cropHeight);
-
-    switch (corner)
-    {
-    case Corner_TopLeft:
-      x = dx;
-      y = dy;
-      break;
-
-    case Corner_TopRight:
-      x = dx + dwidth;
-      y = dy;
-      break;
-
-    case Corner_BottomLeft:
-      x = dx;
-      y = dy + dheight;
-      break;
-
-    case Corner_BottomRight:
-      x = dx + dwidth;
-      y = dy + dheight;
-      break;
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    transform_.Apply(x, y);
-  }
-
-
   bool RadiographyLayer::Contains(double x,
                                   double y) const
   {
-    transformInverse_.Apply(x, y);
+    GetTransformInverse().Apply(x, y);
 
     unsigned int cropX, cropY, cropWidth, cropHeight;
     GetCrop(cropX, cropY, cropWidth, cropHeight);
@@ -140,53 +96,35 @@
   void RadiographyLayer::DrawBorders(CairoContext& context,
                                      double zoom)
   {
-    unsigned int cx, cy, width, height;
-    GetCrop(cx, cy, width, height);
-
-    double dx = static_cast<double>(cx);
-    double dy = static_cast<double>(cy);
-    double dwidth = static_cast<double>(width);
-    double dheight = static_cast<double>(height);
+    if (GetControlPointCount() < 3 )
+      return;
 
     cairo_t* cr = context.GetObject();
     cairo_set_line_width(cr, 2.0 / zoom);
 
-    double x, y;
-    x = dx;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_move_to(cr, x, y);
-
-    x = dx + dwidth;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
+    ControlPoint cp;
+    GetControlPoint(cp, 0);
+    cairo_move_to(cr, cp.x, cp.y);
 
-    x = dx + dwidth;
-    y = dy + dheight;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
+    for (size_t i = 0; i < GetControlPointCount(); i++)
+    {
+      GetControlPoint(cp, i);
+      cairo_line_to(cr, cp.x, cp.y);
+    }
 
-    x = dx;
-    y = dy + dheight;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
-    x = dx;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
+    cairo_close_path(cr);
     cairo_stroke(cr);
   }
 
 
-  RadiographyLayer::RadiographyLayer() :
+  RadiographyLayer::RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene) :
+    IObservable(broker),
     index_(0),
     hasSize_(false),
     width_(0),
     height_(0),
-    prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default)
+    prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default),
+    scene_(scene)
   {
     UpdateTransform();
   }
@@ -197,6 +135,13 @@
     UpdateTransform();
   }
 
+  void RadiographyLayer::SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode  prefferedPhotometricDisplayMode)
+  {
+    prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode;
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
   void RadiographyLayer::SetCrop(unsigned int x,
                                  unsigned int y,
                                  unsigned int width,
@@ -215,6 +160,8 @@
 
     geometry_.SetCrop(x, y, width, height);
     UpdateTransform();
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
   void RadiographyLayer::SetGeometry(const Geometry& geometry)
@@ -225,6 +172,8 @@
     {
       UpdateTransform();
     }
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
 
@@ -237,7 +186,7 @@
     {
       GetGeometry().GetCrop(x, y, width, height);
     }
-    else 
+    else
     {
       x = 0;
       y = 0;
@@ -251,6 +200,8 @@
   {
     geometry_.SetAngle(angle);
     UpdateTransform();
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
 
@@ -269,6 +220,7 @@
     height_ = height;
 
     UpdateTransform();
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
 
@@ -305,7 +257,7 @@
     }
     else
     {
-      transformInverse_.Apply(sceneX, sceneY);
+      GetTransformInverse().Apply(sceneX, sceneY);
 
       int x = static_cast<int>(std::floor(sceneX));
       int y = static_cast<int>(std::floor(sceneY));
@@ -346,6 +298,7 @@
   {
     geometry_.SetPan(x, y);
     UpdateTransform();
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
 
@@ -354,6 +307,7 @@
   {
     geometry_.SetPixelSpacing(x, y);
     UpdateTransform();
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
   }
 
 
@@ -362,48 +316,65 @@
   {
     centerX = static_cast<double>(width_) / 2.0;
     centerY = static_cast<double>(height_) / 2.0;
-    transform_.Apply(centerX, centerY);
-  }
-
-
-  void RadiographyLayer::GetCorner(double& x /* out */,
-                                   double& y /* out */,
-                                   Corner corner) const
-  {
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-    GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight);
+    GetTransform().Apply(centerX, centerY);
   }
 
 
-  bool RadiographyLayer::LookupCorner(Corner& corner /* out */,
-                                      double x,
-                                      double y,
-                                      double zoom,
-                                      double viewportDistance) const
+
+  size_t RadiographyLayer::GetControlPointCount() const {return 4;}
+
+  void RadiographyLayer::GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */,
+                                         size_t index) const
   {
-    static const Corner CORNERS[] = {
-      Corner_TopLeft,
-      Corner_TopRight,
-      Corner_BottomLeft,
-      Corner_BottomRight
-    };
-
     unsigned int cropX, cropY, cropWidth, cropHeight;
     GetCrop(cropX, cropY, cropWidth, cropHeight);
 
+    ControlPoint cp;
+    switch (index)
+    {
+    case ControlPoint_TopLeftCorner:
+      cp = ControlPoint(cropX, cropY, ControlPoint_TopLeftCorner);
+      break;
+
+    case ControlPoint_TopRightCorner:
+      cp = ControlPoint(cropX + cropWidth, cropY, ControlPoint_TopRightCorner);
+      break;
+
+    case ControlPoint_BottomLeftCorner:
+      cp = ControlPoint(cropX, cropY + cropHeight, ControlPoint_BottomLeftCorner);
+      break;
+
+    case ControlPoint_BottomRightCorner:
+      cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, ControlPoint_BottomRightCorner);
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    // transforms image coordinates into scene coordinates
+    GetTransform().Apply(cp.x, cp.y);
+    cpScene = cp;
+  }
+
+  bool RadiographyLayer::LookupControlPoint(ControlPoint& cpScene /* out */,
+                                            double x,
+                                            double y,
+                                            double zoom,
+                                            double viewportDistance) const
+  {
     double threshold = Square(viewportDistance / zoom);
 
-    for (size_t i = 0; i < 4; i++)
+    for (size_t i = 0; i < GetControlPointCount(); i++)
     {
-      double cx, cy;
-      GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight);
+      ControlPoint cp;
+      GetControlPoint(cp, i);
 
-      double d = Square(cx - x) + Square(cy - y);
+      double d = Square(cp.x - x) + Square(cp.y - y);
 
       if (d <= threshold)
       {
-        corner = CORNERS[i];
+        cpScene = cp;
         return true;
       }
     }
--- a/Framework/Radiography/RadiographyLayer.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayer.h	Thu Apr 18 10:43:17 2019 +0200
@@ -24,14 +24,50 @@
 #include "../Toolbox/AffineTransform2D.h"
 #include "../Toolbox/Extent2D.h"
 #include "../Viewport/CairoContext.h"
+#include "../Messages/IMessage.h"
+#include "../Messages/IObservable.h"
 
 namespace OrthancStone
 {
-  class RadiographyLayer : public boost::noncopyable
+  class RadiographyScene;
+
+  struct ControlPoint
+  {
+    double x;
+    double y;
+    size_t index;
+
+    ControlPoint(double x, double y, size_t index)
+      : x(x),
+        y(y),
+        index(index)
+    {}
+
+    ControlPoint()
+      : x(0),
+        y(0),
+        index(std::numeric_limits<size_t>::max())
+    {}
+  };
+
+  class RadiographyLayer : public IObservable
   {
     friend class RadiographyScene;
 
   public:
+    class LayerEditedMessage :
+        public OriginMessage<MessageType_RadiographyLayer_Edited, RadiographyLayer>
+    {
+    private:
+
+    public:
+      LayerEditedMessage(const RadiographyLayer& origin) :
+        OriginMessage(origin)
+      {
+      }
+    };
+
+
     class Geometry
     {
       bool               hasCrop_;
@@ -141,47 +177,41 @@
     AffineTransform2D  transformInverse_;
     Geometry           geometry_;
     PhotometricDisplayMode  prefferedPhotometricDisplayMode_;
-
+    const RadiographyScene&   scene_;
 
   protected:
-    const AffineTransform2D& GetTransform() const
+    virtual const AffineTransform2D& GetTransform() const
     {
       return transform_;
     }
 
-    void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode  prefferedPhotometricDisplayMode)
+    virtual const AffineTransform2D& GetTransformInverse() const
     {
-      prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode;
+      return transformInverse_;
     }
 
+    void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode  prefferedPhotometricDisplayMode);
+
   private:
     void UpdateTransform();
-      
+
     void AddToExtent(Extent2D& extent,
                      double x,
                      double y) const;
 
-    void GetCornerInternal(double& x,
-                           double& y,
-                           Corner corner,
-                           unsigned int cropX,
-                           unsigned int cropY,
-                           unsigned int cropWidth,
-                           unsigned int cropHeight) const;
-
     void SetIndex(size_t index)
     {
       index_ = index;
     }
-      
+
     bool Contains(double x,
                   double y) const;
-      
+
     void DrawBorders(CairoContext& context,
                      double zoom);
 
   public:
-    RadiographyLayer();
+    RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene);
 
     virtual ~RadiographyLayer()
     {
@@ -192,6 +222,11 @@
       return index_;
     }
 
+    const RadiographyScene& GetScene() const
+    {
+      return scene_;
+    }
+
     const Geometry& GetGeometry() const
     {
       return geometry_;
@@ -232,19 +267,19 @@
     unsigned int GetWidth() const
     {
       return width_;
-    }        
+    }
 
     unsigned int GetHeight() const
     {
       return height_;
-    }       
+    }
 
     Extent2D GetExtent() const;
 
-    bool GetPixel(unsigned int& imageX,
-                  unsigned int& imageY,
-                  double sceneX,
-                  double sceneY) const;
+    virtual bool GetPixel(unsigned int& imageX,
+                          unsigned int& imageY,
+                          double sceneX,
+                          double sceneY) const;
 
     void SetPixelSpacing(double x,
                          double y);
@@ -252,15 +287,16 @@
     void GetCenter(double& centerX,
                    double& centerY) const;
 
-    void GetCorner(double& x /* out */,
-                   double& y /* out */,
-                   Corner corner) const;
-      
-    bool LookupCorner(Corner& corner /* out */,
-                      double x,
-                      double y,
-                      double zoom,
-                      double viewportDistance) const;
+    virtual void GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */,
+                                 size_t index) const;
+
+    virtual size_t GetControlPointCount() const;
+
+    bool LookupControlPoint(ControlPoint& cpScene /* out */,
+                            double x,
+                            double y,
+                            double zoom,
+                            double viewportDistance) const;
 
     virtual bool GetDefaultWindowing(float& center,
                                      float& width) const = 0;
@@ -276,5 +312,7 @@
 
     virtual bool GetRange(float& minValue,
                           float& maxValue) const = 0;
-  }; 
+
+    friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to
+  };
 }
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -68,12 +68,10 @@
                                                            RadiographyScene& scene,
                                                            const ViewportGeometry& view,
                                                            size_t layer,
-                                                           double x,
-                                                           double y,
-                                                           Corner corner) :
+                                                           const ControlPoint& startControlPoint) :
     undoRedoStack_(undoRedoStack),
     accessor_(scene, layer),
-    corner_(corner)
+    startControlPoint_(startControlPoint)
   {
     if (accessor_.IsValid())
     {
@@ -101,7 +99,9 @@
   void RadiographyLayerCropTracker::MouseMove(int displayX,
                                               int displayY,
                                               double sceneX,
-                                              double sceneY)
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
   {
     if (accessor_.IsValid())
     {
@@ -112,8 +112,8 @@
       {
         unsigned int targetX, targetWidth;
 
-        if (corner_ == Corner_TopLeft ||
-            corner_ == Corner_BottomLeft)
+        if (startControlPoint_.index == ControlPoint_TopLeftCorner ||
+            startControlPoint_.index == ControlPoint_BottomLeftCorner)
         {
           targetX = std::min(x, cropX_ + cropWidth_);
           targetWidth = cropX_ + cropWidth_ - targetX;
@@ -126,8 +126,8 @@
 
         unsigned int targetY, targetHeight;
 
-        if (corner_ == Corner_TopLeft ||
-            corner_ == Corner_TopRight)
+        if (startControlPoint_.index == ControlPoint_TopLeftCorner ||
+            startControlPoint_.index == ControlPoint_TopRightCorner)
         {
           targetY = std::min(y, cropY_ + cropHeight_);
           targetHeight = cropY_ + cropHeight_ - targetY;
--- a/Framework/Radiography/RadiographyLayerCropTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerCropTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -35,7 +35,7 @@
 
     UndoRedoStack&                   undoRedoStack_;
     RadiographyScene::LayerAccessor  accessor_;
-    Corner                           corner_;
+    ControlPoint                     startControlPoint_;
     unsigned int                     cropX_;
     unsigned int                     cropY_;
     unsigned int                     cropWidth_;
@@ -46,9 +46,7 @@
                                 RadiographyScene& scene,
                                 const ViewportGeometry& view,
                                 size_t layer,
-                                double x,
-                                double y,
-                                Corner corner);
+                                const ControlPoint& startControlPoint);
 
     virtual bool HasRender() const
     {
@@ -63,6 +61,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayerMaskTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,140 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyLayerMaskTracker.h"
+#include "RadiographyMaskLayer.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class RadiographyLayerMaskTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    ControlPoint sourceSceneCp_;
+    ControlPoint targetSceneCp_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
+      if (maskLayer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      unsigned int ix, iy; // image coordinates
+      if (maskLayer->GetPixel(ix, iy, sourceSceneCp_.x, sourceSceneCp_.y))
+      {
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), sourceSceneCp_.index);
+      }
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
+      if (maskLayer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      unsigned int ix, iy; // image coordinates
+      if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y))
+      {
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index);
+      }
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerMaskTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceSceneCp_(tracker.startSceneCp_),
+      targetSceneCp_(tracker.endSceneCp_)
+    {
+      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&(tracker.accessor_.GetLayer()));
+      if (maskLayer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      unsigned int ix, iy; // image coordinates
+      if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y))
+      {
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index);
+      }
+    }
+  };
+
+
+  RadiographyLayerMaskTracker::RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           const ViewportGeometry& view,
+                                                           size_t layer,
+                                                           const ControlPoint& startSceneControlPoint) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    startSceneCp_(startSceneControlPoint),
+    endSceneCp_(startSceneControlPoint)
+  {
+  }
+
+
+  void RadiographyLayerMaskTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerMaskTracker::MouseUp()
+  {
+    if (accessor_.IsValid() && startSceneCp_.x != endSceneCp_.x && startSceneCp_.y != endSceneCp_.y)
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerMaskTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
+  {
+    if (accessor_.IsValid())
+    {
+      unsigned int ix, iy; // image coordinates
+
+      RadiographyLayer& layer = accessor_.GetLayer();
+      if (layer.GetPixel(ix, iy, sceneX, sceneY))
+      {
+        endSceneCp_ = ControlPoint(sceneX, sceneY, startSceneCp_.index);
+
+        RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
+        if (maskLayer == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), startSceneCp_.index);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayerMaskTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/UndoRedoStack.h"
+#include "../Toolbox/ViewportGeometry.h"
+#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerMaskTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    ControlPoint                     startSceneCp_;
+    ControlPoint                     endSceneCp_;
+
+  public:
+    RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                const ViewportGeometry& view,
+                                size_t layer,
+                                const ControlPoint& startSceneControlPoint);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- a/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -97,7 +97,9 @@
   void RadiographyLayerMoveTracker::MouseMove(int displayX,
                                               int displayY,
                                               double sceneX,
-                                              double sceneY)
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
   {
     if (accessor_.IsValid())
     {
--- a/Framework/Radiography/RadiographyLayerMoveTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerMoveTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -61,6 +61,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -85,9 +85,7 @@
   RadiographyLayerResizeTracker::RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
                                                                RadiographyScene& scene,
                                                                size_t layer,
-                                                               double x,
-                                                               double y,
-                                                               Corner corner,
+                                                               const ControlPoint& startControlPoint,
                                                                bool roundScaling) :
     undoRedoStack_(undoRedoStack),
     accessor_(scene, layer),
@@ -101,31 +99,32 @@
       originalPanX_ = accessor_.GetLayer().GetGeometry().GetPanX();
       originalPanY_ = accessor_.GetLayer().GetGeometry().GetPanY();
 
-      switch (corner)
+      size_t oppositeControlPointType;
+      switch (startControlPoint.index)
       {
-        case Corner_TopLeft:
-          oppositeCorner_ = Corner_BottomRight;
+        case ControlPoint_TopLeftCorner:
+          oppositeControlPointType = ControlPoint_BottomRightCorner;
           break;
 
-        case Corner_TopRight:
-          oppositeCorner_ = Corner_BottomLeft;
+        case ControlPoint_TopRightCorner:
+          oppositeControlPointType = ControlPoint_BottomLeftCorner;
           break;
 
-        case Corner_BottomLeft:
-          oppositeCorner_ = Corner_TopRight;
+        case ControlPoint_BottomLeftCorner:
+          oppositeControlPointType = ControlPoint_TopRightCorner;
           break;
 
-        case Corner_BottomRight:
-          oppositeCorner_ = Corner_TopLeft;
+        case ControlPoint_BottomRightCorner:
+          oppositeControlPointType = ControlPoint_TopLeftCorner;
           break;
 
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
-      accessor_.GetLayer().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
+      accessor_.GetLayer().GetControlPoint(startOppositeControlPoint_, oppositeControlPointType);
 
-      double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
+      double d = ComputeDistance(startControlPoint.x, startControlPoint.y, startOppositeControlPoint_.x, startOppositeControlPoint_.y);
       if (d >= std::numeric_limits<float>::epsilon())
       {
         baseScaling_ = 1.0 / d;
@@ -159,14 +158,16 @@
   void RadiographyLayerResizeTracker::MouseMove(int displayX,
                                                 int displayY,
                                                 double sceneX,
-                                                double sceneY)
+                                                double sceneY,
+                                                const std::vector<Touch>& displayTouches,
+                                                const std::vector<Touch>& sceneTouches)
   {
     static const double ROUND_SCALING = 0.1;
         
     if (accessor_.IsValid() &&
         accessor_.GetLayer().GetGeometry().IsResizeable())
     {
-      double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
+      double scaling = ComputeDistance(startOppositeControlPoint_.x, startOppositeControlPoint_.y, sceneX, sceneY) * baseScaling_;
 
       if (roundScaling_)
       {
@@ -178,10 +179,10 @@
                             scaling * originalSpacingY_);
 
       // Keep the opposite corner at a fixed location
-      double ox, oy;
-      layer.GetCorner(ox, oy, oppositeCorner_);
-      layer.SetPan(layer.GetGeometry().GetPanX() + oppositeX_ - ox,
-                   layer.GetGeometry().GetPanY() + oppositeY_ - oy);
+      ControlPoint currentOppositeCorner;
+      layer.GetControlPoint(currentOppositeCorner, startOppositeControlPoint_.index);
+      layer.SetPan(layer.GetGeometry().GetPanX() + startOppositeControlPoint_.x - currentOppositeCorner.x,
+                   layer.GetGeometry().GetPanY() + startOppositeControlPoint_.y - currentOppositeCorner.y);
     }
   }
 }
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -39,18 +39,14 @@
     double                           originalSpacingY_;
     double                           originalPanX_;
     double                           originalPanY_;
-    Corner                           oppositeCorner_;
-    double                           oppositeX_;
-    double                           oppositeY_;
+    ControlPoint                     startOppositeControlPoint_;
     double                           baseScaling_;
 
   public:
     RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
                                   RadiographyScene& scene,
                                   size_t layer,
-                                  double x,
-                                  double y,
-                                  Corner corner,
+                                  const ControlPoint& startControlPoint,
                                   bool roundScaling);
 
     virtual bool HasRender() const
@@ -66,6 +62,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -132,7 +132,9 @@
   void RadiographyLayerRotateTracker::MouseMove(int displayX,
                                                 int displayY,
                                                 double sceneX,
-                                                double sceneY)
+                                                double sceneY,
+                                                const std::vector<Touch>& displayTouches,
+                                                const std::vector<Touch>& sceneTouches)
   {
     static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>(); 
         
--- a/Framework/Radiography/RadiographyLayerRotateTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerRotateTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -68,6 +68,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyMaskLayer.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,143 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyMaskLayer.h"
+#include "RadiographyDicomLayer.h"
+
+#include "RadiographyScene.h"
+#include "Core/Images/Image.h"
+#include "Core/Images/ImageProcessing.h"
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  const unsigned char IN_MASK_VALUE = 0x00;
+  const unsigned char OUT_MASK_VALUE = 0xFF;
+
+  const AffineTransform2D& RadiographyMaskLayer::GetTransform() const
+  {
+    return dicomLayer_.GetTransform();
+  }
+
+  const AffineTransform2D& RadiographyMaskLayer::GetTransformInverse() const
+  {
+    return dicomLayer_.GetTransformInverse();
+  }
+
+  bool RadiographyMaskLayer::GetPixel(unsigned int& imageX,
+                        unsigned int& imageY,
+                        double sceneX,
+                        double sceneY) const
+  {
+    return dicomLayer_.GetPixel(imageX, imageY, sceneX, sceneY);
+  }
+
+  std::string RadiographyMaskLayer::GetInstanceId() const
+  {
+    return dicomLayer_.GetInstanceId();
+  }
+
+  void RadiographyMaskLayer::SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index)
+  {
+    if (index < corners_.size())
+      corners_[index] = corner;
+    else
+      corners_.push_back(corner);
+    invalidated_ = true;
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyMaskLayer::SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners)
+  {
+    corners_ = corners;
+    invalidated_ = true;
+
+    EmitMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer,
+                                    const AffineTransform2D& viewTransform,
+                                    ImageInterpolation interpolation) const
+  {
+    if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded)
+      return;
+
+    if (invalidated_)
+    {
+      mask_.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, dicomLayer_.GetWidth(), dicomLayer_.GetHeight(), false));
+
+      DrawMask();
+
+      invalidated_ = false;
+    }
+
+    {// rendering
+      if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+
+      unsigned int cropX, cropY, cropWidth, cropHeight;
+      dicomLayer_.GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+      const AffineTransform2D t = AffineTransform2D::Combine(
+            viewTransform, dicomLayer_.GetTransform(),
+            AffineTransform2D::CreateOffset(cropX, cropY));
+
+      Orthanc::ImageAccessor cropped;
+      mask_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
+
+      Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
+
+      t.Apply(tmp, cropped, interpolation, true /* clear */);
+
+      // Blit
+      const unsigned int width = buffer.GetWidth();
+      const unsigned int height = buffer.GetHeight();
+
+      for (unsigned int y = 0; y < height; y++)
+      {
+        float *q = reinterpret_cast<float*>(buffer.GetRow(y));
+        const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y));
+
+        for (unsigned int x = 0; x < width; x++, p++, q++)
+        {
+          if (*p == OUT_MASK_VALUE)
+            *q = foreground_;
+          // else keep the underlying pixel value
+        }
+      }
+
+    }
+  }
+
+  void RadiographyMaskLayer::DrawMask() const
+  {
+    // first fill the complete image
+    Orthanc::ImageProcessing::Set(*mask_, OUT_MASK_VALUE);
+
+    // fill mask
+    Orthanc::ImageProcessing::FillPolygon(*mask_, corners_, IN_MASK_VALUE);
+
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyMaskLayer.h	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,127 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "RadiographyLayer.h"
+#include "Core/Images/Image.h"
+#include "Core/Images/ImageProcessing.h"
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+  class RadiographyDicomLayer;
+
+  class RadiographyMaskLayer : public RadiographyLayer
+  {
+  private:
+    std::vector<Orthanc::ImageProcessing::ImagePoint>            corners_;
+    const RadiographyDicomLayer&      dicomLayer_;
+    mutable bool                      invalidated_;
+    float                             foreground_;
+
+    mutable std::auto_ptr<Orthanc::ImageAccessor>  mask_;
+  public:
+    RadiographyMaskLayer(MessageBroker& broker, const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer,
+                         float foreground) :
+      RadiographyLayer(broker, scene),
+      dicomLayer_(dicomLayer),
+      invalidated_(true),
+      foreground_(foreground)
+    {
+    }
+
+    void SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners);
+    void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index);
+
+    const std::vector<Orthanc::ImageProcessing::ImagePoint>& GetCorners() const
+    {
+      return corners_;
+    }
+
+    float GetForeground() const
+    {
+      return foreground_;
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation) const;
+
+    std::string GetInstanceId() const;
+
+    virtual size_t GetControlPointCount() const
+    {
+      return corners_.size();
+    }
+
+    virtual void GetControlPoint(ControlPoint& cpScene,
+                                         size_t index) const
+    {
+      ControlPoint cp(corners_[index].GetX(), corners_[index].GetY(), index);
+
+      // transforms image coordinates into scene coordinates
+      GetTransform().Apply(cp.x, cp.y);
+      cpScene = cp;
+    }
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const
+    {
+      return false;
+    }
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const
+    {
+      minValue = 0;
+      maxValue = 0;
+
+      if (foreground_ < 0)
+      {
+        minValue = foreground_;
+      }
+
+      if (foreground_ > 0)
+      {
+        maxValue = foreground_;
+      }
+
+      return true;
+
+    }
+
+    virtual bool GetPixel(unsigned int& imageX,
+                          unsigned int& imageY,
+                          double sceneX,
+                          double sceneY) const;
+
+  protected:
+    virtual const AffineTransform2D& GetTransform() const;
+
+    virtual const AffineTransform2D& GetTransformInverse() const;
+
+
+  private:
+    void DrawMask() const;
+
+  };
+}
--- a/Framework/Radiography/RadiographyScene.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -24,6 +24,7 @@
 #include "RadiographyAlphaLayer.h"
 #include "RadiographyDicomLayer.h"
 #include "RadiographyTextLayer.h"
+#include "RadiographyMaskLayer.h"
 #include "../Toolbox/DicomFrameConverter.h"
 
 #include <Core/Images/Image.h>
@@ -141,10 +142,15 @@
 
     EmitMessage(GeometryChangedMessage(*this, *layer));
     EmitMessage(ContentChangedMessage(*this, *layer));
+    layer->RegisterObserverCallback(new Callable<RadiographyScene, RadiographyLayer::LayerEditedMessage>(*this, &RadiographyScene::OnLayerEdited));
 
     return *layer;
   }
 
+  void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message)
+  {
+    EmitMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
+  }
 
   RadiographyScene::RadiographyScene(MessageBroker& broker) :
     IObserver(broker),
@@ -246,6 +252,8 @@
     hasWindowing_ = true;
     windowingCenter_ = center;
     windowingWidth_ = width;
+
+    EmitMessage(RadiographyScene::WindowingChangedMessage(*this));
   }
 
 
@@ -253,7 +261,7 @@
                                                const std::string& utf8,
                                                RadiographyLayer::Geometry* geometry)
   {
-    std::auto_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(*this));
+    std::auto_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(IObservable::GetBroker(), *this));
     alpha->LoadText(font, utf8);
     if (geometry != NULL)
     {
@@ -292,9 +300,25 @@
     return LoadAlphaBitmap(block.release(), geometry);
   }
 
+  RadiographyLayer& RadiographyScene::LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
+                                               const RadiographyDicomLayer& dicomLayer,
+                                               float foreground,
+                                               RadiographyLayer::Geometry* geometry)
+  {
+    std::auto_ptr<RadiographyMaskLayer>  mask(new RadiographyMaskLayer(IObservable::GetBroker(), *this, dicomLayer, foreground));
+    mask->SetCorners(corners);
+    if (geometry != NULL)
+    {
+      mask->SetGeometry(*geometry);
+    }
+
+    return RegisterLayer(mask.release());
+  }
+
+
   RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry)
   {
-    std::auto_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(*this));
+    std::auto_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(IObservable::GetBroker(), *this));
     alpha->SetAlpha(bitmap);
     if (geometry != NULL)
     {
@@ -304,13 +328,36 @@
     return RegisterLayer(alpha.release());
   }
 
+  RadiographyLayer& RadiographyScene::LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
+                                                     const std::string& instance,
+                                                     unsigned int frame,
+                                                     DicomFrameConverter* converter,  // takes ownership
+                                                     PhotometricDisplayMode preferredPhotometricDisplayMode,
+                                                     RadiographyLayer::Geometry* geometry)
+  {
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)));
+
+    layer.SetInstance(instance, frame);
+
+    if (geometry != NULL)
+    {
+      layer.SetGeometry(*geometry);
+    }
+
+    layer.SetDicomFrameConverter(converter);
+    layer.SetSourceImage(dicomImage);
+    layer.SetPreferredPhotomotricDisplayMode(preferredPhotometricDisplayMode);
+
+    return layer;
+ }
+
   RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc,
                                                      const std::string& instance,
                                                      unsigned int frame,
                                                      bool httpCompression,
                                                      RadiographyLayer::Geometry* geometry)
   {
-    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer));
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)));
     layer.SetInstance(instance, frame);
 
     if (geometry != NULL)
@@ -354,7 +401,7 @@
 
   RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web)
   {
-    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer);
+    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this));
 
 
     return layer;
@@ -522,41 +569,11 @@
     }
   }
 
-
-  void RadiographyScene::ExportDicom(OrthancApiClient& orthanc,
-                                     const Orthanc::DicomMap& dicom,
-                                     const std::string& parentOrthancId,
-                                     double pixelSpacingX,
-                                     double pixelSpacingY,
-                                     bool invert,
-                                     ImageInterpolation interpolation,
-                                     bool usePam)
-  {
-    Json::Value createDicomRequestContent;
-
-    ExportToCreateDicomRequest(createDicomRequestContent, dicom, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam);
-
-    if (!parentOrthancId.empty())
-    {
-      createDicomRequestContent["Parent"] = parentOrthancId;
-    }
-
-    orthanc.PostJsonAsyncExpectJson(
-          "/tools/create-dicom", createDicomRequestContent,
-          new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage>
-          (*this, &RadiographyScene::OnDicomExported),
-          NULL, NULL);
-  }
-
-  // Export using PAM is faster than using PNG, but requires Orthanc
-  // core >= 1.4.3
-  void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
-                                const Orthanc::DicomMap& dicom,
-                                double pixelSpacingX,
-                                double pixelSpacingY,
-                                bool invert,
-                                ImageInterpolation interpolation,
-                                bool usePam)
+  Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX,
+                                                  double pixelSpacingY,
+                                                  ImageInterpolation interpolation,
+                                                  bool invert,
+                                                  int64_t maxValue /* for inversion */)
   {
     if (pixelSpacingX <= 0 ||
         pixelSpacingY <= 0)
@@ -564,12 +581,10 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    LOG(INFO) << "Exporting DICOM";
-
     Extent2D extent = GetSceneExtent();
 
-    int w = std::ceil(extent.GetWidth() / pixelSpacingX);
-    int h = std::ceil(extent.GetHeight() / pixelSpacingY);
+    int w = static_cast<int>(std::ceil(extent.GetWidth() / pixelSpacingX));
+    int h = static_cast<int>(std::ceil(extent.GetHeight() / pixelSpacingY));
 
     if (w < 0 || h < 0)
     {
@@ -589,9 +604,30 @@
 
     Render(layers, view, interpolation);
 
-    Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16,
-                            layers.GetWidth(), layers.GetHeight(), false);
-    Orthanc::ImageProcessing::Convert(rendered, layers);
+    std::auto_ptr<Orthanc::Image> rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16,
+                                                              layers.GetWidth(), layers.GetHeight(), false));
+
+    Orthanc::ImageProcessing::Convert(*rendered, layers);
+    if (invert)
+      Orthanc::ImageProcessing::Invert(*rendered, maxValue);
+
+    return rendered.release();
+  }
+
+
+  void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
+                                                    const Json::Value& dicomTags,
+                                                    const std::string& parentOrthancId,
+                                                    double pixelSpacingX,
+                                                    double pixelSpacingY,
+                                                    bool invert,
+                                                    ImageInterpolation interpolation,
+                                                    bool usePam)
+  {
+    LOG(INFO) << "Exporting RadiographyScene to DICOM";
+    VLOG(1) << "Exporting RadiographyScene to: export to image";
+
+    std::auto_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags
 
     std::string base64;
 
@@ -600,43 +636,31 @@
 
       if (usePam)
       {
+        VLOG(1) << "Exporting RadiographyScene: convert to PAM";
         Orthanc::PamWriter writer;
-        writer.WriteToMemory(content, rendered);
+        writer.WriteToMemory(content, *rendered);
       }
       else
       {
         Orthanc::PngWriter writer;
-        writer.WriteToMemory(content, rendered);
+        writer.WriteToMemory(content, *rendered);
       }
 
+      VLOG(1) << "Exporting RadiographyScene: encoding to base64";
       Orthanc::Toolbox::EncodeBase64(base64, content);
     }
 
-    std::set<Orthanc::DicomTag> tags;
-    dicom.GetTags(tags);
-
-    createDicomRequestContent["Tags"] = Json::objectValue;
-
-    for (std::set<Orthanc::DicomTag>::const_iterator
-         tag = tags.begin(); tag != tags.end(); ++tag)
-    {
-      const Orthanc::DicomValue& value = dicom.GetValue(*tag);
-      if (!value.IsNull() &&
-          !value.IsBinary())
-      {
-        createDicomRequestContent["Tags"][tag->Format()] = value.GetContent();
-      }
-    }
+    createDicomRequestContent["Tags"] = dicomTags;
 
     PhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode();
     if ((invert && photometricMode != PhotometricDisplayMode_Monochrome2) ||
         (!invert && photometricMode == PhotometricDisplayMode_Monochrome1))
     {
-      createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME1";
+      createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1";
     }
     else
     {
-      createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME2";
+      createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME2";
     }
 
     // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to
@@ -646,15 +670,15 @@
     char buf[32];
     sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
 
-    createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf;
+    createDicomRequestContent["Tags"]["PixelSpacing"] = buf;
 
     float center, width;
     if (GetWindowing(center, width))
     {
-      createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] =
+      createDicomRequestContent["Tags"]["WindowCenter"] =
           boost::lexical_cast<std::string>(boost::math::iround(center));
 
-      createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] =
+      createDicomRequestContent["Tags"]["WindowWidth"] =
           boost::lexical_cast<std::string>(boost::math::iround(width));
     }
 
@@ -663,9 +687,68 @@
     createDicomRequestContent["Content"] = ("data:" +
                                             std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) +
                                             ";base64," + base64);
+
+    if (!parentOrthancId.empty())
+    {
+      createDicomRequestContent["Parent"] = parentOrthancId;
+    }
+
+    VLOG(1) << "Exporting RadiographyScene: create-dicom request is ready";
+  }
+
+
+  void RadiographyScene::ExportDicom(OrthancApiClient& orthanc,
+                                     const Json::Value& dicomTags,
+                                     const std::string& parentOrthancId,
+                                     double pixelSpacingX,
+                                     double pixelSpacingY,
+                                     bool invert,
+                                     ImageInterpolation interpolation,
+                                     bool usePam)
+  {
+    Json::Value createDicomRequestContent;
+
+    ExportToCreateDicomRequest(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam);
+
+    orthanc.PostJsonAsyncExpectJson(
+          "/tools/create-dicom", createDicomRequestContent,
+          new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage>
+          (*this, &RadiographyScene::OnDicomExported),
+          NULL, NULL);
+
   }
 
 
+  // Export using PAM is faster than using PNG, but requires Orthanc
+  // core >= 1.4.3
+  void RadiographyScene::ExportDicom(OrthancApiClient& orthanc,
+                                     const Orthanc::DicomMap& dicom,
+                                     const std::string& parentOrthancId,
+                                     double pixelSpacingX,
+                                     double pixelSpacingY,
+                                     bool invert,
+                                     ImageInterpolation interpolation,
+                                     bool usePam)
+  {
+    std::set<Orthanc::DicomTag> tags;
+    dicom.GetTags(tags);
+
+    Json::Value jsonTags = Json::objectValue;
+
+    for (std::set<Orthanc::DicomTag>::const_iterator
+         tag = tags.begin(); tag != tags.end(); ++tag)
+    {
+      const Orthanc::DicomValue& value = dicom.GetValue(*tag);
+      if (!value.IsNull() &&
+          !value.IsBinary())
+      {
+        jsonTags[tag->Format()] = value.GetContent();
+      }
+    }
+
+    ExportDicom(orthanc, jsonTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam);
+  }
+
   void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
     LOG(INFO) << "DICOM export was successful: "
--- a/Framework/Radiography/RadiographyScene.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.h	Thu Apr 18 10:43:17 2019 +0200
@@ -24,16 +24,21 @@
 #include "RadiographyLayer.h"
 #include "../Toolbox/OrthancApiClient.h"
 #include "Framework/StoneEnumerations.h"
+#include "Core/Images/Image.h"
+#include "Core/Images/ImageProcessing.h"
 
 namespace OrthancStone
 {
+  class RadiographyDicomLayer;
+  class DicomFrameConverter;
+
   class RadiographyScene :
       public IObserver,
       public IObservable
   {
   public:
     class GeometryChangedMessage :
-        public OriginMessage<MessageType_Scene_GeometryChanged, RadiographyScene>
+        public OriginMessage<MessageType_RadiographyScene_GeometryChanged, RadiographyScene>
     {
     private:
       RadiographyLayer&        layer_;
@@ -53,7 +58,7 @@
     };
 
     class ContentChangedMessage :
-        public OriginMessage<MessageType_Scene_ContentChanged, RadiographyScene>
+        public OriginMessage<MessageType_RadiographyScene_ContentChanged, RadiographyScene>
     {
     private:
       RadiographyLayer&        layer_;
@@ -72,6 +77,37 @@
       }
     };
 
+    class LayerEditedMessage :
+        public OriginMessage<MessageType_RadiographyScene_LayerEdited, RadiographyScene>
+    {
+    private:
+      const RadiographyLayer&        layer_;
+
+    public:
+      LayerEditedMessage(const RadiographyScene& origin,
+                         const RadiographyLayer& layer) :
+        OriginMessage(origin),
+        layer_(layer)
+      {
+      }
+
+      const RadiographyLayer& GetLayer() const
+      {
+        return layer_;
+      }
+
+    };
+
+    class WindowingChangedMessage :
+        public OriginMessage<MessageType_RadiographyScene_WindowingChanged, RadiographyScene>
+    {
+
+    public:
+      WindowingChangedMessage(const RadiographyScene& origin) :
+        OriginMessage(origin)
+      {
+      }
+    };
 
     class LayerAccessor : public boost::noncopyable
     {
@@ -126,6 +162,7 @@
 
     void OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message);
 
+    void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message);
   public:
     RadiographyScene(MessageBroker& broker);
     
@@ -150,9 +187,21 @@
                                     unsigned int height,
                                     RadiographyLayer::Geometry* geometry);
 
+    RadiographyLayer& LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
+                               const RadiographyDicomLayer& dicomLayer,
+                               float foreground,
+                               RadiographyLayer::Geometry* geometry);
+
     RadiographyLayer& LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap,  // takes ownership
                                       RadiographyLayer::Geometry* geometry);
 
+    virtual RadiographyLayer& LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
+                                             const std::string& instance,
+                                             unsigned int frame,
+                                             DicomFrameConverter* converter,  // takes ownership
+                                             PhotometricDisplayMode preferredPhotometricDisplayMode,
+                                             RadiographyLayer::Geometry* geometry);
+
     virtual RadiographyLayer& LoadDicomFrame(OrthancApiClient& orthanc,
                                              const std::string& instance,
                                              unsigned int frame,
@@ -165,6 +214,54 @@
 
     const RadiographyLayer& GetLayer(size_t layerIndex) const;
 
+    template <typename TypeLayer>
+    TypeLayer* GetLayer(size_t index = 0)
+    {
+      std::vector<size_t> layerIndexes;
+      GetLayersIndexes(layerIndexes);
+
+      size_t count = 0;
+
+      for (size_t i = 0; i < layerIndexes.size(); ++i)
+      {
+        TypeLayer* typedLayer = dynamic_cast<TypeLayer*>(layers_[layerIndexes[i]]);
+        if (typedLayer != NULL)
+        {
+          if (count == index)
+          {
+            return typedLayer;
+          }
+          count++;
+        }
+      }
+
+      return NULL;
+    }
+
+    template <typename TypeLayer>
+    const TypeLayer* GetLayer(size_t index = 0) const
+    {
+      std::vector<size_t> layerIndexes;
+      GetLayersIndexes(layerIndexes);
+
+      size_t count = 0;
+
+      for (size_t i = 0; i < layerIndexes.size(); ++i)
+      {
+        const TypeLayer* typedLayer = dynamic_cast<const TypeLayer*>(layers_.at(layerIndexes[i]));
+        if (typedLayer != NULL)
+        {
+          if (count == index)
+          {
+            return typedLayer;
+          }
+          count++;
+        }
+      }
+
+      return NULL;
+    }
+
     void GetLayersIndexes(std::vector<size_t>& output) const;
 
     Extent2D GetSceneExtent() const;
@@ -195,13 +292,35 @@
                      ImageInterpolation interpolation,
                      bool usePam);
 
-    // temporary version used by VSOL because we need to send the same request at another url
+    void ExportDicom(OrthancApiClient& orthanc,
+                     const Json::Value& dicomTags,
+                     const std::string& parentOrthancId,
+                     double pixelSpacingX,
+                     double pixelSpacingY,
+                     bool invert,
+                     ImageInterpolation interpolation,
+                     bool usePam);
+
     void ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
-                                    const Orthanc::DicomMap& dicom,
+                                    const Json::Value& dicomTags,
+                                    const std::string& parentOrthancId,
                                     double pixelSpacingX,
                                     double pixelSpacingY,
                                     bool invert,
                                     ImageInterpolation interpolation,
                                     bool usePam);
+
+    Orthanc::Image* ExportToImage(double pixelSpacingX,
+                                  double pixelSpacingY,
+                                  ImageInterpolation interpolation)
+    {
+      return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0);
+    }
+
+    Orthanc::Image* ExportToImage(double pixelSpacingX,
+                                  double pixelSpacingY,
+                                  ImageInterpolation interpolation,
+                                  bool invert,
+                                  int64_t maxValue /* for inversion */);
   };
 }
--- a/Framework/Radiography/RadiographySceneReader.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographySceneReader.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -28,13 +28,37 @@
 
 namespace OrthancStone
 {
-  void RadiographySceneReader::Read(const Json::Value& input)
+
+  void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */,
+                                     DicomFrameConverter* dicomFrameConverter  /* takes ownership */,
+                                     PhotometricDisplayMode preferredPhotometricDisplayMode
+                                     )
+  {
+    dicomImage_.reset(dicomImage);
+    dicomFrameConverter_.reset(dicomFrameConverter);
+    preferredPhotometricDisplayMode_ = preferredPhotometricDisplayMode;
+    Read(input);
+  }
+
+  RadiographyDicomLayer* RadiographySceneBuilder::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
+  {
+    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomImage(dicomImage_.release(), instanceId, frame, dicomFrameConverter_.release(), preferredPhotometricDisplayMode_, geometry)));
+  }
+
+
+  RadiographyDicomLayer* RadiographySceneReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
+  {
+    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry)));
+  }
+
+  void RadiographySceneBuilder::Read(const Json::Value& input)
   {
     unsigned int version = input["version"].asUInt();
 
     if (version != 1)
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
 
+    RadiographyDicomLayer* dicomLayer = NULL;
     for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
     {
       const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
@@ -43,7 +67,26 @@
       if (jsonLayer["type"].asString() == "dicom")
       {
         ReadLayerGeometry(geometry, jsonLayer);
-        scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry);
+        dicomLayer = LoadDicom(jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "mask")
+      {
+        if (dicomLayer == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask
+        }
+        ReadLayerGeometry(geometry, jsonLayer);
+
+        float foreground = jsonLayer["foreground"].asFloat();
+        std::vector<Orthanc::ImageProcessing::ImagePoint> corners;
+        for (size_t i = 0; i < jsonLayer["corners"].size(); i++)
+        {
+          Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(),
+              jsonLayer["corners"][(int)i]["y"].asInt());
+          corners.push_back(corner);
+        }
+
+        scene_.LoadMask(corners, *dicomLayer, foreground, &geometry);
       }
       else if (jsonLayer["type"].asString() == "text")
       {
@@ -90,7 +133,90 @@
     }
   }
 
-  void RadiographySceneReader::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer)
+
+  void RadiographySceneReader::Read(const Json::Value& input)
+  {
+    unsigned int version = input["version"].asUInt();
+
+    if (version != 1)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+
+    RadiographyDicomLayer* dicomLayer = NULL;
+    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
+    {
+      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
+      RadiographyLayer::Geometry geometry;
+
+      if (jsonLayer["type"].asString() == "dicom")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+        dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry)));
+      }
+      else if (jsonLayer["type"].asString() == "mask")
+      {
+        if (dicomLayer == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask
+        }
+        ReadLayerGeometry(geometry, jsonLayer);
+
+        float foreground = jsonLayer["foreground"].asFloat();
+        std::vector<Orthanc::ImageProcessing::ImagePoint> corners;
+        for (size_t i = 0; i < jsonLayer["corners"].size(); i++)
+        {
+          Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(),
+              jsonLayer["corners"][(int)i]["y"].asInt());
+          corners.push_back(corner);
+        }
+
+        scene_.LoadMask(corners, *dicomLayer, foreground, &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "text")
+      {
+        if (fontRegistry_ == NULL || fontRegistry_->GetSize() == 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); // you must provide a FontRegistry if you need to re-create text layers.
+        }
+
+        ReadLayerGeometry(geometry, jsonLayer);
+        const Orthanc::Font* font = fontRegistry_->FindFont(jsonLayer["fontName"].asString());
+        if (font == NULL) // if not found, take the first font in the registry
+        {
+          font = &(fontRegistry_->GetFont(0));
+        }
+        scene_.LoadText(*font, jsonLayer["text"].asString(), &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "alpha")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+
+        const std::string& pngContentBase64 = jsonLayer["content"].asString();
+        std::string pngContent;
+        std::string mimeType;
+        Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64);
+
+        std::auto_ptr<Orthanc::ImageAccessor>  image;
+        if (mimeType == "image/png")
+        {
+          image.reset(new Orthanc::PngReader());
+          dynamic_cast<Orthanc::PngReader*>(image.get())->ReadFromMemory(pngContent);
+        }
+        else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+
+        RadiographyAlphaLayer& layer = dynamic_cast<RadiographyAlphaLayer&>(scene_.LoadAlphaBitmap(image.release(), &geometry));
+
+        if (!jsonLayer["isUsingWindowing"].asBool())
+        {
+          layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble()));
+        }
+      }
+      else
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  void RadiographySceneBuilder::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer)
   {
     {// crop
       unsigned int x, y, width, height;
--- a/Framework/Radiography/RadiographySceneReader.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographySceneReader.h	Thu Apr 18 10:43:17 2019 +0200
@@ -24,6 +24,7 @@
 #include "RadiographyScene.h"
 #include "RadiographyAlphaLayer.h"
 #include "RadiographyDicomLayer.h"
+#include "RadiographyMaskLayer.h"
 #include "RadiographyTextLayer.h"
 #include <json/value.h>
 #include <Core/Images/FontRegistry.h>
@@ -32,28 +33,57 @@
 {
   class OrthancApiClient;
 
-  class RadiographySceneReader : public boost::noncopyable
+  // HACK: I had to introduce this builder class in order to be able to recreate a RadiographyScene
+  // from a serialized scene that is passed to web-workers.
+  // It needs some architecturing...
+  class RadiographySceneBuilder : public boost::noncopyable
   {
-    RadiographyScene&             scene_;
-    OrthancApiClient&             orthancApiClient_;
-    const Orthanc::FontRegistry*  fontRegistry_;
+  protected:
+    RadiographyScene&                               scene_;
+    const Orthanc::FontRegistry*                    fontRegistry_;
+    std::auto_ptr<Orthanc::ImageAccessor>           dicomImage_;
+    std::auto_ptr<DicomFrameConverter>              dicomFrameConverter_;
+    PhotometricDisplayMode                          preferredPhotometricDisplayMode_;
 
   public:
-    RadiographySceneReader(RadiographyScene& scene, OrthancApiClient& orthancApiClient) :
+    RadiographySceneBuilder(RadiographyScene& scene) :
       scene_(scene),
-      orthancApiClient_(orthancApiClient),
       fontRegistry_(NULL)
     {
     }
 
-    void Read(const Json::Value& output);
+    void Read(const Json::Value& input);
+    void Read(const Json::Value& input,
+              Orthanc::ImageAccessor* dicomImage, // takes ownership
+              DicomFrameConverter* dicomFrameConverter, // takes ownership
+              PhotometricDisplayMode preferredPhotometricDisplayMode
+              );
 
     void SetFontRegistry(const Orthanc::FontRegistry& fontRegistry)
     {
       fontRegistry_ = &fontRegistry;
     }
 
-  private:
-    void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& output);
+  protected:
+    void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input);
+    virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
+  };
+
+
+  class RadiographySceneReader : public RadiographySceneBuilder
+  {
+    OrthancApiClient&             orthancApiClient_;
+
+  public:
+    RadiographySceneReader(RadiographyScene& scene, OrthancApiClient& orthancApiClient) :
+      RadiographySceneBuilder(scene),
+      orthancApiClient_(orthancApiClient)
+    {
+    }
+
+    void Read(const Json::Value& input);
+
+  protected:
+    virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
   };
 }
--- a/Framework/Radiography/RadiographySceneWriter.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographySceneWriter.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -57,6 +57,22 @@
     output["fontName"] = layer.GetFontName();
   }
 
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer)
+  {
+    output["type"] = "mask";
+    output["instanceId"] = layer.GetInstanceId(); // the dicom layer it's being linked to
+    output["foreground"] = layer.GetForeground();
+    output["corners"] = Json::arrayValue;
+    const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners = layer.GetCorners();
+    for (size_t i = 0; i < corners.size(); i++)
+    {
+      Json::Value corner;
+      corner["x"] = corners[i].GetX();
+      corner["y"] = corners[i].GetY();
+      output["corners"].append(corner);
+    }
+  }
+
   void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer)
   {
     output["type"] = "alpha";
@@ -128,6 +144,10 @@
     {
       WriteLayer(output, dynamic_cast<const RadiographyAlphaLayer&>(layer));
     }
+    else if (dynamic_cast<const RadiographyMaskLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyMaskLayer&>(layer));
+    }
     else
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
--- a/Framework/Radiography/RadiographySceneWriter.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographySceneWriter.h	Thu Apr 18 10:43:17 2019 +0200
@@ -25,6 +25,7 @@
 #include "RadiographyAlphaLayer.h"
 #include "RadiographyDicomLayer.h"
 #include "RadiographyTextLayer.h"
+#include "RadiographyMaskLayer.h"
 #include <json/value.h>
 
 namespace OrthancStone
@@ -46,5 +47,6 @@
     void WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer);
     void WriteLayer(Json::Value& output, const RadiographyTextLayer& layer);
     void WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer);
   };
 }
--- a/Framework/Radiography/RadiographyTextLayer.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyTextLayer.h	Thu Apr 18 10:43:17 2019 +0200
@@ -34,8 +34,8 @@
     std::string                fontName_;
 
   public:
-    RadiographyTextLayer(const RadiographyScene& scene) :
-      RadiographyAlphaLayer(scene)
+    RadiographyTextLayer(MessageBroker& broker, const RadiographyScene& scene) :
+      RadiographyAlphaLayer(broker, scene)
     {
     }
 
--- a/Framework/Radiography/RadiographyWidget.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyWidget.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -25,16 +25,21 @@
 #include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
 
+#include "RadiographyMaskLayer.h"
 
 namespace OrthancStone
 {
 
   bool RadiographyWidget::IsInvertedInternal() const
   {
-    return (scene_->GetPreferredPhotomotricDisplayMode() == PhotometricDisplayMode_Monochrome1) ^ invert_; // MONOCHROME1 images must be inverted and the user can invert the image too -> XOR the two
+    // MONOCHROME1 images must be inverted and the user can invert the 
+    // image, too -> XOR the two
+    return (scene_->GetPreferredPhotomotricDisplayMode() == 
+      PhotometricDisplayMode_Monochrome1) ^ invert_; 
   }
 
-  void RadiographyWidget::RenderBackground(Orthanc::ImageAccessor& image, float minValue, float maxValue)
+  void RadiographyWidget::RenderBackground(
+    Orthanc::ImageAccessor& image, float minValue, float maxValue)
   {
     // wipe background before rendering
     float backgroundValue = minValue;
@@ -58,7 +63,7 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
 
-    Orthanc::ImageProcessing::Set(image, backgroundValue);
+    Orthanc::ImageProcessing::Set(image, static_cast<int64_t>(backgroundValue));
   }
 
   bool RadiographyWidget::RenderInternal(unsigned int width,
@@ -81,7 +86,8 @@
           floatBuffer_->GetWidth() != width ||
           floatBuffer_->GetHeight() != height)
       {
-        floatBuffer_.reset(new Orthanc::Image(Orthanc::PixelFormat_Float32, width, height, false));
+        floatBuffer_.reset(new Orthanc::Image(
+          Orthanc::PixelFormat_Float32, width, height, false));
       }
 
       if (cairoBuffer_.get() == NULL ||
@@ -167,7 +173,8 @@
 
     if (hasSelection_)
     {
-      scene_->DrawBorder(context, selectedLayer_, view.GetZoom());
+      scene_->DrawBorder(
+        context, static_cast<unsigned int>(selectedLayer_), view.GetZoom());
     }
 
     return true;
@@ -194,6 +201,28 @@
     selectedLayer_ = layer;
   }
 
+  bool RadiographyWidget::SelectMaskLayer(size_t index)
+  {
+    std::vector<size_t> layerIndexes;
+    size_t count = 0;
+    scene_->GetLayersIndexes(layerIndexes);
+
+    for (size_t i = 0; i < layerIndexes.size(); ++i)
+    {
+      const RadiographyMaskLayer* maskLayer = dynamic_cast<const RadiographyMaskLayer*>(&(scene_->GetLayer(layerIndexes[i])));
+      if (maskLayer != NULL)
+      {
+        if (count == index)
+        {
+          Select(layerIndexes[i]);
+          return true;
+        }
+        count++;
+      }
+    }
+
+    return false;
+  }
 
   bool RadiographyWidget::LookupSelectedLayer(size_t& layer)
   {
--- a/Framework/Radiography/RadiographyWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -27,6 +27,8 @@
 
 namespace OrthancStone
 {
+  class RadiographyMaskLayer;
+
   class RadiographyWidget :
     public WorldSceneWidget,
     public IObserver
@@ -76,6 +78,8 @@
 
     void Select(size_t layer);
 
+    bool SelectMaskLayer(size_t index = 0);
+
     bool LookupSelectedLayer(size_t& layer);
 
     void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message);
--- a/Framework/Radiography/RadiographyWindowingTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyWindowingTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -163,7 +163,9 @@
   void RadiographyWindowingTracker::MouseMove(int displayX,
                                               int displayY,
                                               double sceneX,
-                                              double sceneY)
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
   {
     // This follows the behavior of the Osimis Web viewer:
     // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
--- a/Framework/Radiography/RadiographyWindowingTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Radiography/RadiographyWindowingTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -82,6 +82,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/StoneEnumerations.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/StoneEnumerations.h	Thu Apr 18 10:43:17 2019 +0200
@@ -157,8 +157,12 @@
     MessageType_OrthancApi_GenericHttpError_Ready,
     MessageType_OrthancApi_GenericEmptyResponse_Ready,
 
-    MessageType_Scene_GeometryChanged,
-    MessageType_Scene_ContentChanged,
+    MessageType_RadiographyScene_GeometryChanged,
+    MessageType_RadiographyScene_ContentChanged,
+    MessageType_RadiographyScene_LayerEdited,
+    MessageType_RadiographyScene_WindowingChanged,
+
+    MessageType_RadiographyLayer_Edited,
 
     MessageType_ViewportChanged,
 
@@ -172,12 +176,12 @@
   };
 
   
-  enum Corner
+  enum ControlPointType
   {
-    Corner_TopLeft,
-    Corner_TopRight,
-    Corner_BottomLeft,
-    Corner_BottomRight
+    ControlPoint_TopLeftCorner = 0,
+    ControlPoint_TopRightCorner = 1,
+    ControlPoint_BottomRightCorner = 2,
+    ControlPoint_BottomLeftCorner = 3
   };
 
   enum PhotometricDisplayMode
--- a/Framework/Toolbox/DicomFrameConverter.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/DicomFrameConverter.h	Thu Apr 18 10:43:17 2019 +0200
@@ -59,6 +59,17 @@
       SetDefaultParameters();
     }
 
+    ~DicomFrameConverter()
+    {
+      // TODO: check whether this dtor is called or not
+      // An MSVC warning explains that declaring an
+      // std::auto_ptr with a forward-declared type 
+      // prevents its dtor from being called. Does not 
+      // seem an issue here (only POD types inside), but
+      // definitely something to keep an eye on. 
+      (void)0;
+    }
+
     Orthanc::PixelFormat GetExpectedPixelFormat() const
     {
       return expectedPixelFormat_;
--- a/Framework/Toolbox/DicomStructureSet.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -754,7 +754,11 @@
         double x1, y1, x2, y2;
         if (polygon->Project(x1, y1, x2, y2, slice))
         {
-          projected.push_back(CreateRectangle(x1, y1, x2, y2));
+          projected.push_back(CreateRectangle(
+            static_cast<float>(x1), 
+            static_cast<float>(y1), 
+            static_cast<float>(x2), 
+            static_cast<float>(y2)));
         }
       }
 
--- a/Framework/Toolbox/FiniteProjectiveCamera.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -332,7 +332,7 @@
       LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount();
       
       const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z);
-      OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, z);
+      OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, static_cast<unsigned int>(z));
 
       SourceReader pixelReader(sliceReader.GetAccessor());
       
--- a/Framework/Toolbox/ImageGeometry.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/ImageGeometry.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -76,9 +76,7 @@
     }
     else
     {
-      int tmp;
-
-      tmp = std::floor(extent.GetX1());
+      int tmp = static_cast<int>(std::floor(extent.GetX1()));
       if (tmp < 0)
       {
         x1 = 0;
@@ -88,7 +86,7 @@
         x1 = static_cast<unsigned int>(tmp);
       }
 
-      tmp = std::floor(extent.GetY1());
+      tmp = static_cast<int>(std::floor(extent.GetY1()));
       if (tmp < 0)
       {
         y1 = 0;
@@ -98,7 +96,7 @@
         y1 = static_cast<unsigned int>(tmp);
       }
 
-      tmp = std::ceil(extent.GetX2());
+      tmp = static_cast<int>(std::ceil(extent.GetX2()));
       if (tmp < 0)
       {
         return false;
@@ -112,7 +110,7 @@
         x2 = static_cast<unsigned int>(tmp);
       }
 
-      tmp = std::ceil(extent.GetY2());
+      tmp = static_cast<int>(std::ceil(extent.GetY2()));
       if (tmp < 0)
       {
         return false;
@@ -395,8 +393,8 @@
     Reader reader(source);
     unsigned int x1, y1, x2, y2;
 
-    const float floatWidth = source.GetWidth();
-    const float floatHeight = source.GetHeight();
+    const float floatWidth = static_cast<float>(source.GetWidth());
+    const float floatHeight = static_cast<float>(source.GetHeight());
 
     if (GetProjectiveTransformExtent(x1, y1, x2, y2, a,
                                      source.GetWidth(), source.GetHeight(),
--- a/Framework/Toolbox/OrthancApiClient.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -248,6 +248,21 @@
     web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL);
   }
 
+  void OrthancApiClient::PostBinaryAsync(
+      const std::string& uri,
+      const std::string& body,
+      MessageHandler<EmptyResponseReadyMessage>* successCallback,
+      MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload   /* takes ownership */)
+  {
+    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
+                   new WebServicePayload(successCallback, failureCallback, payload),
+                   new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                   (*this, &OrthancApiClient::NotifyHttpSuccess),
+                   new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                   (*this, &OrthancApiClient::NotifyHttpError));
+  }
+
   void OrthancApiClient::PostJsonAsyncExpectJson(
       const std::string& uri,
       const Json::Value& data,
@@ -269,6 +284,18 @@
     return PostBinaryAsync(uri, body);
   }
 
+  void OrthancApiClient::PostJsonAsync(
+      const std::string& uri,
+      const Json::Value& data,
+      MessageHandler<EmptyResponseReadyMessage>* successCallback,
+      MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload   /* takes ownership */)
+  {
+    std::string body;
+    MessagingToolbox::JsonToString(body, data);
+    return PostBinaryAsync(uri, body, successCallback, failureCallback, payload);
+  }
+
   void OrthancApiClient::DeleteAsync(
       const std::string& uri,
       MessageHandler<EmptyResponseReadyMessage>* successCallback,
--- a/Framework/Toolbox/OrthancApiClient.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/OrthancApiClient.h	Thu Apr 18 10:43:17 2019 +0200
@@ -163,6 +163,8 @@
     {
     }
 
+    const std::string& GetBaseUrl() const {return baseUrl_;}
+
     // schedule a GET request expecting a JSON response.
     void GetJsonAsync(const std::string& uri,
                       MessageHandler<JsonResponseReadyMessage>* successCallback,
@@ -201,10 +203,25 @@
     void PostJsonAsync(const std::string& uri,
                        const Json::Value& data);
 
+    // schedule a POST request and don't expect any response.
+    void PostJsonAsync(const std::string& uri,
+                       const Json::Value& data,
+                       MessageHandler<EmptyResponseReadyMessage>* successCallback,
+                       MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                       Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+
     // schedule a POST request and don't mind the response.
     void PostBinaryAsync(const std::string& uri,
                          const std::string& body);
 
+    // schedule a POST request and don't expect any response.
+    void PostBinaryAsync(const std::string& uri,
+                         const std::string& body,
+                         MessageHandler<EmptyResponseReadyMessage>* successCallback,
+                         MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                         Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
     // schedule a DELETE request expecting an empty response.
     void DeleteAsync(const std::string& uri,
                      MessageHandler<EmptyResponseReadyMessage>* successCallback,
--- a/Framework/Toolbox/ViewportGeometry.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/ViewportGeometry.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -121,6 +121,18 @@
   }
 
 
+  void ViewportGeometry::MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
+                                               const std::vector<Touch>& displayTouches) const
+  {
+    double sceneX, sceneY;
+    sceneTouches.clear();
+    for (size_t t = 0; t < displayTouches.size(); t++)
+    {
+      MapPixelCenterToScene(sceneX, sceneY, displayTouches[t].x, displayTouches[t].y);
+      sceneTouches.push_back(Touch((float)sceneX, (float)sceneY));
+    }
+  }
+
   void ViewportGeometry::MapPixelCenterToScene(double& sceneX,
                                                double& sceneY,
                                                int x,
--- a/Framework/Toolbox/ViewportGeometry.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Toolbox/ViewportGeometry.h	Thu Apr 18 10:43:17 2019 +0200
@@ -24,6 +24,7 @@
 #include "../Viewport/CairoContext.h"
 #include "Extent2D.h"
 #include "LinearAlgebra.h"
+#include "../Viewport/IMouseTracker.h"  // to include "Touch" definition
 
 namespace OrthancStone
 {
@@ -69,6 +70,9 @@
                                int x,
                                int y) const;
 
+    void MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
+                               const std::vector<Touch>& displayTouches) const;
+
     void MapSceneToDisplay(int& displayX /* out */,
                            int& displayY /* out */,
                            double x,
--- a/Framework/Viewport/IMouseTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Viewport/IMouseTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -22,9 +22,28 @@
 #pragma once
 
 #include "CairoSurface.h"
+#include <vector>
 
 namespace OrthancStone
 {
+  struct Touch
+  {
+    float x;
+    float y;
+
+    Touch(float x, float y)
+    : x(x),
+      y(y)
+    {
+    }
+    Touch()
+      : x(0.0f),
+        y(0.0f)
+    {
+    }
+  };
+
+
   // this is tracking a mouse in screen coordinates/pixels unlike
   // the IWorldSceneMouseTracker that is tracking a mouse
   // in scene coordinates/mm.
@@ -41,6 +60,9 @@
 
     // Returns "true" iff. the background scene must be repainted
     virtual void MouseMove(int x, 
-                           int y) = 0;
+                           int y,
+                           const std::vector<Touch>& displayTouches) = 0;
+
+    virtual bool IsTouchTracker() const {return false;}
   };
 }
--- a/Framework/Viewport/IViewport.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Viewport/IViewport.h	Thu Apr 18 10:43:17 2019 +0200
@@ -26,6 +26,7 @@
 #include "../Messages/IObservable.h"
 
 #include <Core/Images/ImageAccessor.h>
+#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
 
 namespace OrthancStone
 {
@@ -58,12 +59,14 @@
     virtual void MouseDown(MouseButton button,
                            int x,
                            int y,
-                           KeyboardModifiers modifiers) = 0;
+                           KeyboardModifiers modifiers,
+                           const std::vector<Touch>& touches) = 0;
 
     virtual void MouseUp() = 0;
 
     virtual void MouseMove(int x, 
-                           int y) = 0;
+                           int y,
+                           const std::vector<Touch>& displayTouches) = 0;
 
     virtual void MouseEnter() = 0;
 
--- a/Framework/Viewport/WidgetViewport.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Viewport/WidgetViewport.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -137,18 +137,36 @@
     return true;
   }
 
+  void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches)
+  {
+    MouseDown(MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, KeyboardModifiers_None, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
+  }
+      
+  void WidgetViewport::TouchMove(const std::vector<Touch>& displayTouches)
+  {
+    MouseMove((int)displayTouches[0].x, (int)displayTouches[0].y, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
+  }
+      
+  void WidgetViewport::TouchEnd(const std::vector<Touch>& displayTouches)
+  {
+    // note: TouchEnd is not triggered when a single touch gesture ends (it is only triggered when
+    // going from 2 touches to 1 touch, ...)
+    MouseUp();
+  }
 
   void WidgetViewport::MouseDown(MouseButton button,
                                  int x,
                                  int y,
-                                 KeyboardModifiers modifiers)
+                                 KeyboardModifiers modifiers,
+                                 const std::vector<Touch>& displayTouches
+                                 )
   {
     lastMouseX_ = x;
     lastMouseY_ = y;
 
     if (centralWidget_.get() != NULL)
     {
-      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers));
+      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches));
     }
     else
     {
@@ -171,7 +189,8 @@
 
 
   void WidgetViewport::MouseMove(int x, 
-                                 int y) 
+                                 int y,
+                                 const std::vector<Touch>& displayTouches)
   {
     if (centralWidget_.get() == NULL)
     {
@@ -185,7 +204,7 @@
     
     if (mouseTracker_.get() != NULL)
     {
-      mouseTracker_->MouseMove(x, y);
+      mouseTracker_->MouseMove(x, y, displayTouches);
       repaint = true;
     }
     else
--- a/Framework/Viewport/WidgetViewport.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Viewport/WidgetViewport.h	Thu Apr 18 10:43:17 2019 +0200
@@ -59,17 +59,25 @@
     virtual void MouseDown(MouseButton button,
                            int x,
                            int y,
-                           KeyboardModifiers modifiers);
+                           KeyboardModifiers modifiers,
+                           const std::vector<Touch>& displayTouches);
 
     virtual void MouseUp();
 
     virtual void MouseMove(int x, 
-                           int y);
+                           int y,
+                           const std::vector<Touch>& displayTouches);
 
     virtual void MouseEnter();
 
     virtual void MouseLeave();
 
+    virtual void TouchStart(const std::vector<Touch>& touches);
+    
+    virtual void TouchMove(const std::vector<Touch>& touches);
+    
+    virtual void TouchEnd(const std::vector<Touch>& touches);
+
     virtual void MouseWheel(MouseWheelDirection direction,
                             int x,
                             int y,
--- a/Framework/Widgets/EmptyWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/EmptyWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -76,7 +76,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers)
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches)
     {
       return NULL;
     }
--- a/Framework/Widgets/IWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/IWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -52,7 +52,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers) = 0;
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches) = 0;
 
     virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
                                  int x,
--- a/Framework/Widgets/IWorldSceneInteractor.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/IWorldSceneInteractor.h	Thu Apr 18 10:43:17 2019 +0200
@@ -46,7 +46,8 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar) = 0;
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& touches) = 0;
 
         virtual void MouseOver(CairoContext& context,
                                WorldSceneWidget& widget,
--- a/Framework/Widgets/IWorldSceneMouseTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/IWorldSceneMouseTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "../Viewport/CairoContext.h"
+#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
 
 namespace OrthancStone
 {
@@ -46,6 +47,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY) = 0;
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches) = 0;
   };
 }
--- a/Framework/Widgets/LayoutWidget.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/LayoutWidget.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -68,9 +68,16 @@
     }
 
     virtual void MouseMove(int x, 
-                           int y)
+                           int y,
+                           const std::vector<Touch>& displayTouches)
     {
-      tracker_->MouseMove(x - left_, y - top_);
+      std::vector<Touch> relativeTouches;
+      for (size_t t = 0; t < displayTouches.size(); t++)
+      {
+        relativeTouches.push_back(Touch(displayTouches[t].x - left_, displayTouches[t].y - top_));
+      }
+
+      tracker_->MouseMove(x - left_, y - top_, relativeTouches);
     }
   };
 
@@ -150,14 +157,16 @@
     IMouseTracker* CreateMouseTracker(MouseButton button,
                                       int x,
                                       int y,
-                                      KeyboardModifiers modifiers)
+                                      KeyboardModifiers modifiers,
+                                      const std::vector<Touch>& touches)
     {
       if (Contains(x, y))
       {
         IMouseTracker* tracker = widget_->CreateMouseTracker(button, 
                                                              x - left_, 
                                                              y - top_, 
-                                                             modifiers);
+                                                             modifiers,
+                                                             touches);
         if (tracker)
         {
           return new LayoutMouseTracker(tracker, left_, top_, width_, height_);
@@ -217,7 +226,7 @@
     }
     else if (isHorizontal_)
     {
-      unsigned int padding = paddingLeft_ + paddingRight_ + (children_.size() - 1) * paddingInternal_;
+      unsigned int padding = paddingLeft_ + paddingRight_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_;
       float childWidth = ((static_cast<float>(width_) - static_cast<float>(padding)) / 
                           static_cast<float>(children_.size()));
         
@@ -241,7 +250,7 @@
     }
     else
     {
-      unsigned int padding = paddingTop_ + paddingBottom_ + (children_.size() - 1) * paddingInternal_;
+      unsigned int padding = paddingTop_ + paddingBottom_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_;
       float childHeight = ((static_cast<float>(height_) - static_cast<float>(padding)) / 
                            static_cast<float>(children_.size()));
         
@@ -413,11 +422,12 @@
   IMouseTracker* LayoutWidget::CreateMouseTracker(MouseButton button,
                                                   int x,
                                                   int y,
-                                                  KeyboardModifiers modifiers)
+                                                  KeyboardModifiers modifiers,
+                                                  const std::vector<Touch>& touches)
   {
     for (size_t i = 0; i < children_.size(); i++)
     {
-      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers);
+      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers, touches);
       if (tracker != NULL)
       {
         return tracker;
--- a/Framework/Widgets/LayoutWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/LayoutWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -106,7 +106,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers);
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches);
 
     virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
                                  int x,
--- a/Framework/Widgets/PanMouseTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/PanMouseTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -46,7 +46,9 @@
   void PanMouseTracker::MouseMove(int displayX,
                                   int displayY,
                                   double x,
-                                  double y)
+                                  double y,
+                                  const std::vector<Touch>& displayTouches,
+                                  const std::vector<Touch>& sceneTouches)
   {
     ViewportGeometry view = that_.GetView();
     view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(),
--- a/Framework/Widgets/PanMouseTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/PanMouseTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -54,6 +54,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/PanZoomMouseTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,137 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PanZoomMouseTracker.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <math.h>
+
+namespace OrthancStone
+{
+  Touch GetCenter(const std::vector<Touch>& touches)
+  {
+    return Touch((touches[0].x + touches[1].x) / 2.0f, (touches[0].y + touches[1].y) / 2.0f);
+  }
+
+  double GetDistance(const std::vector<Touch>& touches)
+  {
+    float dx = touches[0].x - touches[1].x;
+    float dy = touches[0].y - touches[1].y;
+    return sqrt((double)(dx * dx) + (double)(dy * dy));
+  }
+
+
+  PanZoomMouseTracker::PanZoomMouseTracker(WorldSceneWidget& that,
+                                           const std::vector<Touch>& startTouches)
+    : that_(that),
+      originalZoom_(that.GetView().GetZoom())
+  {
+    that.GetView().GetPan(originalPanX_, originalPanY_);
+    that.GetView().MapPixelCenterToScene(originalSceneTouches_, startTouches);
+
+    originalDisplayCenter_ = GetCenter(startTouches);
+    originalSceneCenter_ = GetCenter(originalSceneTouches_);
+    originalDisplayDistanceBetweenTouches_ = GetDistance(startTouches);
+
+//    printf("original Pan %f %f\n", originalPanX_, originalPanY_);
+//    printf("original Zoom %f \n", originalZoom_);
+//    printf("original distance %f \n", (float)originalDisplayDistanceBetweenTouches_);
+//    printf("original display touches 0 %f %f\n", startTouches[0].x, startTouches[0].y);
+//    printf("original display touches 1 %f %f\n", startTouches[1].x, startTouches[1].y);
+//    printf("original Scene center %f %f\n", originalSceneCenter_.x, originalSceneCenter_.y);
+
+    unsigned int height = that.GetView().GetDisplayHeight();
+
+    if (height <= 3)
+    {
+      idle_ = true;
+      LOG(WARNING) << "image is too small to zoom (current height = " << height << ")";
+    }
+    else
+    {
+      idle_ = false;
+      normalization_ = 1.0 / static_cast<double>(height - 1);
+    }
+
+  }
+
+
+  void PanZoomMouseTracker::Render(CairoContext& context,
+                                   double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void PanZoomMouseTracker::MouseMove(int displayX,
+                                      int displayY,
+                                      double x,
+                                      double y,
+                                      const std::vector<Touch>& displayTouches,
+                                      const std::vector<Touch>& sceneTouches)
+  {
+    ViewportGeometry view = that_.GetView();
+
+//    printf("Display touches 0 %f %f\n", displayTouches[0].x, displayTouches[0].y);
+//    printf("Display touches 1 %f %f\n", displayTouches[1].x, displayTouches[1].y);
+//    printf("Scene touches 0 %f %f\n", sceneTouches[0].x, sceneTouches[0].y);
+//    printf("Scene touches 1 %f %f\n", sceneTouches[1].x, sceneTouches[1].y);
+
+//    printf("zoom = %f\n", view.GetZoom());
+    Touch currentSceneCenter = GetCenter(sceneTouches);
+    double panX = originalPanX_ + (currentSceneCenter.x - originalSceneCenter_.x) * view.GetZoom();
+    double panY = originalPanY_ + (currentSceneCenter.y - originalSceneCenter_.y) * view.GetZoom();
+
+    view.SetPan(panX, panY);
+
+    static const double MIN_ZOOM = -4;
+    static const double MAX_ZOOM = 4;
+
+    if (!idle_)
+    {
+      double currentDistanceBetweenTouches = GetDistance(displayTouches);
+
+      double dy = static_cast<double>(currentDistanceBetweenTouches - originalDisplayDistanceBetweenTouches_) * normalization_;  // In the range [-1,1]
+      double z;
+
+      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+      if (dy < -1.0)
+      {
+        z = MIN_ZOOM;
+      }
+      else if (dy > 1.0)
+      {
+        z = MAX_ZOOM;
+      }
+      else
+      {
+        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
+      }
+
+      z = pow(2.0, z);
+
+      view.SetZoom(z * originalZoom_);
+    }
+
+    that_.SetView(view);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/PanZoomMouseTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+
+namespace OrthancStone
+{
+  class PanZoomMouseTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;
+    std::vector<Touch> originalSceneTouches_;
+    Touch              originalSceneCenter_;
+    Touch              originalDisplayCenter_;
+    double             originalPanX_;
+    double             originalPanY_;
+    double             originalZoom_;
+    double             originalDisplayDistanceBetweenTouches_;
+    bool               idle_;
+    double             normalization_;
+
+  public:
+    PanZoomMouseTracker(WorldSceneWidget& that,
+                        const std::vector<Touch>& startTouches);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void MouseUp()
+    {
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- a/Framework/Widgets/SliceViewerWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/SliceViewerWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -60,6 +60,9 @@
     };
 
   private:
+    SliceViewerWidget(const SliceViewerWidget&);
+    SliceViewerWidget& operator=(const SliceViewerWidget&);
+
     class Scene;
     
     typedef std::map<const IVolumeSlicer*, size_t>  LayersIndex;
--- a/Framework/Widgets/TestCairoWidget.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/TestCairoWidget.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -99,7 +99,8 @@
     IMouseTracker* TestCairoWidget::CreateMouseTracker(MouseButton button,
                                                        int x,
                                                        int y,
-                                                       KeyboardModifiers modifiers)
+                                                       KeyboardModifiers modifiers,
+                                                       const std::vector<Touch>& touches)
     {
       UpdateStatusBar("Click");
       return NULL;
--- a/Framework/Widgets/TestCairoWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/TestCairoWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -51,7 +51,8 @@
       virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                                 int x,
                                                 int y,
-                                                KeyboardModifiers modifiers);
+                                                KeyboardModifiers modifiers,
+                                                const std::vector<Touch>& touches);
 
       virtual void MouseWheel(MouseWheelDirection direction,
                               int x,
--- a/Framework/Widgets/TestWorldSceneWidget.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/TestWorldSceneWidget.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -41,7 +41,8 @@
                                                           int viewportY,
                                                           double x,
                                                           double y,
-                                                          IStatusBar* statusBar)
+                                                          IStatusBar* statusBar,
+                                                          const std::vector<Touch>& touches)
       {
         if (statusBar)
         {
--- a/Framework/Widgets/WorldSceneWidget.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -23,6 +23,7 @@
 
 #include "PanMouseTracker.h"
 #include "ZoomMouseTracker.h"
+#include "PanZoomMouseTracker.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -72,11 +73,20 @@
     }
 
     virtual void MouseMove(int x,
-                           int y)
+                           int y,
+                           const std::vector<Touch>& displayTouches)
     {
       double sceneX, sceneY;
       view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
-      tracker_->MouseMove(x, y, sceneX, sceneY);
+
+      std::vector<Touch> sceneTouches;
+      for (size_t t = 0; t < displayTouches.size(); t++)
+      {
+        double sx, sy;
+        view_.MapPixelCenterToScene(sx, sy, (int)displayTouches[t].x, (int)displayTouches[t].y);
+        sceneTouches.push_back(Touch(sx, sy));
+      }
+      tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches);
     }
   };
 
@@ -145,7 +155,8 @@
   IMouseTracker* WorldSceneWidget::CreateMouseTracker(MouseButton button,
                                                       int x,
                                                       int y,
-                                                      KeyboardModifiers modifiers)
+                                                      KeyboardModifiers modifiers,
+                                                      const std::vector<Touch>& touches)
   {
     double sceneX, sceneY;
     view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
@@ -155,7 +166,7 @@
 
     if (interactor_)
     {
-      tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar()));
+      tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches));
     }
     
     if (tracker.get() != NULL)
@@ -164,17 +175,26 @@
     }
     else if (hasDefaultMouseEvents_)
     {
-      switch (button)
+      printf("has default mouse events\n");
+      if (touches.size() == 2)
       {
-        case MouseButton_Middle:
-          return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y));
+        printf("2 touches !\n");
+        return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches));
+      }
+      else
+      {
+        switch (button)
+        {
+          case MouseButton_Middle:
+            return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y));
 
-        case MouseButton_Right:
-          return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y));
+          case MouseButton_Right:
+            return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y));
 
-        default:
-          return NULL;
-      }      
+          default:
+            return NULL;
+        }
+      }
     }
     else
     {
--- a/Framework/Widgets/WorldSceneWidget.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/WorldSceneWidget.h	Thu Apr 18 10:43:17 2019 +0200
@@ -88,7 +88,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers);
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches);
 
     virtual void MouseWheel(MouseWheelDirection direction,
                             int x,
--- a/Framework/Widgets/ZoomMouseTracker.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/ZoomMouseTracker.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -61,7 +61,9 @@
   void ZoomMouseTracker::MouseMove(int displayX,
                                    int displayY,
                                    double x,
-                                   double y)
+                                   double y,
+                                   const std::vector<Touch>& displayTouches,
+                                   const std::vector<Touch>& sceneTouches)
   {
     static const double MIN_ZOOM = -4;
     static const double MAX_ZOOM = 4;
--- a/Framework/Widgets/ZoomMouseTracker.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/Widgets/ZoomMouseTracker.h	Thu Apr 18 10:43:17 2019 +0200
@@ -57,6 +57,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/dev.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Framework/dev.h	Thu Apr 18 10:43:17 2019 +0200
@@ -51,7 +51,7 @@
     std::auto_ptr<DownloadStack>  downloadStack_;
     bool                          computeRange_;
     size_t                        pendingSlices_;
-    
+
     void ScheduleSliceDownload()
     {
       assert(downloadStack_.get() != NULL);
@@ -70,20 +70,20 @@
       if (!GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(),
                                        b.GetGeometry().GetNormal()))
       {
-        LOG(ERROR) << "Some slice in the volume image is not parallel to the others";
+        LOG(ERROR) << "A slice in the volume image is not parallel to the others.";
         return false;
       }
 
       if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat())
       {
-        LOG(ERROR) << "The pixel format changes across the slices of the volume image";
+        LOG(ERROR) << "The pixel format changes across the slices of the volume image.";
         return false;
       }
 
       if (a.GetWidth() != b.GetWidth() ||
           a.GetHeight() != b.GetHeight())
       {
-        LOG(ERROR) << "The width/height of the slices change across the volume image";
+        LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image";
         return false;
       }
 
@@ -109,7 +109,7 @@
     void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message)
     {
       assert(&message.GetOrigin() == &loader_);
-      
+
       if (loader_.GetSliceCount() == 0)
       {
         LOG(ERROR) << "Empty volume image";
@@ -156,13 +156,13 @@
       LOG(INFO) << "Creating a volume image of size " << width << "x" << height
                 << "x" << loader_.GetSliceCount() << " in " << Orthanc::EnumerationToString(format);
 
-      image_.reset(new ImageBuffer3D(format, width, height, loader_.GetSliceCount(), computeRange_));
+      image_.reset(new ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSliceCount()), computeRange_));
       image_->SetAxialGeometry(loader_.GetSlice(0).GetGeometry());
       image_->SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(),
                                  loader_.GetSlice(0).GetPixelSpacingY(), spacingZ);
       image_->Clear();
 
-      downloadStack_.reset(new DownloadStack(loader_.GetSliceCount()));
+      downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSliceCount())));
       pendingSlices_ = loader_.GetSliceCount();
 
       for (unsigned int i = 0; i < 4; i++)  // Limit to 4 simultaneous downloads
@@ -175,7 +175,7 @@
       EmitMessage(ISlicedVolume::GeometryReadyMessage(*this));
     }
 
-    
+
     void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message)
     {
       assert(&message.GetOrigin() == &loader_);
@@ -184,7 +184,7 @@
       EmitMessage(ISlicedVolume::GeometryErrorMessage(*this));
     }
 
-    
+
     void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message)
     {
       assert(&message.GetOrigin() == &loader_);
@@ -210,7 +210,7 @@
       ScheduleSliceDownload();
     }
 
-    
+
     void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message)
     {
       assert(&message.GetOrigin() == &loader_);
@@ -312,11 +312,11 @@
     double               sliceThickness_;
     CoordinateSystem3D   reference_;
     DicomFrameConverter  converter_;
-    
+
     double ComputeAxialThickness(const OrthancVolumeImage& volume) const
     {
       double thickness;
-      
+
       size_t n = volume.GetSliceCount();
       if (n > 1)
       {
@@ -342,11 +342,11 @@
         return thickness;
       }
     }
-    
+
     void SetupAxial(const OrthancVolumeImage& volume)
     {
       const Slice& axial = volume.GetSlice(0);
-      
+
       width_ = axial.GetWidth();
       height_ = axial.GetHeight();
       depth_ = volume.GetSliceCount();
@@ -364,7 +364,7 @@
       double axialThickness = ComputeAxialThickness(volume);
 
       width_ = axial.GetWidth();
-      height_ = volume.GetSliceCount();
+      height_ = static_cast<unsigned int>(volume.GetSliceCount());
       depth_ = axial.GetHeight();
 
       pixelSpacingX_ = axial.GetPixelSpacingX();
@@ -373,11 +373,11 @@
 
       Vector origin = axial.GetGeometry().GetOrigin();
       origin += (static_cast<double>(volume.GetSliceCount() - 1) *
-                 axialThickness * axial.GetGeometry().GetNormal());
-      
+                axialThickness * axial.GetGeometry().GetNormal());
+
       reference_ = CoordinateSystem3D(origin,
                                       axial.GetGeometry().GetAxisX(),
-                                      -axial.GetGeometry().GetNormal());
+                                      - axial.GetGeometry().GetNormal());
     }
 
     void SetupSagittal(const OrthancVolumeImage& volume)
@@ -386,7 +386,7 @@
       double axialThickness = ComputeAxialThickness(volume);
 
       width_ = axial.GetHeight();
-      height_ = volume.GetSliceCount();
+      height_ = static_cast<unsigned int>(volume.GetSliceCount());
       depth_ = axial.GetWidth();
 
       pixelSpacingX_ = axial.GetPixelSpacingY();
@@ -395,8 +395,8 @@
 
       Vector origin = axial.GetGeometry().GetOrigin();
       origin += (static_cast<double>(volume.GetSliceCount() - 1) *
-                 axialThickness * axial.GetGeometry().GetNormal());
-      
+                axialThickness * axial.GetGeometry().GetNormal());
+
       reference_ = CoordinateSystem3D(origin,
                                       axial.GetGeometry().GetAxisY(),
                                       axial.GetGeometry().GetNormal());
@@ -415,20 +415,20 @@
 
       switch (projection)
       {
-        case VolumeProjection_Axial:
-          SetupAxial(volume);
-          break;
+      case VolumeProjection_Axial:
+        SetupAxial(volume);
+        break;
 
-        case VolumeProjection_Coronal:
-          SetupCoronal(volume);
-          break;
+      case VolumeProjection_Coronal:
+        SetupCoronal(volume);
+        break;
 
-        case VolumeProjection_Sagittal:
-          SetupSagittal(volume);
-          break;
+      case VolumeProjection_Sagittal:
+        SetupSagittal(volume);
+        break;
 
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
     }
 
@@ -441,7 +441,7 @@
     {
       return reference_.GetNormal();
     }
-    
+
     bool LookupSlice(size_t& index,
                      const CoordinateSystem3D& slice) const
     {
@@ -452,14 +452,14 @@
       {
         return false;
       }
-      
+
       double z = (reference_.ProjectAlongNormal(slice.GetOrigin()) -
                   reference_.ProjectAlongNormal(reference_.GetOrigin())) / sliceThickness_;
 
       int s = static_cast<int>(boost::math::iround(z));
 
       if (s < 0 ||
-          s >= static_cast<int>(depth_))
+        s >= static_cast<int>(depth_))
       {
         return false;
       }
@@ -507,9 +507,9 @@
       RendererFactory(const Orthanc::ImageAccessor& frame,
                       const Slice& slice,
                       bool isFullQuality) :
-        frame_(frame),
-        slice_(slice),
-        isFullQuality_(isFullQuality)
+                      frame_(frame),
+                      slice_(slice),
+                      isFullQuality_(isFullQuality)
       {
       }
 
@@ -525,35 +525,35 @@
     std::auto_ptr<VolumeImageGeometry>  coronalGeometry_;
     std::auto_ptr<VolumeImageGeometry>  sagittalGeometry_;
 
-    
+
     bool IsGeometryReady() const
     {
       return axialGeometry_.get() != NULL;
     }
-    
+
     void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message)
     {
       assert(&message.GetOrigin() == &volume_);
-      
+
       // These 3 values are only used to speed up the IVolumeSlicer
       axialGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Axial));
       coronalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Coronal));
       sagittalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Sagittal));
-      
+
       EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this));
     }
 
     void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message)
     {
       assert(&message.GetOrigin() == &volume_);
-      
+
       EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this));
     }
 
     void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message)
     {
       assert(&message.GetOrigin() == &volume_);
-      
+
       EmitMessage(IVolumeSlicer::ContentChangedMessage(*this));
     }
 
@@ -576,17 +576,17 @@
 
       switch (projection)
       {
-        case VolumeProjection_Axial:
-          return *axialGeometry_;
+      case VolumeProjection_Axial:
+        return *axialGeometry_;
 
-        case VolumeProjection_Sagittal:
-          return *sagittalGeometry_;
+      case VolumeProjection_Sagittal:
+        return *sagittalGeometry_;
 
-        case VolumeProjection_Coronal:
-          return *coronalGeometry_;
+      case VolumeProjection_Coronal:
+        return *coronalGeometry_;
 
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
     }
 
@@ -625,11 +625,11 @@
 
 
   public:
-    VolumeImageMPRSlicer(MessageBroker& broker, 
+    VolumeImageMPRSlicer(MessageBroker& broker,
                          OrthancVolumeImage&  volume) :
-      IVolumeSlicer(broker),
-      IObserver(broker),
-      volume_(volume)
+                         IVolumeSlicer(broker),
+                         IObserver(broker),
+                         volume_(volume)
     {
       volume_.RegisterObserverCallback(
         new Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage>
@@ -652,7 +652,7 @@
                            const CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE
     {
       VolumeProjection projection;
-      
+
       if (!IsGeometryReady() ||
           !DetectProjection(projection, viewportSlice))
       {
@@ -664,16 +664,15 @@
         // we only consider one single reference slice (the one with index 0).
         std::auto_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0));
         slice->GetExtent(points);
-        
+
         return true;
       }
     }
-    
 
     virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE
     {
       VolumeProjection projection;
-      
+
       if (IsGeometryReady() &&
           DetectProjection(projection, viewportSlice))
       {
@@ -688,7 +687,7 @@
           std::auto_ptr<Orthanc::Image> frame;
 
           {
-            ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, closest);
+            ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, static_cast<unsigned int>(closest));
 
             // TODO Transfer ownership if non-axial, to avoid memcpy
             frame.reset(Orthanc::Image::Clone(reader.GetAccessor()));
@@ -738,11 +737,15 @@
     virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
                                                         const ViewportGeometry& view,
                                                         MouseButton button,
+                                                        KeyboardModifiers modifiers,
+                                                        int viewportX,
+                                                        int viewportY,
                                                         double x,
                                                         double y,
-                                                        IStatusBar* statusBar)
+                                                        IStatusBar* statusBar,
+                                                        const std::vector<Touch>& touches) ORTHANC_OVERRIDE
     {
-      return NULL;
+      return  NULL;
     }
 
     virtual void MouseOver(CairoContext& context,
@@ -750,29 +753,29 @@
                            const ViewportGeometry& view,
                            double x,
                            double y,
-                           IStatusBar* statusBar)
+                           IStatusBar* statusBar) ORTHANC_OVERRIDE
     {
     }
 
     virtual void MouseWheel(WorldSceneWidget& widget,
                             MouseWheelDirection direction,
                             KeyboardModifiers modifiers,
-                            IStatusBar* statusBar)
+                            IStatusBar* statusBar) ORTHANC_OVERRIDE
     {
       int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1);
 
       switch (direction)
       {
-        case MouseWheelDirection_Up:
-          OffsetSlice(-scale);
-          break;
+      case MouseWheelDirection_Up:
+        OffsetSlice(-scale);
+        break;
 
-        case MouseWheelDirection_Down:
-          OffsetSlice(scale);
-          break;
+      case MouseWheelDirection_Down:
+        OffsetSlice(scale);
+        break;
 
-        default:
-          break;
+      default:
+        break;
       }
     }
 
@@ -780,16 +783,16 @@
                             KeyboardKeys key,
                             char keyChar,
                             KeyboardModifiers modifiers,
-                            IStatusBar* statusBar)
+                            IStatusBar* statusBar) ORTHANC_OVERRIDE
     {
       switch (keyChar)
       {
-        case 's':
-          widget.FitContent();
-          break;
+      case 's':
+        widget.FitContent();
+        break;
 
-        default:
-          break;
+      default:
+        break;
       }
     }
 
@@ -798,9 +801,9 @@
                           OrthancVolumeImage& volume,
                           SliceViewerWidget& widget,
                           VolumeProjection projection) :
-      IObserver(broker),
-      widget_(widget),
-      projection_(projection)
+                          IObserver(broker),
+                          widget_(widget),
+                          projection_(projection)
     {
       widget.SetInteractor(*this);
 
@@ -839,7 +842,7 @@
 
         if (slice >= static_cast<int>(slices_->GetSliceCount()))
         {
-          slice = slices_->GetSliceCount() - 1;
+          slice = static_cast<unsigned int>(slices_->GetSliceCount()) - 1;
         }
 
         if (slice != static_cast<int>(slice_))
@@ -898,7 +901,7 @@
     SliceViewerWidget&  otherPlane_;
 
   public:
-    ReferenceLineSource(MessageBroker& broker, 
+    ReferenceLineSource(MessageBroker& broker,
                         SliceViewerWidget&  otherPlane) :
       IVolumeSlicer(broker),
       otherPlane_(otherPlane)
@@ -915,7 +918,7 @@
     virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice)
     {
       Slice reference(viewportSlice, 0.001);
-      
+
       Vector p, d;
 
       const CoordinateSystem3D& slice = otherPlane_.GetSlice();
@@ -935,7 +938,7 @@
         viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
 
         const Extent2D extent = otherPlane_.GetSceneExtent();
-        
+
         if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2,
                                                  x1, y1, x2, y2,
                                                  extent.GetX1(), extent.GetY1(),
--- a/Platforms/Generic/Oracle.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Generic/Oracle.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -70,7 +70,7 @@
           {
             command.Execute();
           }
-          catch (Orthanc::OrthancException& ex)
+          catch (Orthanc::OrthancException& /*ex*/)
           {
             // this is probably a curl error that has been triggered.  We may just ignore it.
             // The command.success_ will stay at false and this will be handled in the command.Commit
--- a/Platforms/Wasm/Defaults.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/Defaults.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -49,7 +49,7 @@
 
     viewports_.push_back(viewport);
 
-    printf("There are now %d viewports in C++\n", viewports_.size());
+    printf("There are now %lu viewports in C++\n", viewports_.size());
 
     viewport->SetStatusBar(statusBar_);
 
@@ -64,7 +64,7 @@
   void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) {
     viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;});
 
-    printf("There are now %d viewports in C++\n", viewports_.size());
+    printf("There are now %lu viewports in C++\n", viewports_.size());
   }
 
   void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) {
@@ -191,7 +191,7 @@
         return;  // Unknown button
     }
 
-    viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */);
+    viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None, std::vector<OrthancStone::Touch>());
   }
   
 
@@ -222,9 +222,82 @@
                                               int x,
                                               int y)
   {
-    viewport->MouseMove(x, y);
+    viewport->MouseMove(x, y, std::vector<OrthancStone::Touch>());
+  }
+
+  void GetTouchVector(std::vector<OrthancStone::Touch>& output,
+                      int touchCount,
+                      float x0,
+                      float y0,
+                      float x1,
+                      float y1,
+                      float x2,
+                      float y2)
+  {
+    // TODO: it might be nice to try to pass all the x0,y0 coordinates as arrays but that's not so easy to pass array between JS and C++
+    if (touchCount > 0)
+    {
+      output.push_back(OrthancStone::Touch(x0, y0));
+    }
+    if (touchCount > 1)
+    {
+      output.push_back(OrthancStone::Touch(x1, y1));
+    }
+    if (touchCount > 2)
+    {
+      output.push_back(OrthancStone::Touch(x2, y2));
+    }
+
   }
-  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportTouchStart(ViewportHandle viewport,
+                                              int touchCount,
+                                              float x0,
+                                              float y0,
+                                              float x1,
+                                              float y1,
+                                              float x2,
+                                              float y2)
+  {
+    printf("touch start with %d touches\n", touchCount);
+
+    std::vector<OrthancStone::Touch> touches;
+    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
+    viewport->TouchStart(touches);
+  }
+
+  void EMSCRIPTEN_KEEPALIVE ViewportTouchMove(ViewportHandle viewport,
+                                              int touchCount,
+                                              float x0,
+                                              float y0,
+                                              float x1,
+                                              float y1,
+                                              float x2,
+                                              float y2)
+  {
+    printf("touch move with %d touches\n", touchCount);
+
+    std::vector<OrthancStone::Touch> touches;
+    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
+    viewport->TouchMove(touches);
+  }
+
+  void EMSCRIPTEN_KEEPALIVE ViewportTouchEnd(ViewportHandle viewport,
+                                              int touchCount,
+                                              float x0,
+                                              float y0,
+                                              float x1,
+                                              float y1,
+                                              float x2,
+                                              float y2)
+  {
+    printf("touch end with %d touches remaining\n", touchCount);
+
+    std::vector<OrthancStone::Touch> touches;
+    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
+    viewport->TouchEnd(touches);
+  }
+
   void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport,
                                                int key,
                                                const char* keyChar, 
@@ -269,23 +342,21 @@
     viewport->MouseLeave();
   }
 
-  const char* EMSCRIPTEN_KEEPALIVE SendMessageToStoneApplication(const char* message) 
+  const char* EMSCRIPTEN_KEEPALIVE SendSerializedMessageToStoneApplication(const char* message) 
   {
     static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread)
 
-    printf("SendMessageToStoneApplication\n");
+    printf("SendSerializedMessageToStoneApplication\n");
     printf("%s", message);
 
     if (applicationWasmAdapter.get() != NULL) {
-      printf("sending message to C++\n");
-      applicationWasmAdapter->HandleMessageFromWeb(output, std::string(message));
+      applicationWasmAdapter->HandleSerializedMessageFromWeb(output, std::string(message));
       return output.c_str();
     }
-    printf("This stone application does not have a Web Adapter");
+    printf("This Stone application does not have a Web Adapter");
     return NULL;
   }
 
-
 #ifdef __cplusplus
 }
 #endif
--- a/Platforms/Wasm/Defaults.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/Defaults.h	Thu Apr 18 10:43:17 2019 +0200
@@ -16,7 +16,8 @@
   
   // JS methods accessible from C++
   extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle);
-  extern void UpdateStoneApplicationStatusFromCpp(const char* statusUpdateMessage);
+  extern void UpdateStoneApplicationStatusFromCppWithString(const char* statusUpdateMessage);
+  extern void UpdateStoneApplicationStatusFromCppWithSerializedMessage(const char* statusUpdateMessage);
   
   // C++ methods accessible from JS
   extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle);
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -2,50 +2,59 @@
 
 #include "Framework/Toolbox/MessagingToolbox.h"
 #include "Framework/StoneException.h"
-#include <Applications/Commands/BaseCommandBuilder.h>
 #include <stdio.h>
 #include "Platforms/Wasm/Defaults.h"
 
 namespace OrthancStone
 {
-    WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application)
-    : IObserver(broker),
-      application_(application)
+  WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application)
+  : IObserver(broker),
+    application_(application)
+  {
+  }
+
+  void WasmPlatformApplicationAdapter::HandleSerializedMessageFromWeb(std::string& output, const std::string& input)
+  {
+    try
     {
+      application_.HandleSerializedMessage(input.c_str());
     }
-
-    void WasmPlatformApplicationAdapter::HandleMessageFromWeb(std::string& output, const std::string& input)
+    catch (StoneException& exc)
+    {
+      printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode());
+      printf("While interpreting input: '%s'\n", input.c_str());
+      output = std::string("ERROR : ");
+    }
+    catch (std::exception& exc)
     {
-      try
-      {
-        Json::Value inputJson;
-        // if the message is a command, build it and execute it
-        if (MessagingToolbox::ParseJson(inputJson, input.c_str(), input.size()))
-        {
-            std::unique_ptr<ICommand> command(application_.GetCommandBuilder().CreateFromJson(inputJson));
-            if (command.get() == NULL) 
-              printf("Could not parse command: '%s'\n", input.c_str());
-            else
-              application_.ExecuteCommand(*command);
-        } 
-      }
-      catch (StoneException& exc)
-      {
-        printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode());
-        printf("While interpreting input: '%s'\n", input.c_str());
-      }
+      printf("Error while handling message from web (error text = %s):\n", exc.what());
+      printf("While interpreting input: '%s'\n", input.c_str());
+      output = std::string("ERROR : ");
+    }
+  }
+
+  void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage)
+  {
+    try
+    {
+      UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str());
     }
-
-    void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage)
+    catch (...)
     {
-      try
-      {
-        UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
-      }
-      catch (...)
-      {
-        printf("Error while handling message to web\n");
-      }
+      printf("Error while handling string message to web\n");
     }
+  }
+
+  void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWebWithSerializedMessage(const std::string& statusUpdateMessage)
+  {
+    try
+    {
+      UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str());
+    }
+    catch (...)
+    {
+      printf("Error while handling serialized message to web\n");
+    }
+  }
 
 }
\ No newline at end of file
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.h	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.h	Thu Apr 18 10:43:17 2019 +0200
@@ -12,7 +12,8 @@
     public:
       WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application);
 
-      virtual void HandleMessageFromWeb(std::string& output, const std::string& input);
-      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage);
+      virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input);
+      virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage);
+      virtual void NotifyStatusUpdateFromCppToWebWithSerializedMessage(const std::string& statusUpdateMessage);
   };
 }
\ No newline at end of file
--- a/Platforms/Wasm/WasmWebService.js	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/WasmWebService.js	Thu Apr 18 10:43:17 2019 +0200
@@ -37,7 +37,7 @@
 
   WasmWebService_ScheduleLaterCachedSuccessNotification: function (brol) {
     setTimeout(function() {
-      WasmWebService_NotifyCachedSuccess(brol);
+      window.WasmWebService_NotifyCachedSuccess(brol);
     }, 0);
   },
 
@@ -62,10 +62,10 @@
           var headers = _malloc(s.length + 1);
           stringToUTF8(s, headers, s.length + 1);
 
-          WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
+          window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
                                        this.response.byteLength, headers, payload);
         } else {
-          WasmWebService_NotifyError(callableFailure, url_, payload);
+          window.WasmWebService_NotifyError(callableFailure, url_, payload);
         }
       }
     }
@@ -94,10 +94,10 @@
           var headers = _malloc(s.length + 1);
           stringToUTF8(s, headers, s.length + 1);
 
-          WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
+          window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
                                        this.response.byteLength, headers, payload);
         } else {
-          WasmWebService_NotifyError(callableFailure, url_, payload);
+          window.WasmWebService_NotifyError(callableFailure, url_, payload);
         }
       }
     }
--- a/Platforms/Wasm/default-library.js	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/default-library.js	Thu Apr 18 10:43:17 2019 +0200
@@ -7,9 +7,15 @@
   CreateWasmViewportFromCpp: function(htmlCanvasId) {
     return window.CreateWasmViewport(htmlCanvasId);
   },
-  // each time the StoneApplication updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface
-  UpdateStoneApplicationStatusFromCpp: function(statusUpdateMessage) {
+  // each time the StoneApplication updates its status, it may signal it 
+  // through this method. i.e, to change the status of a button in the web interface
+  UpdateStoneApplicationStatusFromCppWithString: function(statusUpdateMessage) {
     var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage);
-    UpdateWebApplication(statusUpdateMessage_);
+    window.UpdateWebApplicationWithString(statusUpdateMessage_);
+  },
+  // same, but with a serialized message
+  UpdateStoneApplicationStatusFromCppWithSerializedMessage: function(statusUpdateMessage) {
+    var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage);
+    window.UpdateWebApplicationWithSerializedMessage(statusUpdateMessage_);
   }
 });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/logger.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,90 @@
+export enum LogSource {
+  Cpp,
+  Typescript
+}
+
+export class StandardConsoleLogger {
+  public showSource: boolean = true;
+
+  public debug(...args: any[]): void {
+    this._debug(LogSource.Typescript, ...args);
+  }
+
+  public info(...args: any[]): void {
+    this._info(LogSource.Typescript, ...args);
+  }
+
+  public infoFromCpp(message: string): void {
+    this._info(LogSource.Cpp, message);
+  }
+
+  public warning(...args: any[]): void {
+    this._warning(LogSource.Typescript, ...args);
+  }
+
+  public error(...args: any[]): void {
+    this._error(LogSource.Typescript, ...args);
+  }
+
+  public errorFromCpp(message: string): void {
+    this._error(LogSource.Cpp, message);
+  }
+
+  public _debug(source: LogSource, ...args: any[]): void {
+    var output = this.getOutput(source, args);
+    console.debug(...output);
+  }
+
+  private _info(source: LogSource, ...args: any[]): void {
+    var output = this.getOutput(source, args);
+    console.info(...output);
+  }
+
+  public _warning(source: LogSource, ...args: any[]): void {
+    var output = this.getOutput(source, args);
+    console.warn(...output);
+  }
+
+  public _error(source: LogSource, ...args: any[]): void {
+    var output = this.getOutput(source, args);
+    console.error(...output);
+  }
+
+
+  private getOutput(source: LogSource, args: any[]): any[] {
+    var prefix = this.getPrefix();
+    var prefixAndSource = [];
+
+    if (prefix != null) {
+      prefixAndSource = [prefix];
+    } 
+
+    if (this.showSource) {
+      if (source == LogSource.Typescript) {
+        prefixAndSource = [...prefixAndSource, "TS "];
+      } else if (source == LogSource.Cpp) {
+        prefixAndSource = [...prefixAndSource, "C++"];
+      }
+    }
+
+    if (prefixAndSource.length > 0) {
+      prefixAndSource = [...prefixAndSource, "|"];
+    }
+
+    return [...prefixAndSource, ...args];
+  }
+
+  protected getPrefix(): string {
+    return null;
+  }
+}
+
+export class TimeConsoleLogger extends StandardConsoleLogger {
+  protected getPrefix(): string {
+    let now = new Date();
+    let timeString = now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0") + ":" + now.getSeconds().toString().padStart(2, "0") + "." + now.getMilliseconds().toString().padStart(3, "0");
+    return timeString;
+  }
+}
+
+export var defaultLogger: StandardConsoleLogger = new TimeConsoleLogger();
--- a/Platforms/Wasm/stone-framework-loader.ts	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/stone-framework-loader.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -2,6 +2,7 @@
  * This file contains primitives to interface with WebAssembly and
  * with the Stone framework.
  **/
+import * as Logger from './logger'
 
 export declare type InitializationCallback = () => void;
 
@@ -56,13 +57,13 @@
   public static Initialize( verbose: boolean,
                             callback: InitializationCallback)
   {
-    console.log('Initializing WebAssembly Module');
+    Logger.defaultLogger.debug('Initializing WebAssembly Module');
 
     // (<any> window).
     (<any> window).StoneFrameworkModule = {
       preRun: [ 
         function() {
-          console.log('Loading the Stone Framework using WebAssembly');
+          Logger.defaultLogger.debug('Loading the Stone Framework using WebAssembly');
         }
       ],
       postRun: [ 
@@ -70,16 +71,16 @@
           // This function is called by ".js" wrapper once the ".wasm"
           // WebAssembly module has been loaded and compiled by the
           // browser
-          console.log('WebAssembly is ready');
+          Logger.defaultLogger.debug('WebAssembly is ready');
           Framework.singleton_ = new Framework(verbose);
           callback();
         }
       ],
       print: function(text : string) {
-        console.log(text);
+        Logger.defaultLogger.infoFromCpp(text);
       },
       printErr: function(text : string) {
-        console.error(text);
+        Logger.defaultLogger.errorFromCpp(text);
       },
       totalDependencies: 0
     };
--- a/Platforms/Wasm/tsconfig-stone.json	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/tsconfig-stone.json	Thu Apr 18 10:43:17 2019 +0200
@@ -1,6 +1,7 @@
 {
     "include" : [
         "stone-framework-loader.ts",
+        "logger.ts",
         "wasm-application-runner.ts",
         "wasm-viewport.ts"
     ]
--- a/Platforms/Wasm/wasm-application-runner.ts	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/wasm-application-runner.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -1,5 +1,6 @@
 import Stone = require('./stone-framework-loader');
 import StoneViewport = require('./wasm-viewport');
+import * as Logger from './logger'
 
 if (!('WebAssembly' in window)) {
   alert('Sorry, your browser does not support WebAssembly :(');
@@ -19,7 +20,7 @@
 export var CreateCppViewport: Function = null;
 var ReleaseCppViewport: Function = null;
 var StartWasmApplication: Function = null;
-export var SendMessageToStoneApplication: Function = null;
+export var SendSerializedMessageToStoneApplication: Function = null;
 
 function DoAnimationThread() {
   if (WasmDoAnimation != null) {
@@ -84,7 +85,7 @@
   // the WebAssembly environment) and then, create and initialize the Wasm application
   Stone.Framework.Initialize(true, function () {
 
-    console.log("Connecting C++ methods to JS methods");
+    Logger.defaultLogger.debug("Connecting C++ methods to JS methods");
     
     SetStartupParameter = (<any> window).StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']);
     CreateWasmApplication = (<any> window).StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']);
@@ -99,14 +100,14 @@
     // no need to put this into the globals for it's only used in this very module
     WasmDoAnimation = (<any> window).StoneFrameworkModule.cwrap('WasmDoAnimation', null, []);
 
-    SendMessageToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']);
+    SendSerializedMessageToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendSerializedMessageToStoneApplication', 'string', ['string']);
 
-    console.log("Connecting C++ methods to JS methods - done");
+    Logger.defaultLogger.debug("Connecting C++ methods to JS methods - done");
 
     // Prevent scrolling
     document.body.addEventListener('touchmove', function (event) {
       event.preventDefault();
-    }, false);
+    }, { passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface
 
     _InitializeWasmApplication(orthancBaseUrl);
   });
--- a/Platforms/Wasm/wasm-viewport.ts	Thu Apr 18 10:42:01 2019 +0200
+++ b/Platforms/Wasm/wasm-viewport.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -1,5 +1,5 @@
 import wasmApplicationRunner = require('./wasm-application-runner');
-//import stoneFrameworkLoader = require('./stone-framework-loader');
+import * as Logger from './logger'
 
 var isPendingRedraw = false;
 
@@ -7,7 +7,7 @@
 {
   if (!isPendingRedraw) {
     isPendingRedraw = true;
-    console.log('Scheduling a refresh of the viewport, as its content changed');
+    Logger.defaultLogger.debug('Scheduling a refresh of the viewport, as its content changed');
     window.requestAnimationFrame(function() {
       isPendingRedraw = false;
       WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw();
@@ -30,13 +30,6 @@
  
 (<any>window).CreateWasmViewport = CreateWasmViewport;
 
-//  export declare type InitializationCallback = () => void;
-  
-//  export declare var StoneFrameworkModule : any;
-  
-  //const ASSETS_FOLDER : string = "assets/lib";
-  //const WASM_FILENAME : string = "orthanc-framework";
-
 export class WasmViewport {
 
     private static viewportsMapByCppHandle_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); // key = the C++ handle
@@ -65,6 +58,9 @@
     private ViewportMouseLeave : Function;
     private ViewportMouseWheel : Function;
     private ViewportKeyPressed : Function;
+    private ViewportTouchStart : Function;
+    private ViewportTouchMove : Function;
+    private ViewportTouchEnd : Function;
 
     private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object
 
@@ -78,7 +74,7 @@
       this.canvasId_ = canvasId;
       this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement;
       if (this.htmlCanvas_ == null) {
-        console.log("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'");
+        Logger.defaultLogger.error("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'");
       }
       this.context_ = this.htmlCanvas_.getContext('2d');
 
@@ -91,6 +87,9 @@
       this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]);
       this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]);
       this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'number', 'string', 'number', 'number' ]);
+      this.ViewportTouchStart = this.module_.cwrap('ViewportTouchStart', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]);
+      this.ViewportTouchMove = this.module_.cwrap('ViewportTouchMove', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]);
+      this.ViewportTouchEnd = this.module_.cwrap('ViewportTouchEnd', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]);
     }
 
     public GetCppViewport() : number {
@@ -101,7 +100,7 @@
       if (WasmViewport.viewportsMapByCppHandle_[cppViewportHandle] !== undefined) {
         return WasmViewport.viewportsMapByCppHandle_[cppViewportHandle];
       }
-      console.log("WasmViewport not found !");
+      Logger.defaultLogger.error("WasmViewport not found !");
       return undefined;
     }
 
@@ -109,7 +108,7 @@
       if (WasmViewport.viewportsMapByCanvasId_[canvasId] !== undefined) {
         return WasmViewport.viewportsMapByCanvasId_[canvasId];
       }
-      console.log("WasmViewport not found !");
+      Logger.defaultLogger.error("WasmViewport not found !");
       return undefined;
     }
 
@@ -126,13 +125,13 @@
                          this.imageData_.width,
                          this.imageData_.height,
                          this.renderingBuffer_) == 0) {
-        console.log('The rendering has failed');
+        Logger.defaultLogger.error('The rendering has failed');
       } else {
         // Create an accessor to the rendering buffer (i.e. create a
         // "window" above the heap of the WASM module), then copy it to
         // the ImageData object
         this.imageData_.data.set(new Uint8ClampedArray(
-          this.module_.buffer,
+          this.module_.HEAPU8.buffer,
           this.renderingBuffer_,
           this.imageData_.width * this.imageData_.height * 4));
         
@@ -151,7 +150,7 @@
       this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth;  
       this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight;  
 
-      console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height);
+      Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height);
 
       if (this.imageData_ === null) {
         this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height);
@@ -197,7 +196,8 @@
       this.htmlCanvas_.addEventListener('mousedown', function(event) {
         var x = event.pageX - this.offsetLeft;
         var y = event.pageY - this.offsetTop;
-        that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/);    
+
+       that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/);    
       });
     
       this.htmlCanvas_.addEventListener('mousemove', function(event) {
@@ -227,22 +227,62 @@
         var y = event.pageY - this.offsetTop;
         that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey);
         event.preventDefault();
-      });
+      }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface
 
-      this.htmlCanvas_.addEventListener('touchstart', function(event) {
+      this.htmlCanvas_.addEventListener('touchstart', function(event: TouchEvent) {
         // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport)
         event.preventDefault();
         event.stopPropagation();
 
-        that.ResetTouch();
-      });
+        // TODO: find a way to pass the coordinates as an array between JS and C++
+        var x0 = 0;
+        var y0 = 0;
+        var x1 = 0;
+        var y1 = 0;
+        var x2 = 0;
+        var y2 = 0;
+        if (event.targetTouches.length > 0) {
+          x0 = event.targetTouches[0].pageX;
+          y0 = event.targetTouches[0].pageY;
+        }
+        if (event.targetTouches.length > 1) {
+          x1 = event.targetTouches[1].pageX;
+          y1 = event.targetTouches[1].pageY;
+        }
+        if (event.targetTouches.length > 2) {
+          x2 = event.targetTouches[2].pageX;
+          y2 = event.targetTouches[2].pageY;
+        }
+
+        that.ViewportTouchStart(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2);
+      }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface
     
       this.htmlCanvas_.addEventListener('touchend', function(event) {
         // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport)
         event.preventDefault();
         event.stopPropagation();
 
-        that.ResetTouch();
+        // TODO: find a way to pass the coordinates as an array between JS and C++
+        var x0 = 0;
+        var y0 = 0;
+        var x1 = 0;
+        var y1 = 0;
+        var x2 = 0;
+        var y2 = 0;
+        if (event.targetTouches.length > 0) {
+          x0 = event.targetTouches[0].pageX;
+          y0 = event.targetTouches[0].pageY;
+        }
+        if (event.targetTouches.length > 1) {
+          x1 = event.targetTouches[1].pageX;
+          y1 = event.targetTouches[1].pageY;
+        }
+        if (event.targetTouches.length > 2) {
+          x2 = event.targetTouches[2].pageX;
+          y2 = event.targetTouches[2].pageY;
+        }
+
+        that.ViewportTouchEnd(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2);
       });
     
       this.htmlCanvas_.addEventListener('touchmove', function(event: TouchEvent) {
@@ -251,53 +291,31 @@
         event.preventDefault();
         event.stopPropagation();
 
-        // if (!that.touchGestureInProgress_) {
-        //   // starting a new gesture
-        //   that.touchCount_ = event.targetTouches.length;
-        //   for (var t = 0; t < event.targetTouches.length; t++) {
-        //     that.touchGestureLastCoordinates_.push([event.targetTouches[t].pageX, event.targetTouches[t].pageY]);
-        //   }
-        //   that.touchGestureInProgress_ = true;
-        // } else {
-        //   // continuing a gesture
-        //   // TODO: we shall probably forward all touches to the C++ code and let the "interactors/trackers" handle them
 
-        //   if (that.touchCount_ == 1) { // consider it's a left mouse drag
-
-        //   }
-        // }
-
-        // TODO: we shall probably forward all touches to the C++ code and let the "interactors/trackers" handle them
-
-        if (that.touchTranslation_.length == 2) { // 
-          var t = that.GetTouchTranslation(event);
-          that.ViewportMouseMove(that.pimpl_, t[0], t[1]);
+        // TODO: find a way to pass the coordinates as an array between JS and C++
+        var x0 = 0;
+        var y0 = 0;
+        var x1 = 0;
+        var y1 = 0;
+        var x2 = 0;
+        var y2 = 0;
+        if (event.targetTouches.length > 0) {
+          x0 = event.targetTouches[0].pageX;
+          y0 = event.targetTouches[0].pageY;
         }
-        else if (that.touchZoom_.length == 3) {
-          var z0 = that.touchZoom_;
-          var z1 = that.GetTouchZoom(event);
-          that.ViewportMouseMove(that.pimpl_, z0[0], z0[1] - z0[2] + z1[2]);
+        if (event.targetTouches.length > 1) {
+          x1 = event.targetTouches[1].pageX;
+          y1 = event.targetTouches[1].pageY;
         }
-        else {
-          // Realize the gesture event
-          if (event.targetTouches.length == 1) {
-            // Exactly one finger inside the canvas => Setup a translation
-            that.touchTranslation_ = that.GetTouchTranslation(event);
-            that.ViewportMouseDown(that.pimpl_, 
-                                  0 /* left button */,
-                                  that.touchTranslation_[0],
-                                  that.touchTranslation_[1], 0);
-          } else if (event.targetTouches.length == 2) {
-            // Exactly 2 fingers inside the canvas => Setup a pinch/zoom
-            that.touchZoom_ = that.GetTouchZoom(event);
-            var z0 = that.touchZoom_;
-            that.ViewportMouseDown(that.pimpl_, 
-                                  2 /* right button */,
-                                  z0[0],
-                                  z0[1], 0);
-          }        
+        if (event.targetTouches.length > 2) {
+          x2 = event.targetTouches[2].pageX;
+          y2 = event.targetTouches[2].pageY;
         }
-      });
+
+        that.ViewportTouchMove(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2);
+        return;
+
+      }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface
     }  
 
   public ResetTouch() {
@@ -308,10 +326,6 @@
 
     this.touchTranslation_ = false;
     this.touchZoom_ = false;
-
-    // this.touchGestureInProgress_ = false;
-    // this.touchCount_ = 0;
-    // this.touchGestureLastCoordinates_ = [];
   }
   
   public GetTouchTranslation(event) {
@@ -334,7 +348,5 @@
       d
     ];
   }
-    
+   
 }
-
-  
\ No newline at end of file
--- a/README.md	Thu Apr 18 10:42:01 2019 +0200
+++ b/README.md	Thu Apr 18 10:43:17 2019 +0200
@@ -125,7 +125,10 @@
 source code in an `orthanc` folder next to the Stone of Orthanc
 repository, please enter the following:
 
+**Simple make generator with dynamic build**
+
 ```
+# Please set $currentDir to the current folder
 mkdir -p ~/builds/orthanc-stone-build
 cd ~/builds/orthanc-stone-build
 cmake -DORTHANC_FRAMEWORK_SOURCE=path \
@@ -134,6 +137,28 @@
     ~/orthanc-stone/Applications/Samples/
 ```
 
+**Ninja generator with static SDL build (pwsh script)**
+
+```
+# Please yourself one level above the orthanc-stone and orthanc folders
+if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl }
+cd stone_build_sdl
+cmake -G Ninja -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/
+```
+
+**Visual Studio 2017 generator with static SDL build  (pwsh script)**
+
+```
+# The following will use Visual Studio 2017 to build the SDL samples
+# in debug mode (with multiple compilers in parallel). NOTE: place 
+# yourself one level above the `orthanc-stone` and `orthanc` folders
+
+if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl }
+cd stone_build_sdl
+cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/
+cmake --build . --config Debug
+```
+
 If you are working on Windows, add the correct generator option to
 cmake to, for instance, generate msbuild files for Visual Studio.
 
@@ -146,14 +171,29 @@
 
 Building the Qt native samples (SimpleViewer only) under Windows:
 ------------------------------------------------------------------
+
+**Visual Studio 2017 generator with static Qt build  (pwsh script)**
+
 For instance, if Qt is installed in `C:\Qt\5.12.0\msvc2017_64`
 
-`cmake -DSTATIC_BUILD=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/`
+```
+# The following will use Visual Studio 2017 to build the SDL samples
+# in debug mode (with multiple compilers in parallel). NOTE: place 
+# yourself one level above the `orthanc-stone` and `orthanc` folders
+
+if( -not (test-path stone_build_qt)) { mkdir stone_build_qt }
+cd stone_build_qt
+cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON  ../orthanc-stone/Applications/Samples/
+cmake --build . --config Debug
+```
 
 Note: replace `$($pwd)` with the current directory when not using Powershell
 
 
 
+
+
+
 Building the SDL native samples (SimpleViewer only) under Windows:
 ------------------------------------------------------------------
 `cmake -DSTATIC_BUILD=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/`
@@ -196,3 +236,9 @@
   url="https://doi.org/10.1007/s10278-018-0082-y"
 }
 
+Various notes to be deleted
+---------------------------
+class BaseCommand : public ICommand
+
+RadiographySceneCommand
+GenericNoArgCommand
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Apr 18 10:43:17 2019 +0200
@@ -32,6 +32,7 @@
 
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include_directories(${ORTHANC_ROOT})
+include_directories(${ORTHANC_ROOT}/Core/Images) # hack for the numerous #include "../Enumerations.h" in Orthanc to work
 
 
 #####################################################################
@@ -103,6 +104,8 @@
   add_definitions(-DORTHANC_ENABLE_NATIVE=0)
 endif()
 
+
+
 #####################################################################
 ## Configuration of the C/C++ macros
 #####################################################################
@@ -175,10 +178,6 @@
 set(APPLICATIONS_SOURCES
     ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h
     ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp
-    ${ORTHANC_STONE_ROOT}/Applications/Commands/BaseCommandBuilder.cpp
-    ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommand.h
-    ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandExecutor.h
-    ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandBuilder.h
     )
 
 if (NOT ORTHANC_SANDBOXED)
@@ -259,9 +258,11 @@
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerCropTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMaskTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMoveTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerResizeTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerRotateTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyMaskLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneReader.cpp
@@ -298,6 +299,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ViewportGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Viewport/IMouseTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp
@@ -311,6 +313,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanMouseTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanZoomMouseTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/SliceViewerWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestWorldSceneWidget.cpp
--- a/Resources/CMake/OrthancStoneParameters.cmake	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Thu Apr 18 10:43:17 2019 +0200
@@ -22,8 +22,6 @@
 ## Import the parameters of the Orthanc Framework
 #####################################################################
 
-message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}")
-
 include(${CMAKE_CURRENT_LIST_DIR}/../../Resources/Orthanc/DownloadOrthancFramework.cmake)
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
 
--- a/Resources/CodeGeneration/Graveyard/runts.ps1	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/Graveyard/runts.ps1	Thu Apr 18 10:43:17 2019 +0200
@@ -1,9 +1,3 @@
-# echo "+----------------------+"
-# echo "|    template.in.ts    |"
-# echo "+----------------------+"
-
-# tsc -t ES2015 .\template.in.ts; node .\template.in.js
-
 # echo "+----------------------+"
 # echo "|    playground.ts     |"
 # echo "+----------------------+"
--- a/Resources/CodeGeneration/stonegentool.py	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/stonegentool.py	Thu Apr 18 10:43:17 2019 +0200
@@ -6,6 +6,7 @@
 from jinja2 import Template
 from io import StringIO
 import time
+import datetime
 
 """
          1         2         3         4         5         6         7
@@ -38,30 +39,6 @@
     # Return a single string:
     return '\n'.join(trimmed)
 
-
-class GenCode:
-    def __init__(self):
-
-        # file-wide preamble (#include directives, comment...)
-        self.cppPreamble = StringIO()
-
-        self.cppEnums = StringIO()
-        self.cppStructs = StringIO()
-        self.cppDispatcher = StringIO()
-        self.cppHandler = StringIO()
-
-        # file-wide preamble (module directives, comment...)
-        self.tsPreamble = StringIO()
-
-        self.tsEnums = StringIO()
-        self.tsStructs = StringIO()
-        self.tsDispatcher = StringIO()
-        self.tsHandler = StringIO()
-
-    def FlattenToFiles(self, outputDir):
-        raise NotImplementedError()
-
-
 class JsonHelpers:
     """A set of utilities to perform JSON operations"""
 
@@ -105,10 +82,12 @@
   retVal = canonicalTypename
   retVal = retVal.replace("map", "std::map")
   retVal = retVal.replace("vector", "std::vector")
+  retVal = retVal.replace("set", "std::set")
   retVal = retVal.replace("string", "std::string")
   retVal = retVal.replace("int32", "int32_t")
   retVal = retVal.replace("float32", "float")
   retVal = retVal.replace("float64", "double")
+  retVal = retVal.replace("json", "Json::Value")
   return retVal
 
 def CanonToTs(canonicalTypename):
@@ -120,10 +99,12 @@
   retVal = canonicalTypename
   retVal = retVal.replace("map", "Map")
   retVal = retVal.replace("vector", "Array")
+  retVal = retVal.replace("set", "Set")
   retVal = retVal.replace("int32", "number")
   retVal = retVal.replace("float32", "number")
   retVal = retVal.replace("float64", "number")
   retVal = retVal.replace("bool", "boolean")
+  retVal = retVal.replace("json", "Object")
   return retVal
 
 def NeedsTsConstruction(enums, tsType):
@@ -231,6 +212,9 @@
         listOfDependentTypes = SplitListOfTypes(m.group(2))
         return (True, m.group(1), listOfDependentTypes)
 
+def GetStructFields(struct):
+  """This filters out the special metadata key from the struct fields"""
+  return [k for k in struct.keys() if k != '__handler']
 
 def ComputeOrderFromTypeTree(
   ancestors, 
@@ -263,13 +247,15 @@
         struct = schema[GetLongTypename(shortTypename, schema)]
         # The keys in the struct dict are the member names
         # The values in the struct dict are the member types
-        for field in struct.keys():
-          # we fill the chain of dependent types (starting here)
-          ancestors.append(shortTypename)
-          ComputeOrderFromTypeTree(
-            ancestors, genOrder, struct[field], schema)
-          # don't forget to restore it!
-          ancestors.pop()
+        if struct:
+          # we reach this if struct is not None AND not empty
+          for field in GetStructFields(struct):
+            # we fill the chain of dependent types (starting here)
+            ancestors.append(shortTypename)
+            ComputeOrderFromTypeTree(
+              ancestors, genOrder, struct[field], schema)
+            # don't forget to restore it!
+            ancestors.pop()
         
         # now we're pretty sure our dependencies have been processed,
         # we can start marking our code for generation (it might 
@@ -335,6 +321,12 @@
   # TODO: check struct fields are unique (in each struct)
   # TODO: check that in the source schema, there are spaces after each colon
 
+nonTypeKeys = ['rootName']
+def GetTypesInSchema(schema):
+  """Returns the top schema keys that are actual type names"""
+  typeList = [k for k in schema if k not in nonTypeKeys]
+  return typeList
+
 # +-----------------------+
 # | Main processing logic |
 # +-----------------------+
@@ -352,13 +344,59 @@
   # anything and we'll handle them, in their original declaration 
   # order, at the start
   genOrder = []
-  for fullName in schema.keys():
+  for fullName in GetTypesInSchema(schema):
     if IsStructType(fullName):
       realName = GetShortTypename(fullName)
       ancestors = []
       ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema)
   return genOrder
 
+def GetStructFields(fieldDict):
+  """Returns the regular (non __handler) struct fields"""
+  # the following happens for empty structs
+  if fieldDict == None:
+    return fieldDict
+  ret = {}
+  for k,v in fieldDict.items():
+    if k != "__handler":
+      ret[k] = v
+    if k.startswith("__") and k != "__handler":
+      raise RuntimeError("Fields starting with __ (double underscore) are reserved names!")
+  return ret
+
+def GetStructMetadata(fieldDict):
+  """Returns the __handler struct fields (there are default values that
+     can be overridden by entries in the schema
+     Not tested because it's a fail-safe: if something is broken in this, 
+     dependent projects will not build."""
+  metadataDict = {}
+  metadataDict['handleInCpp'] = False
+  metadataDict['handleInTypescript'] = False
+  
+  if fieldDict != None:
+    for k,v in fieldDict.items():
+      if k.startswith("__") and k != "__handler":
+        raise RuntimeError("Fields starting with __ (double underscore) are reserved names")
+      if k == "__handler":
+        if type(v) == list:
+          for i in v:
+            if i == "cpp":
+              metadataDict['handleInCpp'] = True
+            elif i == "ts":
+              metadataDict['handleInTypescript'] = True
+            else:
+              raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\"")
+        elif type(v) == str:
+          if v == "cpp":
+            metadataDict['handleInCpp'] = True
+          elif v == "ts":
+            metadataDict['handleInTypescript'] = True
+          else:
+            raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\" (or a list of both)")
+        else:
+            raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\" (or a list of both)")
+  return metadataDict
+
 def ProcessSchema(schema, genOrder):
   # sanity check
   CheckSchemaSchema(schema)
@@ -403,7 +441,8 @@
     fieldDict = schema["struct " + typename]
     struct = {}
     struct['name'] = typename
-    struct['fields'] = fieldDict
+    struct['fields'] = GetStructFields(fieldDict)
+    struct['__meta__'] = GetStructMetadata(fieldDict)
     structs.append(struct)
   
   templatingDict = {}
@@ -437,7 +476,8 @@
       nextCh = schemaText[i+1]
       if ch == ':':
         if not (nextCh == ' ' or nextCh == '\n'):
-          assert(False)
+          lineNumber = schemaText.count("\n",0,i) + 1
+          raise RuntimeError("Error at line " + str(lineNumber) + " in the schema: colons must be followed by a space or a newline!")
     schema = yaml.load(schemaText)
   return schema
 
@@ -445,11 +485,33 @@
   obj = LoadSchema(fn)
   genOrder = ComputeRequiredDeclarationOrder(obj)
   templatingDict = ProcessSchema(obj, genOrder)
+  currentDT = datetime.datetime.now()
+  templatingDict['currentDatetime'] = str(currentDT)
   return templatingDict
 
 # +-----------------------+
 # |      ENTRY POINT      |
 # +-----------------------+
+def Process(schemaFile, outDir):
+  tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
+
+  tsTemplateFile = \
+    os.path.join(os.path.dirname(__file__), 'template.in.ts.j2')
+  template = MakeTemplateFromFile(tsTemplateFile)
+  renderedTsCode = template.render(**tdico)
+  outputTsFile = os.path.join( \
+    outDir,str(tdico['rootName']) + "_generated.ts")
+  with open(outputTsFile,"wt",encoding='utf8') as outFile:
+    outFile.write(renderedTsCode)
+
+  cppTemplateFile = \
+    os.path.join(os.path.dirname(__file__), 'template.in.h.j2')
+  template = MakeTemplateFromFile(cppTemplateFile)
+  renderedCppCode = template.render(**tdico)
+  outputCppFile = os.path.join( \
+    outDir, str(tdico['rootName']) + "_generated.hpp")
+  with open(outputCppFile,"wt",encoding='utf8') as outFile:
+    outFile.write(renderedCppCode)
 
 if __name__ == "__main__":
   import argparse
@@ -483,237 +545,4 @@
   args = parser.parse_args()
   schemaFile = args.input_schema
   outDir = args.out_dir
-
-  tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
-
-  tsTemplateFile = \
-    os.path.join(os.path.dirname(__file__), 'template.in.ts')
-  template = MakeTemplateFromFile(tsTemplateFile)
-  renderedTsCode = template.render(**tdico)
-  outputTsFile = os.path.join( \
-    outDir,str(tdico['rootName']) + "_generated.ts")
-  with open(outputTsFile,"wt",encoding='utf8') as outFile:
-    outFile.write(renderedTsCode)
-
-  cppTemplateFile = \
-    os.path.join(os.path.dirname(__file__), 'template.in.h')
-  template = MakeTemplateFromFile(cppTemplateFile)
-  renderedCppCode = template.render(**tdico)
-  outputCppFile = os.path.join( \
-    outDir, str(tdico['rootName']) + "_generated.hpp")
-  with open(outputCppFile,"wt",encoding='utf8') as outFile:
-    outFile.write(renderedCppCode)
-
-# def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None:
-#   """Writes the enumerations in genc"""
-#   enumDict:Dict=schema[fullName]
-#   # jinja2 template
-#   j2cppEnum = Template(trim(
-#   """ {{fullName}}
-#       {
-#           {% for key in enumDict.keys()%}
-#           {{key}},
-#           {%endfor%}
-#       };
-#   """))
-#   j2cppEnumR = j2cppEnum.render(locals())
-#   genc.cppEnums.write(j2cppEnumR)
-
-#   j2tsEnum = Template(trim(
-#   """ export {{fullName}}
-#       {
-#           {% for key in enumDict.keys()%}
-#           {{key}},
-#           {%endfor%}
-#       };
-#   """))
-#   j2cppEnumR = j2cppEnum.render(locals())
-#   genc.tsEnums.write(j2cppEnumR)
-
-  
-
-# def GetSerializationCode(typename: str,valueName: str, tempName: str)
-#   if IsPrimitiveType(typename) or IsTemplateCollection(typename):
-#     # no need to write code for the primitive types or collections.
-#     # It is handled in C++ by the template functions and in TS by 
-#     # the JSON.stringify code.
-#   elif IsStructType(typename):
-#     pass
-
-# def GenStructTypeDeclAndSerialize(genc: GenCode, type, schema) -> None:
-#   ######
-#   # CPP
-#   ######
-#   sampleCpp = """  struct Message1
-#     {
-#       int32_t a;
-#       std::string b;
-#       EnumMonth0 c;
-#       bool d;
-#     };
-
-#     Json::Value StoneSerialize(const Message1& value)
-#     {
-#       Json::Value result(Json::objectValue);
-#       result["a"] = StoneSerialize(value.a);
-#       result["b"] = StoneSerialize(value.b);
-#       result["c"] = StoneSerialize(value.c);
-#       result["d"] = StoneSerialize(value.d);
-#       return result;
-#     }
-#     """
-    
-
-#   ######
-#   # TS
-#   ######
-#   sampleTs = """
-#       {
-#         export class Message1 {
-#           a: number;
-#           b: string;
-#           c: EnumMonth0;
-#           d: boolean;
-#           public StoneSerialize(): string {
-#             let container: object = {};
-#             container['type'] = 'Message1';
-#             container['value'] = this;
-#             return JSON.stringify(container);
-#           }
-#         };
-#       }
-#     """
-
-
-
-
-#   tsText: StringIO = StringIO()
-#   cppText: StringIO = StringIO()
-
-#   tsText.write("class %s\n" % typeDict["name"])
-#   tsText.write("{\n")
-
-#   cppText.write("struct %s\n" % typeDict["name"])
-#   cppText.write("{\n")
-
-#   """
-  
-#   GenerateSerializationCode(typename,valueName)
-
-#   primitives:
-#   -----------
-#   int
-#     jsonValue val(objectInt);
-#     val.setValue("$name")
-#     parent.add(("$name",$name)
-#   double
-#     ...
-#   string
-#     ...
-
-#   collections:
-#   -----------
-#   dict { }
-
-#   serializeValue()
-#   """
-
-#   for i in range(len(typeDict["fields"])):
-#     field = typeDict["fields"][i]
-#     name = field["name"]
-#     tsType = GetTypeScriptTypenameFromCanonical(field["type"])
-#     tsText.write("    public %s %s;\n" % (tsType, name))
-#     cppType = GetCppTypenameFromCanonical(field["type"])
-#     cppText.write("    %s %s;\n" % (cppType, name))
-
-#   tsText.write("};\n\n")
-#   cppText.write("};\n\n")
-
-#   genc.tsStructs.write(tsText.getvalue())
-#   genc.cppStructs.write(cppText.getvalue())
-
-
-# def GenerateCodeFromTsTemplate(genc)
-
-
-# +-----------------------+
-# |    CODE GENERATION    |
-# +-----------------------+
-
-# def GenPreambles(rootName: str, genc: GenCode) -> None:
-#   cppPreambleT = Template(trim(
-#     """// autogenerated by stonegentool on {{time.ctime()}} 
-#     // for module {{rootName}}
-#     #include <cstdint>
-#     #include <string>
-#     #include <vector>
-#     #include <map>
-#     namespace {{rootName}}
-#     {
-#       Json::Value StoneSerialize(int32_t value)
-#       {
-#         Json::Value result(value);
-#         return result;
-#       }
-#       Json::Value StoneSerialize(double value)
-#       {
-#         Json::Value result(value);
-#         return result;
-#       }
-#       Json::Value StoneSerialize(bool value)
-#       {
-#         Json::Value result(value);
-#         return result;
-#       }
-#       Json::Value StoneSerialize(const std::string& value)
-#       {
-#         // the following is better than 
-#         Json::Value result(value.data(),value.data()+value.size());
-#         return result;
-#       }
-#       template<typename T>
-#       Json::Value StoneSerialize(const std::map<std::string,T>& value)
-#       {
-#         Json::Value result(Json::objectValue);
-
-#         for (std::map<std::string, T>::const_iterator it = value.cbegin();
-#           it != value.cend(); ++it)
-#         {
-#           // it->first it->second
-#           result[it->first] = StoneSerialize(it->second);
-#         }
-#         return result;
-#       }
-#       template<typename T>
-#       Json::Value StoneSerialize(const std::vector<T>& value)
-#       {
-#         Json::Value result(Json::arrayValue);
-#         for (size_t i = 0; i < value.size(); ++i)
-#         {
-#           result.append(StoneSerialize(value[i]));
-#         }
-#         return result;
-#       }
-#     """
-#   cppPreambleR = cppPreambleT.render(locals())
-#   genc.cppPreamble.write(cppPreambleR)
-  
-#   tsPreambleT = Template(trim(
-#     """// autogenerated by stonegentool on {{time.ctime()}} 
-#     // for module {{rootName}}
-    
-#     namespace {{rootName}}
-#     {
-#     """
-#   tsPreambleR = tsPreambleT.render(locals())
-#   genc.tsPreamble.write(tsPreambleR)
-
-# def ComputeOrder_ProcessStruct( \
-#   genOrder: List[str], name:str, schema: Dict[str, str]) -> None:
-#   # let's generate the code according to the
-#   struct = schema[name]
-
-#   if not IsStructType(name):
-#     raise Exception(f'{typename} should start with "struct "')
-
-
+  Process(schemaFile, outDir)
--- a/Resources/CodeGeneration/stonegentool_test.py	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/stonegentool_test.py	Thu Apr 18 10:43:17 2019 +0200
@@ -379,7 +379,7 @@
       os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml')
     tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
     tsTemplateFile = \
-      os.path.join(os.path.dirname(__file__), 'template.in.ts')
+      os.path.join(os.path.dirname(__file__), 'template.in.ts.j2')
     template = MakeTemplateFromFile(tsTemplateFile)
     renderedCode = template.render(**tdico)
     print(renderedCode)
--- a/Resources/CodeGeneration/template.in.h	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,365 +0,0 @@
-/*
-         1         2         3         4         5         6         7
-12345678901234567890123456789012345678901234567890123456789012345678901234567890
-
-*/
-
-#include <iostream>
-#include <string>
-#include <sstream>
-#include <assert.h>
-#include <memory>
-#include <optional>
-#include <json/json.h>
-
-//#define STONEGEN_NO_CPP11 1
-
-#ifdef STONEGEN_NO_CPP11
-#define StoneSmartPtr std::auto_ptr
-#else 
-#define StoneSmartPtr std::unique_ptr
-#endif 
-
-namespace {{rootName}}
-{
-  /** Throws in case of problem */
-  void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue)
-  {
-    destValue = jsonValue.asInt();
-  }
-
-  Json::Value _StoneSerializeValue(int32_t value)
-  {
-    Json::Value result(value);
-    return result;
-  }
-
-  /** Throws in case of problem */
-  void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue)
-  {
-    destValue = jsonValue.asDouble();
-  }
-
-  Json::Value _StoneSerializeValue(double value)
-  {
-    Json::Value result(value);
-    return result;
-  }
-
-  /** Throws in case of problem */
-  void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue)
-  {
-    destValue = jsonValue.asBool();
-  }
-
-  Json::Value _StoneSerializeValue(bool value)
-  {
-    Json::Value result(value);
-    return result;
-  }
-
-  /** Throws in case of problem */
-  void _StoneDeserializeValue(
-       std::string& destValue
-     , const Json::Value& jsonValue)
-  {
-    destValue = jsonValue.asString();
-  }
-
-  Json::Value _StoneSerializeValue(const std::string& value)
-  {
-    // the following is better than 
-    Json::Value result(value.data(),value.data()+value.size());
-    return result;
-  }
-
-  std::string MakeIndent(int indent)
-  {
-    char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!!
-    for(size_t i = 0; i < indent; ++i)
-      txt[i] = ' ';
-    txt[indent] = 0;
-    std::string retVal(txt);
-    free(txt); // NO EXCEPTION ABOVE !!!!!!!!!!
-    return retVal;
-  }
-
-  // generic dumper
-  template<typename T>
-  std::ostream& StoneDumpValue(std::ostream& out, const T& value, int indent)
-  {
-    out << MakeIndent(indent) << value;
-    return out;
-  }
-
-  // string dumper
-  std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, int indent)
-  {
-    out << MakeIndent(indent) << "\"" << value  << "\"";
-    return out;
-  }
-
-  /** Throws in case of problem */
-  template<typename T>
-  void _StoneDeserializeValue(
-    std::map<std::string, T>& destValue, const Json::Value& jsonValue)
-  {
-    destValue.clear();
-    for (
-      Json::Value::const_iterator itr = jsonValue.begin();
-      itr != jsonValue.end();
-      itr++)
-    {
-      std::string key;
-      _StoneDeserializeValue(key, itr.key());
-
-      T innerDestValue;
-      _StoneDeserializeValue(innerDestValue, *itr);
-
-      destValue[key] = innerDestValue;
-    }
-  }
-
-  template<typename T>
-  Json::Value _StoneSerializeValue(const std::map<std::string,T>& value)
-  {
-    Json::Value result(Json::objectValue);
-
-    for (typename std::map<std::string, T>::const_iterator it = value.cbegin();
-      it != value.cend(); ++it)
-    {
-      // it->first it->second
-      result[it->first] = _StoneSerializeValue(it->second);
-    }
-    return result;
-  }
-
-  template<typename T>
-  std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, int indent)
-  {
-    out << MakeIndent(indent) << "{\n";
-    for (typename std::map<std::string, T>::const_iterator it = value.cbegin();
-      it != value.cend(); ++it)
-    {
-      out << MakeIndent(indent+2) << "\"" << it->first << "\" : ";
-      StoneDumpValue(out, it->second, indent+2);
-    }
-    out << MakeIndent(indent) << "}\n";
-    return out;
-  }
-
-  /** Throws in case of problem */
-  template<typename T>
-  void _StoneDeserializeValue(
-    std::vector<T>& destValue, const Json::Value& jsonValue)
-  {
-    destValue.clear();
-    destValue.reserve(jsonValue.size());
-    for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++)
-    {
-      T innerDestValue;
-      _StoneDeserializeValue(innerDestValue, jsonValue[i]);
-      destValue.push_back(innerDestValue);
-    }
-  }
-
-  template<typename T>
-  Json::Value _StoneSerializeValue(const std::vector<T>& value)
-  {
-    Json::Value result(Json::arrayValue);
-    for (size_t i = 0; i < value.size(); ++i)
-    {
-      result.append(_StoneSerializeValue(value[i]));
-    }
-    return result;
-  }
-
-  template<typename T>
-  std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, int indent)
-  {
-    out << MakeIndent(indent) << "[\n";
-    for (size_t i = 0; i < value.size(); ++i)
-    {
-      StoneDumpValue(out, value[i], indent+2);
-    }
-    out << MakeIndent(indent) << "]\n";
-    return out;
-  }
-
-  void StoneCheckSerializedValueTypeGeneric(const Json::Value& value)
-  {
-    if ((!value.isMember("type")) || (!value["type"].isString()))
-    {
-      std::stringstream ss;
-      ss << "Cannot deserialize value ('type' key invalid)";
-      throw std::runtime_error(ss.str());
-    }
-  }
-
-  void StoneCheckSerializedValueType(
-    const Json::Value& value, std::string typeStr)
-  {
-    StoneCheckSerializedValueTypeGeneric(value);
-
-    std::string actTypeStr = value["type"].asString();
-    if (actTypeStr != typeStr)
-    {
-      std::stringstream ss;
-      ss << "Cannot deserialize type" << actTypeStr
-        << "into " << typeStr;
-      throw std::runtime_error(ss.str());
-    }
-  }
-
-  // end of generic methods
-
-// end of generic methods
-{% for enum in enums%}
-  enum {{enum['name']}} {
-{% for key in enum['fields']%}    {{key}},
-{%endfor%}  };
-
-  void _StoneDeserializeValue(
-    {{enum['name']}}& destValue, const Json::Value& jsonValue)
-  {
-    destValue = static_cast<{{enum['name']}}>(jsonValue.asInt64());
-  }
-
-  Json::Value _StoneSerializeValue(const {{enum['name']}}& value)
-  {
-    return Json::Value(static_cast<int64_t>(value));
-  }
-
-  std::ostream& StoneDumpValue(std::ostream& out, const {{enum['name']}}& value, int indent = 0)
-  {
-{% for key in enum['fields']%}    if( value == {{key}})
-    {
-      out << MakeIndent(indent) << "{{key}}" << std::endl;
-    }
-{%endfor%}    return out;
-  }
-
-{%endfor%}
-{% for struct in structs%}
-#ifdef _MSC_VER
-#pragma region {{struct['name']}}
-#endif //_MSC_VER
-
-  struct {{struct['name']}}
-  {
-{% for key in struct['fields']%}    {{CanonToCpp(struct['fields'][key])}} {{key}};
-{% endfor %}
-    {{struct['name']}}()
-    {
-{% for key in struct['fields']%}      {{key}} = {{CanonToCpp(struct['fields'][key])}}();
-{% endfor %}
-    }
-  };
-
-  void _StoneDeserializeValue({{struct['name']}}& destValue, const Json::Value& value)
-  {
-{% for key in struct['fields']%}    _StoneDeserializeValue(destValue.{{key}}, value["{{key}}"]);
-{% endfor %}
-  }
-
-  Json::Value _StoneSerializeValue(const {{struct['name']}}& value)
-  {
-    Json::Value result(Json::objectValue);
-{% for key in struct['fields']%}    result["{{key}}"] = _StoneSerializeValue(value.{{key}});
-{% endfor %}
-    return result;
-  }
-
-  std::ostream& StoneDumpValue(std::ostream& out, const {{struct['name']}}& value, int indent = 0)
-  {
-    out << MakeIndent(indent) << "{\n";
-{% for key in struct['fields']%}    out << MakeIndent(indent) << "{{key}}:\n";
-    StoneDumpValue(out, value.{{key}},indent+2);
-    out << "\n";
-{% endfor %}
-    out << MakeIndent(indent) << "}\n";
-    return out;
-  }
-
-  void StoneDeserialize({{struct['name']}}& destValue, const Json::Value& value)
-  {
-    StoneCheckSerializedValueType(value, "{{rootName}}.{{struct['name']}}");
-    _StoneDeserializeValue(destValue, value["value"]);
-  }
-
-  Json::Value StoneSerialize(const {{struct['name']}}& value)
-  {
-    Json::Value result(Json::objectValue);
-    result["type"] = "{{rootName}}.{{struct['name']}}";
-    result["value"] = _StoneSerializeValue(value);
-    return result;
-  }
-
-#ifdef _MSC_VER
-#pragma endregion {{struct['name']}}
-#endif //_MSC_VER
-{% endfor %}
-#ifdef _MSC_VER
-#pragma region Dispatching code
-#endif //_MSC_VER
-
-  class IHandler
-  {
-  public:
-{% for struct in structs%}    virtual bool Handle(const {{struct['name']}}& value) = 0;
-{% endfor %}  };
-
-  /** Service function for StoneDispatchToHandler */
-  bool StoneDispatchJsonToHandler(
-    const Json::Value& jsonValue, IHandler* handler)
-  {
-    StoneCheckSerializedValueTypeGeneric(jsonValue);
-    std::string type = jsonValue["type"].asString();
-    if (type == "")
-    {
-      // this should never ever happen
-      throw std::runtime_error("Caught empty type while dispatching");
-    }
-{% for struct in structs%}    else if (type == "{{rootName}}.{{struct['name']}}")
-    {
-      {{struct['name']}} value;
-      _StoneDeserializeValue(value, jsonValue["value"]);
-      return handler->Handle(value);
-    }
-{% endfor %}    else
-    {
-      return false;
-    }
-  }
-
-  /** Takes a serialized type and passes this to the handler */
-  bool StoneDispatchToHandler(std::string strValue, IHandler* handler)
-  {
-    Json::Value readValue;
-
-    Json::CharReaderBuilder builder;
-    Json::CharReader* reader = builder.newCharReader();
-
-    StoneSmartPtr<Json::CharReader> ptr(reader);
-
-    std::string errors;
-
-    bool ok = reader->parse(
-      strValue.c_str(),
-      strValue.c_str() + strValue.size(),
-      &readValue,
-      &errors
-    );
-    if (!ok)
-    {
-      std::stringstream ss;
-      ss << "Jsoncpp parsing error: " << errors;
-      throw std::runtime_error(ss.str());
-    }
-    return StoneDispatchJsonToHandler(readValue, handler);
-  }
-
-#ifdef _MSC_VER
-#pragma endregion Dispatching code
-#endif //_MSC_VER
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CodeGeneration/template.in.h.j2	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,451 @@
+/*
+         1         2         3         4         5         6         7
+12345678901234567890123456789012345678901234567890123456789012345678901234567890
+
+Generated on {{currentDatetime}} by stonegentool
+
+*/
+#pragma once
+
+#include <exception>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <assert.h>
+#include <memory>
+#include <set>
+#include <json/json.h>
+
+//#define STONEGEN_NO_CPP11 1
+
+#ifdef STONEGEN_NO_CPP11
+#define StoneSmartPtr std::auto_ptr
+#else 
+#define StoneSmartPtr std::unique_ptr
+#endif 
+
+namespace {{rootName}}
+{
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asInt();
+  }
+
+  inline Json::Value _StoneSerializeValue(int32_t value)
+  {
+    Json::Value result(value);
+    return result;
+  }
+
+  inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue;
+  }
+
+  inline Json::Value _StoneSerializeValue(Json::Value value)
+  {
+    return value;
+  }
+
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asDouble();
+  }
+
+  inline Json::Value _StoneSerializeValue(double value)
+  {
+    Json::Value result(value);
+    return result;
+  }
+
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asBool();
+  }
+
+  inline Json::Value _StoneSerializeValue(bool value)
+  {
+    Json::Value result(value);
+    return result;
+  }
+
+  /** Throws in case of problem */
+  inline void _StoneDeserializeValue(
+       std::string& destValue
+     , const Json::Value& jsonValue)
+  {
+    destValue = jsonValue.asString();
+  }
+
+  inline Json::Value _StoneSerializeValue(const std::string& value)
+  {
+    // the following is better than 
+    Json::Value result(value.data(),value.data()+value.size());
+    return result;
+  }
+
+  inline std::string MakeIndent(size_t indent)
+  {
+    char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!!
+    for(size_t i = 0; i < indent; ++i)
+      txt[i] = ' ';
+    txt[indent] = 0;
+    std::string retVal(txt);
+    free(txt); // NO EXCEPTION ABOVE !!!!!!!!!!
+    return retVal;
+  }
+
+  // generic dumper
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent)
+  {
+    out << MakeIndent(indent) << value;
+    return out;
+  }
+
+  // string dumper
+  inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "\"" << value  << "\"";
+    return out;
+  }
+
+  /** Throws in case of problem */
+  template<typename T>
+  void _StoneDeserializeValue(
+    std::map<std::string, T>& destValue, const Json::Value& jsonValue)
+  {
+    destValue.clear();
+    for (
+      Json::Value::const_iterator itr = jsonValue.begin();
+      itr != jsonValue.end();
+      itr++)
+    {
+      std::string key;
+      _StoneDeserializeValue(key, itr.key());
+
+      T innerDestValue;
+      _StoneDeserializeValue(innerDestValue, *itr);
+
+      destValue[key] = innerDestValue;
+    }
+  }
+
+  template<typename T>
+  Json::Value _StoneSerializeValue(const std::map<std::string,T>& value)
+  {
+    Json::Value result(Json::objectValue);
+
+    for (typename std::map<std::string, T>::const_iterator it = value.cbegin();
+      it != value.cend(); ++it)
+    {
+      // it->first it->second
+      result[it->first] = _StoneSerializeValue(it->second);
+    }
+    return result;
+  }
+
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "{\n";
+    for (typename std::map<std::string, T>::const_iterator it = value.cbegin();
+      it != value.cend(); ++it)
+    {
+      out << MakeIndent(indent+2) << "\"" << it->first << "\" : ";
+      StoneDumpValue(out, it->second, indent+2);
+    }
+    out << MakeIndent(indent) << "}\n";
+    return out;
+  }
+
+  /** Throws in case of problem */
+  template<typename T>
+  void _StoneDeserializeValue(
+    std::vector<T>& destValue, const Json::Value& jsonValue)
+  {
+    destValue.clear();
+    destValue.reserve(jsonValue.size());
+    for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++)
+    {
+      T innerDestValue;
+      _StoneDeserializeValue(innerDestValue, jsonValue[i]);
+      destValue.push_back(innerDestValue);
+    }
+  }
+
+  template<typename T>
+  Json::Value _StoneSerializeValue(const std::vector<T>& value)
+  {
+    Json::Value result(Json::arrayValue);
+    for (size_t i = 0; i < value.size(); ++i)
+    {
+      result.append(_StoneSerializeValue(value[i]));
+    }
+    return result;
+  }
+
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "[\n";
+    for (size_t i = 0; i < value.size(); ++i)
+    {
+      StoneDumpValue(out, value[i], indent+2);
+    }
+    out << MakeIndent(indent) << "]\n";
+    return out;
+  }
+
+  /** Throws in case of problem */
+  template<typename T>
+  void _StoneDeserializeValue(
+    std::set<T>& destValue, const Json::Value& jsonValue)
+  {
+    destValue.clear();
+    for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++)
+    {
+      T innerDestValue;
+      _StoneDeserializeValue(innerDestValue, jsonValue[i]);
+      destValue.insert(innerDestValue);
+    }
+  }
+
+  template<typename T>
+  Json::Value _StoneSerializeValue(const std::set<T>& value)
+  {
+    Json::Value result(Json::arrayValue);
+    for (typename std::set<T>::const_iterator it = value.begin(); it != value.end(); ++it)
+    {
+      result.append(_StoneSerializeValue(*it));
+    }
+    return result;
+  }
+
+  template<typename T>
+  std::ostream& StoneDumpValue(std::ostream& out, const std::set<T>& value, size_t indent)
+  {
+    out << MakeIndent(indent) << "[\n";
+    for (typename std::set<T>::const_iterator it = value.begin(); it != value.end(); ++it)
+    {
+      StoneDumpValue(out, *it, indent+2);
+    }
+    out << MakeIndent(indent) << "]\n";
+    return out;
+  }
+
+  inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value)
+  {
+    if ((!value.isMember("type")) || (!value["type"].isString()))
+    {
+      std::stringstream ss;
+      ss << "Cannot deserialize value ('type' key invalid)";
+      throw std::runtime_error(ss.str());
+    }
+  }
+
+  inline void StoneCheckSerializedValueType(
+    const Json::Value& value, std::string typeStr)
+  {
+    StoneCheckSerializedValueTypeGeneric(value);
+
+    std::string actTypeStr = value["type"].asString();
+    if (actTypeStr != typeStr)
+    {
+      std::stringstream ss;
+      ss << "Cannot deserialize type" << actTypeStr
+        << "into " << typeStr;
+      throw std::runtime_error(ss.str());
+    }
+  }
+
+  // end of generic methods
+
+// end of generic methods
+{% for enum in enums%}
+  enum {{enum['name']}} {
+{% for key in enum['fields']%}    {{enum['name']}}_{{key}},
+{%endfor%}  };
+
+  inline std::string ToString(const {{enum['name']}}& value)
+  {
+{% for key in enum['fields']%}    if( value == {{enum['name']}}_{{key}})
+    {
+      return std::string("{{key}}");
+    }
+{%endfor%}    std::stringstream ss;
+    ss << "Value \"" << value << "\" cannot be converted to {{enum['name']}}. Possible values are: "
+{% for key in enum['fields']%}        << " {{key}} = " << static_cast<int64_t>({{enum['name']}}_{{key}})  << ", " 
+{% endfor %}        << std::endl;
+    std::string msg = ss.str();
+    throw std::runtime_error(msg);
+  }
+
+  inline void FromString({{enum['name']}}& value, std::string strValue)
+  {
+{% for key in enum['fields']%}    if( strValue == std::string("{{key}}") )
+    {
+      value = {{enum['name']}}_{{key}};
+      return;
+    }
+{%endfor%}
+    std::stringstream ss;
+    ss << "String \"" << strValue << "\" cannot be converted to {{enum['name']}}. Possible values are: {% for key in enum['fields']%}{{key}} {% endfor %}";
+    std::string msg = ss.str();
+    throw std::runtime_error(msg);
+  }
+
+
+  inline void _StoneDeserializeValue(
+    {{enum['name']}}& destValue, const Json::Value& jsonValue)
+  {
+    FromString(destValue, jsonValue.asString());
+  }
+
+  inline Json::Value _StoneSerializeValue(const {{enum['name']}}& value)
+  {
+    std::string strValue = ToString(value);
+    return Json::Value(strValue);
+  }
+
+  inline std::ostream& StoneDumpValue(std::ostream& out, const {{enum['name']}}& value, size_t indent = 0)
+  {
+{% for key in enum['fields']%}    if( value == {{enum['name']}}_{{key}})
+    {
+      out << MakeIndent(indent) << "{{key}}" << std::endl;
+    }
+{%endfor%}    return out;
+  }
+
+{%endfor%}
+{% for struct in structs%}
+#ifdef _MSC_VER
+#pragma region {{struct['name']}}
+#endif //_MSC_VER
+
+  struct {{struct['name']}}
+  {
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}    {{CanonToCpp(struct['fields'][key])}} {{key}};
+{% endfor %}{% endif %}{% endif %}
+    {{struct['name']}}({% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{{CanonToCpp(struct['fields'][key])}} {{key}} = {{CanonToCpp(struct['fields'][key])}}(){{ ", " if not loop.last }}{% endfor %}{% endif %}{% endif %})
+    {
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}      this->{{key}} = {{key}};
+{% endfor %}{% endif %}{% endif %}    }
+  };
+
+  inline void _StoneDeserializeValue({{struct['name']}}& destValue, const Json::Value& value)
+  {
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}    _StoneDeserializeValue(destValue.{{key}}, value["{{key}}"]);
+{% endfor %}{% endif %}{% endif %}    }
+
+  inline Json::Value _StoneSerializeValue(const {{struct['name']}}& value)
+  {
+    Json::Value result(Json::objectValue);
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}    result["{{key}}"] = _StoneSerializeValue(value.{{key}});
+{% endfor %}{% endif %}{% endif %}
+    return result;
+  }
+
+  inline std::ostream& StoneDumpValue(std::ostream& out, const {{struct['name']}}& value, size_t indent = 0)
+  {
+    out << MakeIndent(indent) << "{\n";
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}    out << MakeIndent(indent) << "{{key}}:\n";
+    StoneDumpValue(out, value.{{key}},indent+2);
+    out << "\n";
+{% endfor %}{% endif %}{% endif %}
+    out << MakeIndent(indent) << "}\n";
+    return out;
+  }
+
+  inline void StoneDeserialize({{struct['name']}}& destValue, const Json::Value& value)
+  {
+    StoneCheckSerializedValueType(value, "{{rootName}}.{{struct['name']}}");
+    _StoneDeserializeValue(destValue, value["value"]);
+  }
+
+  inline Json::Value StoneSerializeToJson(const {{struct['name']}}& value)
+  {
+    Json::Value result(Json::objectValue);
+    result["type"] = "{{rootName}}.{{struct['name']}}";
+    result["value"] = _StoneSerializeValue(value);
+    return result;
+  }
+
+  inline std::string StoneSerialize(const {{struct['name']}}& value)
+  {
+    Json::Value resultJson = StoneSerializeToJson(value);
+    std::string resultStr = resultJson.toStyledString();
+    return resultStr;
+  }
+
+#ifdef _MSC_VER
+#pragma endregion {{struct['name']}}
+#endif //_MSC_VER
+{% endfor %}
+#ifdef _MSC_VER
+#pragma region Dispatching code
+#endif //_MSC_VER
+
+  class IHandler
+  {
+  public:
+{% for struct in structs%}{% if struct['__meta__'].handleInCpp %}    virtual bool Handle(const {{struct['name']}}& value) = 0;
+{% endif %}{% endfor %}  };
+
+  /** Service function for StoneDispatchToHandler */
+  inline bool StoneDispatchJsonToHandler(
+    const Json::Value& jsonValue, IHandler* handler)
+  {
+    StoneCheckSerializedValueTypeGeneric(jsonValue);
+    std::string type = jsonValue["type"].asString();
+    if (type == "")
+    {
+      // this should never ever happen
+      throw std::runtime_error("Caught empty type while dispatching");
+    }
+{% for struct in structs%}{% if struct['__meta__'].handleInCpp %}    else if (type == "{{rootName}}.{{struct['name']}}")
+    {
+      {{struct['name']}} value;
+      _StoneDeserializeValue(value, jsonValue["value"]);
+      return handler->Handle(value);
+    }
+{% endif %}{% endfor %}    else
+    {
+      return false;
+    }
+  }
+
+  /** Takes a serialized type and passes this to the handler */
+  inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler)
+  {
+    Json::Value readValue;
+
+    Json::CharReaderBuilder builder;
+    Json::CharReader* reader = builder.newCharReader();
+
+    StoneSmartPtr<Json::CharReader> ptr(reader);
+
+    std::string errors;
+
+    bool ok = reader->parse(
+      strValue.c_str(),
+      strValue.c_str() + strValue.size(),
+      &readValue,
+      &errors
+    );
+    if (!ok)
+    {
+      std::stringstream ss;
+      ss << "Jsoncpp parsing error: " << errors;
+      throw std::runtime_error(ss.str());
+    }
+    return StoneDispatchJsonToHandler(readValue, handler);
+  }
+
+#ifdef _MSC_VER
+#pragma endregion Dispatching code
+#endif //_MSC_VER
+}
--- a/Resources/CodeGeneration/template.in.ts	Thu Apr 18 10:42:01 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/*
-         1         2         3         4         5         6         7
-12345678901234567890123456789012345678901234567890123456789012345678901234567890
-*/
-
-function StoneCheckSerializedValueType(value: any, typeStr: string)
-{
-  StoneCheckSerializedValueTypeGeneric(value);
-
-  if (value['type'] != typeStr)
-  {
-    throw new Error(
-      `Cannot deserialize type ${value['type']} into ${typeStr}`);
-  }
-}
-
-function isString(val: any) :boolean
-{
-  return ((typeof val === 'string') || (val instanceof String));
-}
-
-function StoneCheckSerializedValueTypeGeneric(value: any)
-{
-  // console.//log("+-------------------------------------------------+");
-  // console.//log("|            StoneCheckSerializedValueTypeGeneric |");
-  // console.//log("+-------------------------------------------------+");
-  // console.//log("value = ");
-  // console.//log(value);
-  if ( (!('type' in value)) || (!isString(value.type)) )
-  {
-    throw new Error(
-      "Cannot deserialize value ('type' key invalid)");
-  }
-}
-
-// end of generic methods
-{% for enum in enums%}
-export enum {{enum['name']}} {
-  {% for key in enum['fields']%}{{key}},
-  {%endfor%}
-};
-{%endfor%}
-
-{% for struct in structs%}  export class {{struct['name']}} {
-{% for key in struct['fields']%}    {{key}}:{{CanonToTs(struct['fields'][key])}};
-{% endfor %}
-  constructor() {
-{% for key in struct['fields']%}{% if NeedsTsConstruction(enums,CanonToTs(struct['fields'][key])) %}      this.{{key}} = new {{CanonToTs(struct['fields'][key])}}();
-{% endif %}{% endfor %}    }
-
-  public StoneSerialize(): string {
-    let container: object = {};
-    container['type'] = '{{rootName}}.{{struct['name']}}';
-    container['value'] = this;
-    return JSON.stringify(container);
-  }
-
-  public static StoneDeserialize(valueStr: string) : {{struct['name']}}
-  {
-    let value: any = JSON.parse(valueStr);
-    StoneCheckSerializedValueType(value, '{{rootName}}.{{struct['name']}}');
-    let result: {{struct['name']}} = value['value'] as {{struct['name']}};
-    return result;
-  }
-}
-
-{% endfor %}
-
-export interface IHandler {
-  {% for struct in structs%}    Handle{{struct['name']}}(value:  {{struct['name']}}): boolean;
-  {% endfor %}
-};
-
-/** Service function for StoneDispatchToHandler */
-export function StoneDispatchJsonToHandler(
-  jsonValue: any, handler: IHandler): boolean
-{
-  StoneCheckSerializedValueTypeGeneric(jsonValue);
-  let type: string = jsonValue["type"];
-  if (type == "")
-  {
-    // this should never ever happen
-    throw new Error("Caught empty type while dispatching");
-  }
-{% for struct in structs%}    else if (type == "{{rootName}}.{{struct['name']}}")
-  {
-    let value = jsonValue["value"] as {{struct['name']}};
-    return handler.Handle{{struct['name']}}(value);
-  }
-{% endfor %}
-  else
-  {
-    return false;
-  }
-}
-
-/** Takes a serialized type and passes this to the handler */
-export function StoneDispatchToHandler(
-  strValue: string, handler: IHandler): boolean
-{
-  // console.//log("+------------------------------------------------+");
-  // console.//log("|            StoneDispatchToHandler              |");
-  // console.//log("+------------------------------------------------+");
-  // console.//log("strValue = ");
-  // console.//log(strValue);
-  let jsonValue: any = JSON.parse(strValue)
-  return StoneDispatchJsonToHandler(jsonValue, handler);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CodeGeneration/template.in.ts.j2	Thu Apr 18 10:43:17 2019 +0200
@@ -0,0 +1,134 @@
+/*
+         1         2         3         4         5         6         7
+12345678901234567890123456789012345678901234567890123456789012345678901234567890
+
+Generated on {{currentDatetime}} by stonegentool
+
+*/
+
+function StoneCheckSerializedValueType(value: any, typeStr: string)
+{
+  StoneCheckSerializedValueTypeGeneric(value);
+
+  if (value['type'] != typeStr)
+  {
+    throw new Error(
+      `Cannot deserialize type ${value['type']} into ${typeStr}`);
+  }
+}
+
+function isString(val: any) :boolean
+{
+  return ((typeof val === 'string') || (val instanceof String));
+}
+
+function StoneCheckSerializedValueTypeGeneric(value: any)
+{
+  // console.//log("+-------------------------------------------------+");
+  // console.//log("|            StoneCheckSerializedValueTypeGeneric |");
+  // console.//log("+-------------------------------------------------+");
+  // console.//log("value = ");
+  // console.//log(value);
+  if ( (!('type' in value)) || (!isString(value.type)) )
+  {
+    throw new Error(
+      "Cannot deserialize value ('type' key invalid)");
+  }
+}
+
+// end of generic methods
+{% for enum in enums%}
+export enum {{enum['name']}} {
+{% for key in enum['fields']%}  {{key}} = "{{key}}"{% if not loop.last %},{%endif%}
+{%endfor%}};
+
+export function {{enum['name']}}_FromString(strValue:string) : {{enum['name']}}
+{
+{% for key in enum['fields'] %}  if( strValue == "{{key}}" )
+  {
+    return {{enum['name']}}.{{key}};
+  }
+{%endfor%}
+  let msg : string =  `String ${strValue} cannot be converted to {{enum['name']}}. Possible values are: {% for key in enum['fields']%}{{key}}{% if not loop.last %}, {%endif%}{% endfor %}`;
+  throw new Error(msg);
+}
+
+export function {{enum['name']}}_ToString(value:{{enum['name']}}) : string
+{
+{% for key in enum['fields'] %}  if( value == {{enum['name']}}.{{key}} )
+  {
+    return "{{key}}";
+  }
+{%endfor%}
+  let msg : string = `Value ${value} cannot be converted to {{enum['name']}}. Possible values are: `;
+{% for key in enum['fields']%}  {
+    let _{{key}}_enumValue : string = {{enum['name']}}.{{key}}; // enums are strings in stonecodegen, so this will work.
+    let msg_{{key}} : string = `{{key}} (${_{{key}}_enumValue}){% if not loop.last %}, {%endif%}`;
+    msg = msg + msg_{{key}};
+  }
+{%endfor%}  throw new Error(msg);
+}
+{%endfor%}
+
+
+{% for struct in structs%}export class {{struct['name']}} {
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}  {{key}}:{{CanonToTs(struct['fields'][key])}};
+{% endfor %}{% endif %}{% endif %}
+  constructor() {
+{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{% if NeedsTsConstruction(enums,CanonToTs(struct['fields'][key])) %}    this.{{key}} = new {{CanonToTs(struct['fields'][key])}}();
+{% endif %}{% endfor %}{% endif %}{% endif %}  }
+
+  public StoneSerialize(): string {
+    let container: object = {};
+    container['type'] = '{{rootName}}.{{struct['name']}}';
+    container['value'] = this;
+    return JSON.stringify(container);
+  }
+
+  public static StoneDeserialize(valueStr: string) : {{struct['name']}}
+  {
+    let value: any = JSON.parse(valueStr);
+    StoneCheckSerializedValueType(value, '{{rootName}}.{{struct['name']}}');
+    let result: {{struct['name']}} = value['value'] as {{struct['name']}};
+    return result;
+  }
+}
+{% endfor %}
+export interface IHandler {
+{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %}  Handle{{struct['name']}}(value:  {{struct['name']}}): boolean;
+{% endif %}{% endfor %}};
+
+/** Service function for StoneDispatchToHandler */
+export function StoneDispatchJsonToHandler(
+  jsonValue: any, handler: IHandler): boolean
+{
+  StoneCheckSerializedValueTypeGeneric(jsonValue);
+  let type: string = jsonValue["type"];
+  if (type == "")
+  {
+    // this should never ever happen
+    throw new Error("Caught empty type while dispatching");
+  }
+{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %}  else if (type == "{{rootName}}.{{struct['name']}}")
+  {
+    let value = jsonValue["value"] as {{struct['name']}};
+    return handler.Handle{{struct['name']}}(value);
+  }
+{% endif %}{% endfor %}  else
+  {
+    return false;
+  }
+}
+
+/** Takes a serialized type and passes this to the handler */
+export function StoneDispatchToHandler(
+  strValue: string, handler: IHandler): boolean
+{
+  // console.//log("+------------------------------------------------+");
+  // console.//log("|            StoneDispatchToHandler              |");
+  // console.//log("+------------------------------------------------+");
+  // console.//log("strValue = ");
+  // console.//log(strValue);
+  let jsonValue: any = JSON.parse(strValue)
+  return StoneDispatchJsonToHandler(jsonValue, handler);
+}
--- a/Resources/CodeGeneration/testCppHandler/CMakeLists.txt	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/testCppHandler/CMakeLists.txt	Thu Apr 18 10:43:17 2019 +0200
@@ -4,7 +4,7 @@
 
 set(testCppHandler_Codegen_Deps
   ${CMAKE_CURRENT_LIST_DIR}/../test_data/test1.yaml 
-  ${CMAKE_CURRENT_LIST_DIR}/../template.in.h
+  ${CMAKE_CURRENT_LIST_DIR}/../template.in.h.j2
 ) 
 
 add_custom_command(
--- a/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt	Thu Apr 18 10:43:17 2019 +0200
@@ -14,8 +14,8 @@
 
 set(testWasmIntegratedCpp_Codegen_Deps
   ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml 
-  ${CMAKE_CURRENT_LIST_DIR}/../template.in.h
-  ${CMAKE_CURRENT_LIST_DIR}/../template.in.ts
+  ${CMAKE_CURRENT_LIST_DIR}/../template.in.h.j2
+  ${CMAKE_CURRENT_LIST_DIR}/../template.in.ts.j2
 ) 
 
 set(jsoncppRootDir ${CMAKE_CURRENT_LIST_DIR}/jsoncpp-1.8.4)
@@ -23,7 +23,7 @@
 add_custom_command(
     OUTPUT  ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.ts
     COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/../stonegentool.py -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml
-    DEPENDS ${testCppHandler_Codegen_Deps}
+    DEPENDS ${testCppHandler_Codegen_Deps} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml 
 )
 
 add_executable(testWasmIntegratedCpp
--- a/Resources/CodeGeneration/testWasmIntegrated/main.cpp	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/testWasmIntegrated/main.cpp	Thu Apr 18 10:43:17 2019 +0200
@@ -8,6 +8,45 @@
 int main()
 {
     std::cout << "Hello world from testWasmIntegrated! (this is sent from C++)" << std::endl;
+    try
+    {
+    const char* jsonData = R"bgo({"definition":
+    {
+      "val" : [ "berk", 42 ],
+      "zozo" : { "23": "zloutch", "lalala": 42}
+    }
+    })bgo";
+    std::string strValue(jsonData);
+    
+    Json::Value readValue;
+
+    Json::CharReaderBuilder builder;
+    Json::CharReader* reader = builder.newCharReader();
+
+    StoneSmartPtr<Json::CharReader> ptr(reader);
+
+    std::string errors;
+
+    bool ok = reader->parse(
+      strValue.c_str(),
+      strValue.c_str() + strValue.size(),
+      &readValue,
+      &errors
+    );
+    if (!ok)
+    {
+      std::stringstream ss;
+      ss << "Jsoncpp parsing error: " << errors;
+      throw std::runtime_error(ss.str());
+    }
+    std::cout << "Json parsing OK" << std::endl;
+    std::cout << readValue  << std::endl;
+    }
+    catch(std::exception& e)
+    {
+      std::cout << "Json parsing THROW" << std::endl;
+      std::cout << "e.what() = " << e.what() << std::endl;
+    }
 }
 
 extern "C" void SendMessageFromCppJS(const char* message);
--- a/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts	Thu Apr 18 10:43:17 2019 +0200
@@ -57,6 +57,7 @@
 struct C:
   someBs: vector<B>
   ddd:    vector<string>
+  definition: vector<json>
 
 struct A:
   someStrings: vector<string>
@@ -76,6 +77,7 @@
   titi: map<string, string>
   lulu: map<string, Message1>
   movieType: MovieType
+  definition: json
 
 enum MovieType:
   - RomCom
@@ -106,14 +108,14 @@
       {
         "a" : 43,
         "b" : "Sandrine",
-        "c" : 2,
+        "c" : "March",
         "d" : true
       },
       "55" : 
       {
         "a" : 42,
         "b" : "Benjamin",
-        "c" : 0,
+        "c" : "January",
         "d" : false
       }
     },
@@ -122,13 +124,13 @@
       {
         "a" : 42,
         "b" : "Benjamin",
-        "c" : 0,
+        "c" : "March",
         "d" : false
       },
       {
         "a" : 43,
         "b" : "Sandrine",
-        "c" : 2,
+        "c" : "January",
         "d" : false
       }
     ],
@@ -142,7 +144,16 @@
     [
       "Mercadet",
       "Poisson"
-    ]
+    ],
+    "definition":
+    {
+      "val" : [ "berk", 42 ],
+      "zozo" :
+      {
+        "23": "zloutch",
+        "lalala": 42
+      }
+    }
   }
 }`;
 stockSerializedMessages["Test 2"] = ` {
@@ -224,8 +235,8 @@
 
 // this method is called "from the C++ code" when the StoneApplication is updated.
 // it can be used to update the UI of the application
-function UpdateWebApplication(statusUpdateMessageString: string) {
-  console.log("updating web application: ", statusUpdateMessageString);
+function UpdateWebApplicationWithString(statusUpdateMessageString: string) {
+  console.log("updating web application (string): ", statusUpdateMessageString);
   let statusUpdateMessage = JSON.parse(statusUpdateMessageString);
 
   if ("event" in statusUpdateMessage)
@@ -237,3 +248,10 @@
     }
   }
 }
+
+
+function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) {
+  console.log("updating web application (serialized message): ", statusUpdateMessageString);
+  console.log("<not supported!>");
+}
+ 
\ No newline at end of file
--- a/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml	Thu Apr 18 10:42:01 2019 +0200
+++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml	Thu Apr 18 10:43:17 2019 +0200
@@ -11,6 +11,7 @@
 struct C:
   someBs: vector<B>
   ddd:    vector<string>
+  definition: vector<json>
 
 struct A:
   someStrings: vector<string>
@@ -30,6 +31,7 @@
   titi: map<string, string>
   lulu: map<string, Message1>
   movieType: MovieType
+  definition: json
 
 enum MovieType:
   - RomCom
@@ -40,10 +42,17 @@
 enum CrispType:
   - SaltAndPepper
   - CreamAndChives
+  - Barbecue
   - Paprika
-  - Barbecue
 
 enum EnumMonth0:
   - January
   - February
   - March
+
+enum Tata:
+  - Lolo
+  - Rrrrrrrrrrrr
+
+
+