[med-svn] [orthanc-wsi] 02/03: New upstream version 0.2+dfsg

Sebastien Jodogne jodogne-guest at moszumanska.debian.org
Mon Nov 28 17:42:44 UTC 2016


This is an automated email from the git hooks/post-receive script.

jodogne-guest pushed a commit to branch master
in repository orthanc-wsi.

commit 3c6e241ea544677ae14a187e83474069df14ef44
Author: jodogne-guest <s.jodogne at gmail.com>
Date:   Mon Nov 28 18:38:50 2016 +0100

    New upstream version 0.2+dfsg
---
 .hg_archival.txt                                   |   6 +-
 Applications/ApplicationToolbox.cpp                |  10 +-
 Applications/ApplicationToolbox.h                  |   2 +-
 Applications/CMakeLists.txt                        |  57 +-
 Applications/DicomToTiff.cpp                       | 136 +++--
 Applications/Dicomizer.cpp                         |  44 +-
 Framework/Algorithms/PyramidReader.cpp             |  50 +-
 Framework/Algorithms/PyramidReader.h               |  11 +-
 Framework/Algorithms/ReconstructPyramidCommand.cpp |  11 +-
 Framework/Algorithms/ReconstructPyramidCommand.h   |   2 +-
 Framework/Algorithms/TranscodeTileCommand.cpp      |  10 +-
 Framework/Algorithms/TranscodeTileCommand.h        |   2 +-
 Framework/DicomToolbox.cpp                         |  95 +---
 Framework/DicomToolbox.h                           |  17 -
 Framework/DicomizerParameters.cpp                  |   6 +-
 Framework/DicomizerParameters.h                    |   4 +-
 Framework/Enumerations.cpp                         |   7 +-
 Framework/Enumerations.h                           |   2 +-
 Framework/ImageToolbox.cpp                         |  14 +-
 Framework/ImageToolbox.h                           |   2 +-
 Framework/ImagedVolumeParameters.cpp               |   2 +-
 Framework/Inputs/DecodedTiledPyramid.h             |   6 +-
 Framework/Inputs/DicomPyramid.cpp                  |  45 +-
 Framework/Inputs/DicomPyramid.h                    |  19 +-
 Framework/Inputs/DicomPyramidInstance.cpp          | 251 +++++++--
 Framework/Inputs/DicomPyramidInstance.h            |  20 +-
 Framework/Inputs/DicomPyramidLevel.cpp             |  64 ++-
 Framework/Inputs/DicomPyramidLevel.h               |  47 +-
 Framework/Inputs/HierarchicalTiff.cpp              |   7 +-
 Framework/Inputs/HierarchicalTiff.h                |   9 +-
 Framework/Inputs/ITiledPyramid.h                   |   6 +-
 Framework/Inputs/OpenSlideLibrary.cpp              |   4 +-
 Framework/Inputs/OpenSlideLibrary.h                |   4 +-
 Framework/Inputs/OpenSlidePyramid.cpp              |   6 +-
 Framework/Inputs/PyramidWithRawTiles.cpp           |  14 +-
 Framework/Inputs/SingleLevelDecodedPyramid.cpp     |   2 +-
 Framework/Inputs/TiledJpegImage.h                  |   2 +-
 Framework/Inputs/TiledPngImage.h                   |   2 +-
 Framework/Inputs/TiledPyramidStatistics.cpp        |   5 +-
 Framework/Inputs/TiledPyramidStatistics.h          |   8 +-
 Framework/Jpeg2000Reader.cpp                       |   6 +-
 Framework/Jpeg2000Reader.h                         |   2 +-
 Framework/Jpeg2000Writer.cpp                       |   4 +-
 Framework/Jpeg2000Writer.h                         |   2 +-
 Framework/Messaging/CurlOrthancConnection.cpp      |  59 ---
 Framework/Messaging/CurlOrthancConnection.h        |  53 --
 Framework/Messaging/IOrthancConnection.cpp         |  63 ---
 Framework/Messaging/IOrthancConnection.h           |  52 --
 Framework/Messaging/OrthancConnectionBase.cpp      |  41 --
 Framework/Messaging/OrthancConnectionBase.h        |  51 --
 Framework/Messaging/PluginOrthancConnection.cpp    | 133 -----
 Framework/Messaging/PluginOrthancConnection.h      |  50 --
 Framework/Orthanc/Core/Uuid.cpp                    | 162 ------
 Framework/Outputs/DicomPyramidWriter.cpp           |   6 +-
 Framework/Outputs/DicomPyramidWriter.h             |   2 +-
 Framework/Outputs/HierarchicalTiffWriter.cpp       |  16 +-
 Framework/Outputs/IPyramidWriter.h                 |   2 +-
 Framework/Outputs/InMemoryTiledImage.cpp           |   5 +-
 Framework/Outputs/InMemoryTiledImage.h             |   6 +-
 Framework/Outputs/MultiframeDicomWriter.cpp        |   4 +-
 Framework/Outputs/MultiframeDicomWriter.h          |   2 +-
 Framework/Outputs/PyramidWriterBase.cpp            |   4 +-
 Framework/Outputs/TruncatedPyramidWriter.cpp       |   2 +-
 Framework/PrecompiledHeadersWSI.h                  |   5 +-
 Framework/{Messaging => Targets}/FolderTarget.cpp  |   6 +-
 Framework/{Messaging => Targets}/FolderTarget.h    |   0
 Framework/{Messaging => Targets}/IFileTarget.h     |   0
 Framework/{Messaging => Targets}/OrthancTarget.cpp |  30 +-
 Framework/{Messaging => Targets}/OrthancTarget.h   |  10 +-
 NEWS                                               |  10 +
 Resources/CMake/LibTiffConfiguration.cmake         |  21 +-
 Resources/CMake/Version.cmake                      |   2 +-
 .../Graveyard}/CMakeLists.txt                      | 137 ++---
 Resources/Graveyard/Hello.cpp                      |  85 +++
 .../Orthanc/Core/Cache/LeastRecentlyUsedIndex.h    | 346 ++++++++++++
 .../Orthanc/Core/ChunkedBuffer.cpp                 |   0
 .../Orthanc/Core/ChunkedBuffer.h                   |   0
 .../Orthanc/Core/DicomFormat/DicomArray.cpp        |   0
 .../Orthanc/Core/DicomFormat/DicomArray.h          |   0
 .../Orthanc/Core/DicomFormat/DicomElement.h        |   0
 .../Orthanc/Core/DicomFormat/DicomMap.cpp          |   0
 .../Orthanc/Core/DicomFormat/DicomMap.h            |   0
 .../Orthanc/Core/DicomFormat/DicomTag.cpp          |   0
 .../Orthanc/Core/DicomFormat/DicomTag.h            |   0
 .../Orthanc/Core/DicomFormat/DicomValue.cpp        |   2 +-
 .../Orthanc/Core/DicomFormat/DicomValue.h          |   7 +-
 {Framework => Resources}/Orthanc/Core/Endianness.h |   0
 .../Orthanc/Core/EnumerationDictionary.h           |   0
 .../Orthanc/Core/Enumerations.cpp                  |   5 +-
 .../Orthanc/Core/Enumerations.h                    |   3 +-
 .../Orthanc/Core/HttpClient.cpp                    |  29 +-
 {Framework => Resources}/Orthanc/Core/HttpClient.h |   9 +
 {Framework => Resources}/Orthanc/Core/ICommand.h   |   0
 .../Orthanc/Core/IDynamicObject.h                  |   0
 .../Orthanc/Core/Images/IImageWriter.cpp           |  13 +-
 .../Orthanc/Core/Images/IImageWriter.h             |   8 +
 .../Orthanc/Core/Images/Image.cpp                  |   0
 .../Orthanc/Core/Images/Image.h                    |   0
 .../Orthanc/Core/Images/ImageAccessor.cpp          |   0
 .../Orthanc/Core/Images/ImageAccessor.h            |   0
 .../Orthanc/Core/Images/ImageBuffer.cpp            |   0
 .../Orthanc/Core/Images/ImageBuffer.h              |   0
 .../Orthanc/Core/Images/ImageProcessing.cpp        |   0
 .../Orthanc/Core/Images/ImageProcessing.h          |   0
 .../Orthanc/Core/Images/JpegErrorManager.cpp       |   0
 .../Orthanc/Core/Images/JpegErrorManager.h         |   0
 .../Orthanc/Core/Images/JpegReader.cpp             |  10 +-
 .../Orthanc/Core/Images/JpegReader.h               |   6 +
 .../Orthanc/Core/Images/JpegWriter.cpp             |  10 +-
 .../Orthanc/Core/Images/JpegWriter.h               |   2 +
 .../Orthanc/Core/Images/PngReader.cpp              |  11 +-
 .../Orthanc/Core/Images/PngReader.h                |   6 +
 .../Orthanc/Core/Images/PngWriter.cpp              |   9 +-
 .../Orthanc/Core/Images/PngWriter.h                |   2 +
 {Framework => Resources}/Orthanc/Core/Logging.cpp  | 197 +++----
 {Framework => Resources}/Orthanc/Core/Logging.h    |  10 +
 .../Orthanc/Core/MultiThreading/BagOfTasks.h       |   0
 .../Core/MultiThreading/BagOfTasksProcessor.cpp    |   0
 .../Core/MultiThreading/BagOfTasksProcessor.h      |   0
 .../Orthanc/Core/MultiThreading/Semaphore.cpp      |   0
 .../Orthanc/Core/MultiThreading/Semaphore.h        |   0
 .../Core/MultiThreading/SharedMessageQueue.cpp     |   0
 .../Core/MultiThreading/SharedMessageQueue.h       |   0
 .../Orthanc/Core/OrthancException.h                |   0
 .../Orthanc/Core/PrecompiledHeaders.cpp            |   0
 .../Orthanc/Core/PrecompiledHeaders.h              |   3 +-
 Resources/Orthanc/Core/SystemToolbox.cpp           | 539 +++++++++++++++++++
 .../Orthanc/Core/SystemToolbox.h                   |  98 ++--
 .../Orthanc/Core/TemporaryFile.cpp                 |  82 +--
 .../Orthanc/Core/TemporaryFile.h                   |  39 +-
 {Framework => Resources}/Orthanc/Core/Toolbox.cpp  | 589 +++------------------
 {Framework => Resources}/Orthanc/Core/Toolbox.h    | 113 ++--
 .../Orthanc/Core/WebServiceParameters.cpp          |  12 +-
 .../Orthanc/Core/WebServiceParameters.h            |   6 +
 .../Orthanc/OrthancServer/FromDcmtkBridge.cpp      | 271 +++++++---
 .../Orthanc/OrthancServer/FromDcmtkBridge.h        |  90 +++-
 .../OrthancServer/PrecompiledHeadersServer.h       |   0
 .../Orthanc/OrthancServer/ServerEnumerations.cpp   |   0
 .../Orthanc/OrthancServer/ServerEnumerations.h     |   0
 .../Orthanc/OrthancServer/ToDcmtkBridge.cpp        |   0
 .../Orthanc/OrthancServer/ToDcmtkBridge.h          |   0
 .../Orthanc/Plugins/Engine/SharedLibrary.cpp       |   2 +-
 .../Orthanc/Plugins/Engine/SharedLibrary.h         |   2 +-
 .../Plugins/Samples/Common/DicomDatasetReader.cpp  | 122 +++++
 .../Plugins/Samples/Common/DicomDatasetReader.h    |  30 +-
 .../Orthanc/Plugins/Samples/Common/DicomPath.cpp   |  77 ++-
 .../Orthanc/Plugins/Samples/Common/DicomPath.h     |  93 ++--
 .../Orthanc/Plugins/Samples/Common/DicomTag.cpp    | 110 ++++
 .../Orthanc/Plugins/Samples/Common/DicomTag.h      |  95 ++++
 .../Plugins/Samples/Common/ExportedSymbols.list    |   0
 .../Plugins/Samples/Common/FullOrthancDataset.cpp  | 196 +++++++
 .../Plugins/Samples/Common/FullOrthancDataset.h    |  35 +-
 .../Orthanc/Plugins/Samples/Common/IDicomDataset.h |  24 +-
 .../Plugins/Samples/Common/IOrthancConnection.cpp  |  78 ++-
 .../Plugins/Samples/Common/IOrthancConnection.h    |  75 ++-
 .../Samples/Common/OrthancHttpConnection.cpp       | 107 ++++
 .../Plugins/Samples/Common/OrthancHttpConnection.h |  53 +-
 .../Samples/Common/OrthancPluginConnection.cpp     |  74 ++-
 .../Samples/Common/OrthancPluginConnection.h       |  38 +-
 .../Samples/Common/OrthancPluginCppWrapper.cpp     |  68 ++-
 .../Samples/Common/OrthancPluginCppWrapper.h       |  35 +-
 .../Samples/Common/SimplifiedOrthancDataset.cpp    | 156 ++++++
 .../Samples/Common/SimplifiedOrthancDataset.h      |  35 +-
 .../Plugins/Samples/Common/VersionScript.map       |   0
 {Framework => Resources}/Orthanc/README.txt        |   0
 .../Resources/CMake/AutoGeneratedCode.cmake        |   0
 .../Resources/CMake/BoostConfiguration.cmake       |   5 +-
 .../Orthanc/Resources/CMake/Compiler.cmake         |  29 +-
 .../Resources/CMake/DcmtkConfiguration.cmake       |  26 +-
 .../Orthanc/Resources/CMake/DownloadPackage.cmake  |  24 +-
 .../Resources/CMake/JsonCppConfiguration.cmake     |   0
 .../Resources/CMake/LibCurlConfiguration.cmake     |   0
 .../Resources/CMake/LibJpegConfiguration.cmake     |   0
 .../Resources/CMake/LibPngConfiguration.cmake      |   0
 .../Resources/CMake/OpenSslConfiguration.cmake     |   0
 .../CMake/VisualStudioPrecompiledHeaders.cmake     |   0
 .../Resources/CMake/ZlibConfiguration.cmake        |   0
 .../Orthanc/Resources/EmbedResources.py            |   4 +
 .../Orthanc/Resources/MinGW-W64-Toolchain32.cmake  |   0
 .../Orthanc/Resources/MinGW-W64-Toolchain64.cmake  |   0
 .../Orthanc/Resources/MinGWToolchain.cmake         |   0
 .../Resources/Patches/dcmtk-3.6.0-mingw64.patch    |   0
 .../Resources/Patches/dcmtk-3.6.0-speed.patch      |   0
 .../Resources/Patches/dcmtk-3.6.1-speed.patch      |   0
 .../Resources/ThirdParty/VisualStudio/stdint.h     |   0
 .../Orthanc/Resources/ThirdParty/base64/base64.cpp |   0
 .../Orthanc/Resources/ThirdParty/base64/base64.h   |   0
 .../Orthanc/Resources/WindowsResources.py          |   0
 .../Orthanc/Resources/WindowsResources.rc          |   0
 .../Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h     |   0
 Resources/OrthancWSIClearCache.py                  |  77 +++
 Resources/SyncOrthancFolder.py                     |  26 +-
 TODO                                               |   8 +-
 ViewerPlugin/CMakeLists.txt                        |  16 +-
 ViewerPlugin/DicomPyramidCache.cpp                 | 156 ++++++
 ViewerPlugin/DicomPyramidCache.h                   |  70 +++
 ViewerPlugin/Plugin.cpp                            | 145 ++---
 197 files changed, 4022 insertions(+), 2640 deletions(-)

diff --git a/.hg_archival.txt b/.hg_archival.txt
index 9dab074..2add95f 100644
--- a/.hg_archival.txt
+++ b/.hg_archival.txt
@@ -1,5 +1,5 @@
 repo: 4a7a53257c7df5a97aea39377b8c9a6e815c9763
-node: b1d6a0efe09b27f8d7095a792823c6da658b45b3
-branch: OrthancWSI-0.1
+node: cafc4728a8577cd9f07e4bbff7a638ba577ceb10
+branch: OrthancWSI-0.2
 latesttag: null
-latesttagdistance: 35
+latesttagdistance: 76
diff --git a/Applications/ApplicationToolbox.cpp b/Applications/ApplicationToolbox.cpp
index 89fe8dd..49ebabb 100644
--- a/Applications/ApplicationToolbox.cpp
+++ b/Applications/ApplicationToolbox.cpp
@@ -21,10 +21,10 @@
 #include "ApplicationToolbox.h"
 
 #include "../Framework/Inputs/OpenSlideLibrary.h"
-#include "../Framework/Orthanc/Core/HttpClient.h"
-#include "../Framework/Orthanc/Core/Logging.h"
-#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
-#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h"
+#include "../Resources/Orthanc/Core/HttpClient.h"
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
+#include "../Resources/Orthanc/OrthancServer/FromDcmtkBridge.h"
 
 #include <boost/lexical_cast.hpp>
 #include <boost/regex.hpp>
@@ -38,7 +38,7 @@ namespace OrthancWSI
       Orthanc::Logging::Initialize();
       Orthanc::HttpClient::InitializeOpenSsl();
       Orthanc::HttpClient::GlobalInitialize();
-      Orthanc::FromDcmtkBridge::InitializeDictionary();
+      Orthanc::FromDcmtkBridge::InitializeDictionary(false /* don't load private dictionary */);
     }
 
 
diff --git a/Applications/ApplicationToolbox.h b/Applications/ApplicationToolbox.h
index 6650f1c..1bfe5ca 100644
--- a/Applications/ApplicationToolbox.h
+++ b/Applications/ApplicationToolbox.h
@@ -20,7 +20,7 @@
 
 #pragma once
 
-#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasks.h"
+#include "../Resources/Orthanc/Core/MultiThreading/BagOfTasks.h"
 
 #include <string>
 #include <stdint.h>
diff --git a/Applications/CMakeLists.txt b/Applications/CMakeLists.txt
index 622ba23..572a651 100644
--- a/Applications/CMakeLists.txt
+++ b/Applications/CMakeLists.txt
@@ -9,6 +9,7 @@ project(OrthancWSIApplications)
 # Generic parameters
 SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
 SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+SET(ENABLE_PROFILING OFF CACHE BOOL "Whether to enable the generation of profiling information with gprof")
 
 # Optional components
 SET(ENABLE_SSL OFF CACHE BOOL "Include support for SSL")
@@ -32,6 +33,7 @@ SET(USE_SYSTEM_LIBTIFF ON CACHE BOOL "Use the system version of libtiff")
 SET(USE_SYSTEM_OPENJPEG ON CACHE BOOL "Use the system version of OpenJpeg")
 SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL")
 SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib")
+SET(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
 
 SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") 
 
@@ -41,12 +43,13 @@ SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionar
 #####################################################################
 
 SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
-SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc)
+SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Resources/Orthanc)
 SET(USE_OPENJPEG_JP2 ON)
 SET(ENABLE_JPEG OFF)           # Disable DCMTK's support for JPEG, that clashes with libtiff
 SET(ENABLE_JPEG_LOSSLESS OFF)  # Disable DCMTK's support for JPEG-LS
 SET(ENABLE_DCMTK_NETWORK OFF)  # Disable DCMTK's support for DICOM networking
 SET(STANDALONE_BUILD ON)       # Embed DCMTK's dictionaries for static builds
+SET(USE_DCMTK_361_PRIVATE_DIC OFF)  # No need for private tags
 
 include(CheckIncludeFiles)
 include(CheckIncludeFileCXX)
@@ -80,23 +83,39 @@ add_definitions(
   -DORTHANC_ENABLE_DCMTK=1
   -DORTHANC_ENABLE_LOGGING=1
   -DORTHANC_ENABLE_MD5=0
-  -DORTHANC_JPEG_ENABLED=0     # Disable DCMTK's support for JPEG
-  -DORTHANC_PKCS11_ENABLED=0
-  -DORTHANC_PLUGINS_ENABLED=1  # To enable class Orthanc::SharedLibrary
-  -DORTHANC_PUGIXML_ENABLED=0
+  -DORTHANC_ENABLE_JPEG=0     # Disable DCMTK's support for JPEG
+  -DORTHANC_ENABLE_PKCS11=0
+  -DORTHANC_ENABLE_PLUGINS=1  # To enable class Orthanc::SharedLibrary
+  -DORTHANC_ENABLE_PUGIXML=0
+  -DORTHANC_SANDBOXED=0
+  -DHAS_ORTHANC_EXCEPTION=1
   )
 
 
 #####################################################################
+## Find the Orthanc SDK
+#####################################################################
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  include_directories(${ORTHANC_ROOT}/Sdk-1.0.0)
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+#####################################################################
 ## Configure optional third-party components
 #####################################################################
 
 if (ENABLE_SSL)
   set(ENABLE_PKCS11 OFF)
-  add_definitions(-DORTHANC_SSL_ENABLED=1)
+  add_definitions(-DORTHANC_ENABLE_SSL=1)
   include(${ORTHANC_ROOT}/Resources/CMake/OpenSslConfiguration.cmake)
 else()
-  add_definitions(-DORTHANC_SSL_ENABLED=0)
+  add_definitions(-DORTHANC_ENABLE_SSL=0)
 endif()
 
 
@@ -106,7 +125,6 @@ endif()
 #####################################################################
 
 set(ORTHANC_WSI_SOURCES
-  #${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp
   ${ORTHANC_WSI_DIR}/Framework/Algorithms/PyramidReader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Algorithms/ReconstructPyramidCommand.cpp
   ${ORTHANC_WSI_DIR}/Framework/Algorithms/TranscodeTileCommand.cpp
@@ -127,11 +145,8 @@ set(ORTHANC_WSI_SOURCES
   ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/CurlOrthancConnection.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/FolderTarget.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Targets/FolderTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Targets/OrthancTarget.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/DicomPyramidWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/HierarchicalTiffWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/InMemoryTiledImage.cpp
@@ -161,13 +176,25 @@ set(ORTHANC_CORE_SOURCES
   ${ORTHANC_ROOT}/Core/Logging.cpp
   ${ORTHANC_ROOT}/Core/MultiThreading/BagOfTasksProcessor.cpp
   ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp
+  ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
+  ${ORTHANC_ROOT}/Core/TemporaryFile.cpp
   ${ORTHANC_ROOT}/Core/Toolbox.cpp
-  ${ORTHANC_ROOT}/Core/Uuid.cpp
   ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
   ${ORTHANC_ROOT}/OrthancServer/FromDcmtkBridge.cpp
   ${ORTHANC_ROOT}/OrthancServer/ServerEnumerations.cpp
   ${ORTHANC_ROOT}/OrthancServer/ToDcmtkBridge.cpp
+
   ${ORTHANC_ROOT}/Plugins/Engine/SharedLibrary.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomTag.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancHttpConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp
+
   ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
   )
 
@@ -187,7 +214,7 @@ if (MSVC)
   add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1)
 
   ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
-    "PrecompiledHeaders.h" "${ORTHANC_WSI_DIR}/Framework/Orthanc/Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES)
+    "PrecompiledHeaders.h" "${ORTHANC_WSI_DIR}/Resources/Orthanc/Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES)
 
   ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
     "PrecompiledHeadersWSI.h" "${ORTHANC_WSI_DIR}/Framework/PrecompiledHeadersWSI.cpp" ORTHANC_WSI_SOURCES)
diff --git a/Applications/DicomToTiff.cpp b/Applications/DicomToTiff.cpp
index 99d2c9c..cb43de1 100644
--- a/Applications/DicomToTiff.cpp
+++ b/Applications/DicomToTiff.cpp
@@ -21,11 +21,13 @@
 #include "../Framework/DicomToolbox.h"
 #include "../Framework/ImageToolbox.h"
 #include "../Framework/Inputs/DicomPyramid.h"
-#include "../Framework/Messaging/CurlOrthancConnection.h"
-#include "../Framework/Orthanc/Core/Logging.h"
-#include "../Framework/Orthanc/Core/OrthancException.h"
+#include "../Framework/Inputs/TiledPyramidStatistics.h"
 #include "../Framework/Outputs/HierarchicalTiffWriter.h"
 
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h"
+
 #include "ApplicationToolbox.h"
 
 #include <boost/program_options.hpp>
@@ -114,7 +116,8 @@ static bool ParseParameters(int& exitStatus,
               << std::endl
               << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
               << std::endl << std::endl
-              << "Convert a DICOM for digital pathology stored in some Orthanc server as a standard hierarchical TIFF."
+              << "Convert a DICOM image for digital pathology stored in some Orthanc server as a" << std::endl
+              << "standard hierarchical TIFF (whose tiles are all encoded using JPEG)."
               << std::endl;
 
     std::cout << allWithoutHidden << "\n";
@@ -167,56 +170,8 @@ static Orthanc::ImageAccessor* CreateEmptyTile(const OrthancWSI::IPyramidWriter&
 
 
 
-static void RunTranscode(OrthancWSI::ITiledPyramid& source,
-                         const boost::program_options::variables_map& options)
-{
-  OrthancWSI::HierarchicalTiffWriter target(options["output"].as<std::string>(),
-                                            source.GetPixelFormat(), 
-                                            source.GetImageCompression(), 
-                                            source.GetTileWidth(), 
-                                            source.GetTileHeight());
-
-  std::auto_ptr<Orthanc::ImageAccessor> empty(CreateEmptyTile(target, options));
-
-  for (unsigned int level = 0; level < source.GetLevelCount(); level++)
-  {
-    LOG(WARNING) << "Creating level " << level << " of size " 
-                 << source.GetLevelWidth(level) << "x" << source.GetLevelHeight(level);
-    target.AddLevel(source.GetLevelWidth(level), source.GetLevelHeight(level));
-  }
-
-  for (unsigned int level = 0; level < source.GetLevelCount(); level++)
-  {
-    LOG(WARNING) << "Transcoding level " << level;
-
-    unsigned int countX = OrthancWSI::CeilingDivision(source.GetLevelWidth(level), source.GetTileWidth());
-    unsigned int countY = OrthancWSI::CeilingDivision(source.GetLevelHeight(level), source.GetTileHeight());
-
-    for (unsigned int tileY = 0; tileY < countY; tileY++)
-    {
-      for (unsigned int tileX = 0; tileX < countX; tileX++)
-      {
-        LOG(INFO) << "Dealing with tile (" << tileX << "," << tileY << ") at level " << level;
-        std::string tile;
-
-        if (source.ReadRawTile(tile, level, tileX, tileY))
-        {
-          target.WriteRawTile(tile, source.GetImageCompression(), level, tileX, tileY);
-        }
-        else
-        {
-          target.EncodeTile(*empty, level, tileX, tileY);
-        }
-      }        
-    }
-
-    target.Flush();
-  }
-}
-
-
-static void RunReencode(OrthancWSI::ITiledPyramid& source,
-                        const boost::program_options::variables_map& options)
+static void Run(OrthancWSI::ITiledPyramid& source,
+                const boost::program_options::variables_map& options)
 {
   OrthancWSI::HierarchicalTiffWriter target(options["output"].as<std::string>(),
                                             source.GetPixelFormat(), 
@@ -224,6 +179,9 @@ static void RunReencode(OrthancWSI::ITiledPyramid& source,
                                             source.GetTileWidth(), 
                                             source.GetTileHeight());
 
+  bool reencode = (options.count("reencode") &&
+                   options["reencode"].as<bool>());
+
   if (options.count("jpeg-quality"))
   {
     target.SetJpegQuality(options["jpeg-quality"].as<int>());
@@ -240,7 +198,7 @@ static void RunReencode(OrthancWSI::ITiledPyramid& source,
 
   for (unsigned int level = 0; level < source.GetLevelCount(); level++)
   {
-    LOG(WARNING) << "Reencoding level " << level;
+    LOG(WARNING) << std::string(reencode ? "Reencoding" : "Transcoding") << " level " << level;
 
     unsigned int countX = OrthancWSI::CeilingDivision(source.GetLevelWidth(level), source.GetTileWidth());
     unsigned int countY = OrthancWSI::CeilingDivision(source.GetLevelHeight(level), source.GetTileHeight());
@@ -251,14 +209,58 @@ static void RunReencode(OrthancWSI::ITiledPyramid& source,
       {
         LOG(INFO) << "Dealing with tile (" << tileX << "," << tileY << ") at level " << level;
 
-        std::auto_ptr<Orthanc::ImageAccessor> tile(source.DecodeTile(level, tileX, tileY));
-        if (tile.get() == NULL)
+        bool missing = false;
+        bool success = true;
+
+        // Give a first try to get the raw tile
+        std::string tile;
+        OrthancWSI::ImageCompression compression;
+        if (source.ReadRawTile(tile, compression, level, tileX, tileY))
         {
-          target.EncodeTile(*empty, level, tileX, tileY);
+          if (reencode ||
+              compression == OrthancWSI::ImageCompression_Jpeg)
+          {
+            target.WriteRawTile(tile, compression, level, tileX, tileY);
+          }
+          else
+          {
+            success = false;  // Re-encoding is mandatory
+          }
         }
         else
         {
-          target.EncodeTile(*tile, level, tileX, tileY);
+          // Give a second try to get the decoded tile
+          compression = OrthancWSI::ImageCompression_Unknown;
+
+          std::auto_ptr<Orthanc::ImageAccessor> tile(source.DecodeTile(level, tileX, tileY));
+          if (tile.get() == NULL)
+          {
+            // Unable to read the raw tile or to decode it: The tile is missing (sparse tiling)
+            missing = true;
+          }
+          else if (reencode)
+          {
+            target.EncodeTile(*empty, level, tileX, tileY);
+          }
+          else
+          {
+            success = false;  // Re-encoding is mandatory
+          }
+        }
+
+        if (!success)
+        {
+          LOG(WARNING) << "Cannot transcode a DICOM image that is not encoded using JPEG (it is " 
+                       << OrthancWSI::EnumerationToString(compression) 
+                       << "), please use the --reencode=1 option";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        
+        if (missing)
+        {
+          LOG(WARNING) << "Sparse tiling: Using an empty image for missing tile ("
+                       << tileX << "," << tileY << ") at level " << level;
+          target.EncodeTile(*empty, level, tileX, tileY);
         }
       }        
     }
@@ -297,18 +299,12 @@ int main(int argc, char* argv[])
         params.SetPassword(options["password"].as<std::string>());
       }
 
-      OrthancWSI::CurlOrthancConnection orthanc(params);
-      OrthancWSI::DicomPyramid source(orthanc, options["input"].as<std::string>());
+      OrthancPlugins::OrthancHttpConnection orthanc(params);
+      OrthancWSI::DicomPyramid source(orthanc, options["input"].as<std::string>(), 
+                                      false /* don't use cached metadata */);
 
-      if (options.count("reencode") &&
-          options["reencode"].as<bool>())
-      {
-        RunReencode(source, options);
-      }
-      else
-      {
-        RunTranscode(source, options);
-      }
+      OrthancWSI::TiledPyramidStatistics stats(source);
+      Run(stats, options);
     }
   }
   catch (Orthanc::OrthancException& e)
diff --git a/Applications/Dicomizer.cpp b/Applications/Dicomizer.cpp
index c325fcc..d2de5a6 100644
--- a/Applications/Dicomizer.cpp
+++ b/Applications/Dicomizer.cpp
@@ -28,14 +28,15 @@
 #include "../Framework/Inputs/TiledJpegImage.h"
 #include "../Framework/Inputs/TiledPngImage.h"
 #include "../Framework/Inputs/TiledPyramidStatistics.h"
-#include "../Framework/Orthanc/Core/HttpClient.h"
-#include "../Framework/Orthanc/Core/Logging.h"
-#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
-#include "../Framework/Orthanc/Core/Toolbox.h"
-#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h"
 #include "../Framework/Outputs/DicomPyramidWriter.h"
 #include "../Framework/Outputs/TruncatedPyramidWriter.h"
 
+#include "../Resources/Orthanc/Core/HttpClient.h"
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
+#include "../Resources/Orthanc/Core/SystemToolbox.h"
+#include "../Resources/Orthanc/OrthancServer/FromDcmtkBridge.h"
+
 #include "ApplicationToolbox.h"
 
 #include <EmbeddedResources.h>
@@ -133,7 +134,6 @@ static void Recompress(OrthancWSI::IFileTarget& output,
   OrthancWSI::TiledPyramidStatistics stats(source);
 
   LOG(WARNING) << "Size of source tiles: " << stats.GetTileWidth() << "x" << stats.GetTileHeight();
-  LOG(WARNING) << "Source image compression: " << OrthancWSI::EnumerationToString(stats.GetImageCompression());
   LOG(WARNING) << "Pixel format: " << Orthanc::EnumerationToString(stats.GetPixelFormat());
   LOG(WARNING) << "Smoothing is " << (parameters.IsSmoothEnabled() ? "enabled" : "disabled");
 
@@ -210,7 +210,7 @@ static DcmDataset* ParseDataset(const std::string& path)
   else
   {
     std::string content;
-    Orthanc::Toolbox::ReadFile(content, path);
+    Orthanc::SystemToolbox::ReadFile(content, path);
 
     Json::Reader reader;
     if (!reader.parse(content, json, false))
@@ -241,7 +241,7 @@ static DcmDataset* ParseDataset(const std::string& path)
   OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ImageOrientationSlide, "0\\-1\\0\\-1\\0\\0");
 
   std::string date, time;
-  Orthanc::Toolbox::GetNowDicom(date, time);
+  Orthanc::SystemToolbox::GetNowDicom(date, time);
   OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyDate, date);
   OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyTime, time);
   OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesDate, date);
@@ -338,12 +338,13 @@ static void SetupDimension(DcmDataset& dataset,
 
 static void EnrichDataset(DcmDataset& dataset,
                           const OrthancWSI::ITiledPyramid& source,
+                          OrthancWSI::ImageCompression sourceCompression,
                           const OrthancWSI::DicomizerParameters& parameters,
                           const OrthancWSI::ImagedVolumeParameters& volume)
 {
   Orthanc::Encoding encoding = Orthanc::FromDcmtkBridge::DetectEncoding(dataset, Orthanc::Encoding_Latin1);
 
-  if (source.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg ||
+  if (sourceCompression == OrthancWSI::ImageCompression_Jpeg ||
       parameters.GetTargetCompression() == OrthancWSI::ImageCompression_Jpeg)
   {
     // Takes as estimation a 1:10 compression ratio
@@ -410,7 +411,7 @@ static void EnrichDataset(DcmDataset& dataset,
   }
   else
   {
-    Orthanc::Toolbox::ReadFile(profile, parameters.GetIccProfilePath());
+    Orthanc::SystemToolbox::ReadFile(profile, parameters.GetIccProfilePath());
   }
 
   
@@ -796,7 +797,8 @@ static bool ParseParameters(int& exitStatus,
 }
 
 
-OrthancWSI::ITiledPyramid* OpenInputPyramid(const std::string& path,
+OrthancWSI::ITiledPyramid* OpenInputPyramid(OrthancWSI::ImageCompression& sourceCompression,
+                                            const std::string& path,
                                             const OrthancWSI::DicomizerParameters& parameters)
 {
   LOG(WARNING) << "The input image is: " << path;
@@ -807,20 +809,28 @@ OrthancWSI::ITiledPyramid* OpenInputPyramid(const std::string& path,
   switch (format)
   {
     case OrthancWSI::ImageCompression_Png:
+    {
+      sourceCompression = OrthancWSI::ImageCompression_Unknown;
       return new OrthancWSI::TiledPngImage(path, 
                                            parameters.GetTargetTileWidth(512), 
                                            parameters.GetTargetTileHeight(512));
+    }
 
     case OrthancWSI::ImageCompression_Jpeg:
+    {
+      sourceCompression = OrthancWSI::ImageCompression_Unknown;
       return new OrthancWSI::TiledJpegImage(path, 
                                             parameters.GetTargetTileWidth(512), 
                                             parameters.GetTargetTileHeight(512));
+    }
 
     case OrthancWSI::ImageCompression_Tiff:
     {
       try
       {
-        return new OrthancWSI::HierarchicalTiff(path);
+        std::auto_ptr<OrthancWSI::HierarchicalTiff> tiff(new OrthancWSI::HierarchicalTiff(path));
+        sourceCompression = tiff->GetImageCompression();
+        return tiff.release();
       }
       catch (Orthanc::OrthancException&)
       {
@@ -835,6 +845,7 @@ OrthancWSI::ITiledPyramid* OpenInputPyramid(const std::string& path,
   try
   {
     LOG(WARNING) << "Trying to open the input pyramid with OpenSlide";
+    sourceCompression = OrthancWSI::ImageCompression_Unknown;
     return new OrthancWSI::OpenSlidePyramid(path, 
                                             parameters.GetTargetTileWidth(512), 
                                             parameters.GetTargetTileHeight(512));
@@ -860,15 +871,20 @@ int main(int argc, char* argv[])
 
     if (ParseParameters(exitStatus, parameters, volume, argc, argv))
     {
-      std::auto_ptr<OrthancWSI::ITiledPyramid> source(OpenInputPyramid(parameters.GetInputFile(), parameters));
+      OrthancWSI::ImageCompression sourceCompression;
+      std::auto_ptr<OrthancWSI::ITiledPyramid> source;
+
+      source.reset(OpenInputPyramid(sourceCompression, parameters.GetInputFile(), parameters));
       if (source.get() == NULL)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
       }
+
+      LOG(WARNING) << "Compression of the individual source tiles: " << OrthancWSI::EnumerationToString(sourceCompression);
       
       // Create the shared DICOM tags
       std::auto_ptr<DcmDataset> dataset(ParseDataset(parameters.GetDatasetPath()));
-      EnrichDataset(*dataset, *source, parameters, volume);
+      EnrichDataset(*dataset, *source, sourceCompression, parameters, volume);
 
       std::auto_ptr<OrthancWSI::IFileTarget> output(parameters.CreateTarget());
       Recompress(*output, *source, *dataset, parameters, volume);
diff --git a/Framework/Algorithms/PyramidReader.cpp b/Framework/Algorithms/PyramidReader.cpp
index 1bb30b1..0e15d11 100644
--- a/Framework/Algorithms/PyramidReader.cpp
+++ b/Framework/Algorithms/PyramidReader.cpp
@@ -22,8 +22,8 @@
 #include "PyramidReader.h"
 
 #include "../ImageToolbox.h"
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 #include <cassert>
 
@@ -32,11 +32,12 @@ namespace OrthancWSI
   class PyramidReader::SourceTile : public boost::noncopyable
   {
   private: 
-    PyramidReader&  that_;
-    unsigned int    tileX_;
-    unsigned int    tileY_;
-    bool            hasRawTile_;
-    std::string     rawTile_;
+    PyramidReader&    that_;
+    unsigned int      tileX_;
+    unsigned int      tileY_;
+    bool              hasRawTile_;
+    std::string       rawTile_;
+    ImageCompression  rawTileCompression_;
 
     std::auto_ptr<Orthanc::ImageAccessor>  decoded_;
 
@@ -96,7 +97,7 @@ namespace OrthancWSI
     {
       if (!that_.parameters_.IsForceReencode() &&
           !IsRepaintNeeded() &&
-          that_.source_.ReadRawTile(rawTile_, that_.level_, tileX, tileY))
+          that_.source_.ReadRawTile(rawTile_, rawTileCompression_, that_.level_, tileX, tileY))
       {
         hasRawTile_ = true;
       }
@@ -113,9 +114,17 @@ namespace OrthancWSI
       }
     }
 
-    bool HasRawTile() const
+    bool HasRawTile(ImageCompression& compression) const
     {
-      return hasRawTile_;
+      if (hasRawTile_)
+      {
+        compression = rawTileCompression_;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
     }
 
     const std::string& GetRawTile() const
@@ -139,7 +148,7 @@ namespace OrthancWSI
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
         }
 
-        decoded_.reset(ImageToolbox::DecodeTile(rawTile_, that_.source_.GetImageCompression()));
+        decoded_.reset(ImageToolbox::DecodeTile(rawTile_, rawTileCompression_));
         if (decoded_.get() == NULL)
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
@@ -180,11 +189,12 @@ namespace OrthancWSI
   }
 
 
-  void PyramidReader::CheckTileSize(const std::string& tile) const
+  void PyramidReader::CheckTileSize(const std::string& tile,
+                                    ImageCompression compression) const
   {
     if (parameters_.IsSafetyCheck())
     {
-      std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(tile, source_.GetImageCompression()));
+      std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(tile, compression));
       CheckTileSize(*decoded);
     }
   }
@@ -225,9 +235,9 @@ namespace OrthancWSI
     levelHeight_(source.GetLevelHeight(level)),
     sourceTileWidth_(source.GetTileWidth()),
     sourceTileHeight_(source.GetTileHeight()),
-                                                                         targetTileWidth_(targetTileWidth),
-                                                                         targetTileHeight_(targetTileHeight),
-                                                                         parameters_(parameters)
+    targetTileWidth_(targetTileWidth),
+    targetTileHeight_(targetTileHeight),
+    parameters_(parameters)
   {
     if (sourceTileWidth_ % targetTileWidth_ != 0 ||
         sourceTileHeight_ % targetTileHeight_ != 0)
@@ -248,7 +258,8 @@ namespace OrthancWSI
   }
 
 
-  const std::string* PyramidReader::GetRawTile(unsigned int tileX,
+  const std::string* PyramidReader::GetRawTile(ImageCompression& compression,
+                                               unsigned int tileX,
                                                unsigned int tileY)
   {
     if (sourceTileWidth_ != targetTileWidth_ ||
@@ -258,9 +269,10 @@ namespace OrthancWSI
     }
 
     SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
-    if (source.HasRawTile())
+
+    if (source.HasRawTile(compression))
     {
-      CheckTileSize(source.GetRawTile());
+      CheckTileSize(source.GetRawTile(), compression);
       return &source.GetRawTile();
     }
     else
diff --git a/Framework/Algorithms/PyramidReader.h b/Framework/Algorithms/PyramidReader.h
index d8ede82..05a5dad 100644
--- a/Framework/Algorithms/PyramidReader.h
+++ b/Framework/Algorithms/PyramidReader.h
@@ -56,7 +56,8 @@ namespace OrthancWSI
 
     void CheckTileSize(const Orthanc::ImageAccessor& tile) const;
 
-    void CheckTileSize(const std::string& tile) const;
+    void CheckTileSize(const std::string& tile,
+                       ImageCompression compression) const;
 
     SourceTile& AccessSourceTile(const Location& location);
 
@@ -77,17 +78,13 @@ namespace OrthancWSI
       return parameters_;
     }
 
-    ImageCompression GetImageCompression() const
-    {
-      return source_.GetImageCompression();
-    }
-
     Orthanc::PixelFormat GetPixelFormat() const
     {
       return source_.GetPixelFormat();
     }
 
-    const std::string* GetRawTile(unsigned int tileX,
+    const std::string* GetRawTile(ImageCompression& compression,
+                                  unsigned int tileX,
                                   unsigned int tileY);
 
     Orthanc::ImageAccessor GetDecodedTile(unsigned int tileX,
diff --git a/Framework/Algorithms/ReconstructPyramidCommand.cpp b/Framework/Algorithms/ReconstructPyramidCommand.cpp
index fd4f368..5c85d4d 100644
--- a/Framework/Algorithms/ReconstructPyramidCommand.cpp
+++ b/Framework/Algorithms/ReconstructPyramidCommand.cpp
@@ -22,9 +22,9 @@
 #include "ReconstructPyramidCommand.h"
 
 #include "../ImageToolbox.h"
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Images/Image.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Images/Image.h"
 
 #include <cassert>
 
@@ -51,12 +51,13 @@ namespace OrthancWSI
     {
       result.reset(new Orthanc::ImageAccessor(source_.GetDecodedTile(x, y)));
 
-      const std::string* rawTile = source_.GetRawTile(x, y);
+      ImageCompression compression;
+      const std::string* rawTile = source_.GetRawTile(compression, x, y);
 
       if (rawTile != NULL)
       {
         // Simple transcoding
-        target_.WriteRawTile(*rawTile, source_.GetImageCompression(), level + shiftTargetLevel_, x, y);
+        target_.WriteRawTile(*rawTile, compression, level + shiftTargetLevel_, x, y);
       }
       else
       {
diff --git a/Framework/Algorithms/ReconstructPyramidCommand.h b/Framework/Algorithms/ReconstructPyramidCommand.h
index d2f6c09..2f61c8b 100644
--- a/Framework/Algorithms/ReconstructPyramidCommand.h
+++ b/Framework/Algorithms/ReconstructPyramidCommand.h
@@ -22,7 +22,7 @@
 
 #include "PyramidReader.h"
 #include "../Outputs/IPyramidWriter.h"
-#include "../Orthanc/Core/MultiThreading/BagOfTasks.h"
+#include "../../Resources/Orthanc/Core/MultiThreading/BagOfTasks.h"
 
 
 namespace OrthancWSI
diff --git a/Framework/Algorithms/TranscodeTileCommand.cpp b/Framework/Algorithms/TranscodeTileCommand.cpp
index 4409c0a..0c4f54d 100644
--- a/Framework/Algorithms/TranscodeTileCommand.cpp
+++ b/Framework/Algorithms/TranscodeTileCommand.cpp
@@ -21,8 +21,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "TranscodeTileCommand.h"
 
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
 
 #include <cassert>
 
@@ -61,12 +61,14 @@ namespace OrthancWSI
       for (unsigned int y = y_; y < y_ + countTilesY_; y++)
       {
         LOG(INFO) << "Adding tile (" << x << "," << y << ") at level " << level_;
-        const std::string* rawTile = source_.GetRawTile(x, y);
+
+        ImageCompression compression;
+        const std::string* rawTile = source_.GetRawTile(compression, x, y);
 
         if (rawTile != NULL)
         {
           // Simple transcoding
-          target_.WriteRawTile(*rawTile, source_.GetImageCompression(), level_, x, y);
+          target_.WriteRawTile(*rawTile, compression, level_, x, y);
         }
         else
         {
diff --git a/Framework/Algorithms/TranscodeTileCommand.h b/Framework/Algorithms/TranscodeTileCommand.h
index 7136140..8e9b7b1 100644
--- a/Framework/Algorithms/TranscodeTileCommand.h
+++ b/Framework/Algorithms/TranscodeTileCommand.h
@@ -23,7 +23,7 @@
 #include "PyramidReader.h"
 #include "../Outputs/IPyramidWriter.h"
 
-#include "../Orthanc/Core/MultiThreading/BagOfTasks.h"
+#include "../../Resources/Orthanc/Core/MultiThreading/BagOfTasks.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/DicomToolbox.cpp b/Framework/DicomToolbox.cpp
index 1130a30..c35bc76 100644
--- a/Framework/DicomToolbox.cpp
+++ b/Framework/DicomToolbox.cpp
@@ -21,17 +21,15 @@
 #include "PrecompiledHeadersWSI.h"
 #include "DicomToolbox.h"
 
-#include "Orthanc/Core/Logging.h"
-#include "Orthanc/Core/OrthancException.h"
-#include "Orthanc/Core/Toolbox.h"
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/Toolbox.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
 #  include <dcmtk/dcmdata/dcelem.h>
 #  include <dcmtk/dcmdata/dcsequen.h>
 #endif
 
-#include <boost/lexical_cast.hpp>
-
 namespace OrthancWSI
 {
   namespace DicomToolbox
@@ -160,92 +158,5 @@ namespace OrthancWSI
       }
     }
 #endif
-
-
-    bool GetStringTag(std::string& result,
-                      const Json::Value& simplifiedTags,
-                      const std::string& tagName,
-                      const std::string& defaultValue)
-    {
-      if (simplifiedTags.type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      if (!simplifiedTags.isMember(tagName))
-      {
-        result = defaultValue;
-        return false;
-      }
-      else if (simplifiedTags[tagName].type() != Json::stringValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-      else
-      {
-        result = simplifiedTags[tagName].asString();
-        return true;
-      }
-    }
-
-
-    std::string GetMandatoryStringTag(const Json::Value& simplifiedTags,
-                                      const std::string& tagName)
-    {
-      std::string s;
-      if (GetStringTag(s, simplifiedTags, tagName, ""))
-      {
-        return s;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);        
-      }
-    }
-
-
-    const Json::Value& GetSequenceTag(const Json::Value& simplifiedTags,
-                                      const std::string& tagName)
-    {
-      if (simplifiedTags.type() != Json::objectValue ||
-          !simplifiedTags.isMember(tagName) ||
-          simplifiedTags[tagName].type() != Json::arrayValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      return simplifiedTags[tagName];
-    }
-
-
-    int GetIntegerTag(const Json::Value& simplifiedTags,
-                      const std::string& tagName)
-    {
-      try
-      {
-        std::string s = Orthanc::Toolbox::StripSpaces(GetMandatoryStringTag(simplifiedTags, tagName));
-        return boost::lexical_cast<int>(s);
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
-      }
-    }
-
-
-    unsigned int GetUnsignedIntegerTag(const Json::Value& simplifiedTags,
-                                       const std::string& tagName)
-    {
-      int value = GetIntegerTag(simplifiedTags, tagName);
-
-      if (value >= 0)
-      {
-        return static_cast<unsigned int>(value);
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
   }
 }
diff --git a/Framework/DicomToolbox.h b/Framework/DicomToolbox.h
index f360226..874a8b3 100644
--- a/Framework/DicomToolbox.h
+++ b/Framework/DicomToolbox.h
@@ -62,22 +62,5 @@ namespace OrthancWSI
     std::string GetStringTag(DcmItem& dataset,
                              const DcmTagKey& key);
 #endif
-
-    bool GetStringTag(std::string& result,
-                      const Json::Value& simplifiedTags,
-                      const std::string& tagName,
-                      const std::string& defaultValue);
-
-    std::string GetMandatoryStringTag(const Json::Value& simplifiedTags,
-                                      const std::string& tagName);
-
-    const Json::Value& GetSequenceTag(const Json::Value& simplifiedTags,
-                                      const std::string& tagName);
-
-    int GetIntegerTag(const Json::Value& simplifiedTags,
-                      const std::string& tagName);
-
-    unsigned int GetUnsignedIntegerTag(const Json::Value& simplifiedTags,
-                                       const std::string& tagName);
   }
 }
diff --git a/Framework/DicomizerParameters.cpp b/Framework/DicomizerParameters.cpp
index c16c403..5af659a 100644
--- a/Framework/DicomizerParameters.cpp
+++ b/Framework/DicomizerParameters.cpp
@@ -21,10 +21,10 @@
 #include "PrecompiledHeadersWSI.h"
 #include "DicomizerParameters.h"
 
-#include "Messaging/FolderTarget.h"
-#include "Messaging/OrthancTarget.h"
+#include "Targets/FolderTarget.h"
+#include "Targets/OrthancTarget.h"
 
-#include "Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
 
 #include <boost/thread.hpp>
 #include <boost/lexical_cast.hpp>
diff --git a/Framework/DicomizerParameters.h b/Framework/DicomizerParameters.h
index ede1506..4b5e668 100644
--- a/Framework/DicomizerParameters.h
+++ b/Framework/DicomizerParameters.h
@@ -22,9 +22,9 @@
 
 #include "Inputs/ITiledPyramid.h"
 #include "Outputs/IPyramidWriter.h"
-#include "Messaging/IFileTarget.h"
+#include "Targets/IFileTarget.h"
 #include "DicomToolbox.h"
-#include "Orthanc/Core/WebServiceParameters.h"
+#include "../Resources/Orthanc/Core/WebServiceParameters.h"
 
 #include <stdint.h>
 
diff --git a/Framework/Enumerations.cpp b/Framework/Enumerations.cpp
index 52cda01..e1ff6b5 100644
--- a/Framework/Enumerations.cpp
+++ b/Framework/Enumerations.cpp
@@ -22,8 +22,9 @@
 #include "Enumerations.h"
 
 #include "Jpeg2000Reader.h"
-#include "Orthanc/Core/OrthancException.h"
-#include "Orthanc/Core/Toolbox.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/SystemToolbox.h"
+#include "../Resources/Orthanc/Core/Toolbox.h"
 
 #include <string.h>
 #include <boost/algorithm/string/predicate.hpp>
@@ -82,7 +83,7 @@ namespace OrthancWSI
   ImageCompression DetectFormatFromFile(const std::string& path)
   {
     std::string header;
-    Orthanc::Toolbox::ReadHeader(header, path, 256);
+    Orthanc::SystemToolbox::ReadHeader(header, path, 256);
 
     ImageCompression tmp = DetectFormatFromMemory(header.c_str(), header.size());
     if (tmp != ImageCompression_Unknown)
diff --git a/Framework/Enumerations.h b/Framework/Enumerations.h
index 0b63f81..a4f5715 100644
--- a/Framework/Enumerations.h
+++ b/Framework/Enumerations.h
@@ -20,7 +20,7 @@
 
 #pragma once
 
-#include "Orthanc/Core/Enumerations.h"
+#include "../Resources/Orthanc/Core/Enumerations.h"
 
 #include <stdint.h>
 #include <string>
diff --git a/Framework/ImageToolbox.cpp b/Framework/ImageToolbox.cpp
index 112a41a..b72b3cb 100644
--- a/Framework/ImageToolbox.cpp
+++ b/Framework/ImageToolbox.cpp
@@ -24,13 +24,13 @@
 #include "Jpeg2000Reader.h"
 #include "Jpeg2000Writer.h"
 
-#include "Orthanc/Core/OrthancException.h"
-#include "Orthanc/Core/Images/ImageProcessing.h"
-#include "Orthanc/Core/Images/PngReader.h"
-#include "Orthanc/Core/Images/PngWriter.h"
-#include "Orthanc/Core/Images/JpegReader.h"
-#include "Orthanc/Core/Images/JpegWriter.h"
-#include "Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include "../Resources/Orthanc/Core/Images/PngReader.h"
+#include "../Resources/Orthanc/Core/Images/PngWriter.h"
+#include "../Resources/Orthanc/Core/Images/JpegReader.h"
+#include "../Resources/Orthanc/Core/Images/JpegWriter.h"
+#include "../Resources/Orthanc/Core/Logging.h"
 
 #include <string.h>
 #include <memory>
diff --git a/Framework/ImageToolbox.h b/Framework/ImageToolbox.h
index f18328f..91b97a9 100644
--- a/Framework/ImageToolbox.h
+++ b/Framework/ImageToolbox.h
@@ -20,7 +20,7 @@
 
 #pragma once
 
-#include "Orthanc/Core/Images/ImageAccessor.h"
+#include "../Resources/Orthanc/Core/Images/ImageAccessor.h"
 #include "Enumerations.h"
 #include "Inputs/ITiledPyramid.h"
 
diff --git a/Framework/ImagedVolumeParameters.cpp b/Framework/ImagedVolumeParameters.cpp
index 93b458d..1cff542 100644
--- a/Framework/ImagedVolumeParameters.cpp
+++ b/Framework/ImagedVolumeParameters.cpp
@@ -21,7 +21,7 @@
 #include "PrecompiledHeadersWSI.h"
 #include "ImagedVolumeParameters.h"
 
-#include "Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Inputs/DecodedTiledPyramid.h b/Framework/Inputs/DecodedTiledPyramid.h
index b4a5676..ed89878 100644
--- a/Framework/Inputs/DecodedTiledPyramid.h
+++ b/Framework/Inputs/DecodedTiledPyramid.h
@@ -59,12 +59,8 @@ namespace OrthancWSI
                                                unsigned int tileX,
                                                unsigned int tileY);
 
-    virtual ImageCompression GetImageCompression() const
-    {
-      return ImageCompression_None;
-    }
-
     virtual bool ReadRawTile(std::string& tile,
+                             ImageCompression& compression,
                              unsigned int level,
                              unsigned int tileX,
                              unsigned int tileY)
diff --git a/Framework/Inputs/DicomPyramid.cpp b/Framework/Inputs/DicomPyramid.cpp
index b3f04e0..c73e015 100644
--- a/Framework/Inputs/DicomPyramid.cpp
+++ b/Framework/Inputs/DicomPyramid.cpp
@@ -22,8 +22,8 @@
 #include "DicomPyramid.h"
 
 #include "../DicomToolbox.h"
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 #include <algorithm>
 #include <cassert>
@@ -60,17 +60,20 @@ namespace OrthancWSI
   }
 
 
-  void DicomPyramid::RegisterInstances(const std::string& seriesId)
+  void DicomPyramid::RegisterInstances(const std::string& seriesId,
+                                       bool useCache)
   {
     Json::Value series;
-    IOrthancConnection::RestApiGet(series, orthanc_, "/series/" + seriesId);
+    OrthancPlugins::IOrthancConnection::RestApiGet(series, orthanc_, "/series/" + seriesId);
 
-    if (series.type() != Json::objectValue)
+    if (series.type() != Json::objectValue ||
+        !series.isMember("Instances") ||
+        series["Instances"].type() != Json::arrayValue)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
     }
 
-    const Json::Value& instances = DicomToolbox::GetSequenceTag(series, "Instances");
+    const Json::Value& instances = series["Instances"];
     instances_.reserve(instances.size());
 
     for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
@@ -84,7 +87,7 @@ namespace OrthancWSI
 
       try
       {
-        instances_.push_back(new DicomPyramidInstance(orthanc_, instance));
+        instances_.push_back(new DicomPyramidInstance(orthanc_, instance, useCache));
       }
       catch (Orthanc::OrthancException&)
       {
@@ -108,8 +111,7 @@ namespace OrthancWSI
     {
       const DicomPyramidInstance& b = *instances_[i];
 
-      if (a.GetImageCompression() != b.GetImageCompression() ||
-          a.GetPixelFormat() != b.GetPixelFormat() ||
+      if (a.GetPixelFormat() != b.GetPixelFormat() ||
           a.GetTileWidth() != b.GetTileWidth() ||
           a.GetTileHeight() != b.GetTileHeight() ||
           a.GetTotalWidth() < b.GetTotalWidth() ||
@@ -136,12 +138,13 @@ namespace OrthancWSI
   }
 
 
-  DicomPyramid::DicomPyramid(IOrthancConnection& orthanc,
-                             const std::string& seriesId) :
+  DicomPyramid::DicomPyramid(OrthancPlugins::IOrthancConnection& orthanc,
+                             const std::string& seriesId,
+                             bool useCache) :
     orthanc_(orthanc),
     seriesId_(seriesId)
   {
-    RegisterInstances(seriesId);
+    RegisterInstances(seriesId, useCache);
 
     // Sort the instances of the pyramid by decreasing total widths
     std::sort(instances_.begin(), instances_.end(), Comparator());
@@ -201,19 +204,22 @@ namespace OrthancWSI
 
 
   bool DicomPyramid::ReadRawTile(std::string& tile,
+                                 ImageCompression& compression,
                                  unsigned int level,
                                  unsigned int tileX,
                                  unsigned int tileY)
   {
     CheckLevel(level);
       
-    ImageCompression compression;
     Orthanc::PixelFormat format;
       
-    if (levels_[level]->DownloadRawTile(compression, format, tile, orthanc_, tileX, tileY))
+    if (levels_[level]->DownloadRawTile(tile, format, compression, orthanc_, tileX, tileY))
     {
-      assert(compression == GetImageCompression() &&
-             format == GetPixelFormat());
+      if (format != GetPixelFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
       return true;
     }
     else
@@ -223,13 +229,6 @@ namespace OrthancWSI
   }
 
 
-  ImageCompression DicomPyramid::GetImageCompression() const
-  {
-    assert(!instances_.empty() && instances_[0] != NULL);
-    return instances_[0]->GetImageCompression();
-  }
-
-
   Orthanc::PixelFormat DicomPyramid::GetPixelFormat() const
   {
     assert(!instances_.empty() && instances_[0] != NULL);
diff --git a/Framework/Inputs/DicomPyramid.h b/Framework/Inputs/DicomPyramid.h
index 0232a0c..0925e09 100644
--- a/Framework/Inputs/DicomPyramid.h
+++ b/Framework/Inputs/DicomPyramid.h
@@ -31,22 +31,24 @@ namespace OrthancWSI
   private:
     struct Comparator;
 
-    IOrthancConnection&                 orthanc_;
-    std::string                         seriesId_;
-    std::vector<DicomPyramidInstance*>  instances_;
-    std::vector<DicomPyramidLevel*>     levels_;
+    OrthancPlugins::IOrthancConnection&  orthanc_;
+    std::string                          seriesId_;
+    std::vector<DicomPyramidInstance*>   instances_;
+    std::vector<DicomPyramidLevel*>      levels_;
 
     void Clear();
 
-    void RegisterInstances(const std::string& seriesId);
+    void RegisterInstances(const std::string& seriesId,
+                           bool useCache);
 
     void Check(const std::string& seriesId) const;
 
     void CheckLevel(size_t level) const;
 
   public:
-    DicomPyramid(IOrthancConnection& orthanc,
-                 const std::string& seriesId);
+    DicomPyramid(OrthancPlugins::IOrthancConnection& orthanc,
+                 const std::string& seriesId,
+                 bool useCache);
 
     virtual ~DicomPyramid()
     {
@@ -72,12 +74,11 @@ namespace OrthancWSI
     virtual unsigned int GetTileHeight() const;
 
     virtual bool ReadRawTile(std::string& tile,
+                             ImageCompression& compression,
                              unsigned int level,
                              unsigned int tileX,
                              unsigned int tileY);
 
-    virtual ImageCompression GetImageCompression() const;
-
     virtual Orthanc::PixelFormat GetPixelFormat() const;
   };
 }
diff --git a/Framework/Inputs/DicomPyramidInstance.cpp b/Framework/Inputs/DicomPyramidInstance.cpp
index 2ca7665..daf48cb 100644
--- a/Framework/Inputs/DicomPyramidInstance.cpp
+++ b/Framework/Inputs/DicomPyramidInstance.cpp
@@ -21,19 +21,30 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "DicomPyramidInstance.h"
 
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Toolbox.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Toolbox.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
 #include "../DicomToolbox.h"
 
 #include <cassert>
+#include <json/writer.h>
+
+#define SERIALIZED_METADATA  "4200"
 
 namespace OrthancWSI
 {
-  static ImageCompression DetectImageCompression(const Json::Value& header)
+  static ImageCompression DetectImageCompression(OrthancPlugins::IOrthancConnection& orthanc,
+                                                 const std::string& instanceId)
   {
+    using namespace OrthancPlugins;
+
+    DicomDatasetReader header(new FullOrthancDataset
+                              (orthanc, "/instances/" + instanceId + "/header"));
+
     std::string s = Orthanc::Toolbox::StripSpaces
-      (DicomToolbox::GetMandatoryStringTag(header, "TransferSyntaxUID"));
+      (header.GetMandatoryStringValue(DICOM_TAG_TRANSFER_SYNTAX_UID));
 
     if (s == "1.2.840.10008.1.2" ||
         s == "1.2.840.10008.1.2.1")
@@ -57,10 +68,12 @@ namespace OrthancWSI
   }
 
 
-  static Orthanc::PixelFormat DetectPixelFormat(const Json::Value& dicom)
+  static Orthanc::PixelFormat DetectPixelFormat(OrthancPlugins::DicomDatasetReader& reader)
   {
+    using namespace OrthancPlugins;
+
     std::string photometric = Orthanc::Toolbox::StripSpaces
-      (DicomToolbox::GetMandatoryStringTag(dicom, "PhotometricInterpretation"));
+      (reader.GetMandatoryStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION));
 
     if (photometric == "PALETTE")
     {
@@ -68,9 +81,9 @@ namespace OrthancWSI
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
 
-    unsigned int bitsStored = DicomToolbox::GetUnsignedIntegerTag(dicom, "BitsStored");
-    unsigned int samplesPerPixel = DicomToolbox::GetUnsignedIntegerTag(dicom, "SamplesPerPixel");
-    bool isSigned = (DicomToolbox::GetUnsignedIntegerTag(dicom, "PixelRepresentation") != 0);
+    unsigned int bitsStored = reader.GetUnsignedIntegerValue(DICOM_TAG_BITS_STORED);
+    unsigned int samplesPerPixel = reader.GetUnsignedIntegerValue(DICOM_TAG_SAMPLES_PER_PIXEL);
+    bool isSigned = (reader.GetUnsignedIntegerValue(DICOM_TAG_PIXEL_REPRESENTATION) != 0);
 
     if (bitsStored == 8 &&
         samplesPerPixel == 1 &&
@@ -91,49 +104,75 @@ namespace OrthancWSI
     }
   }
 
+
+  ImageCompression  DicomPyramidInstance::GetImageCompression(OrthancPlugins::IOrthancConnection& orthanc)
+  {
+    /**
+     * Lazy detection of the image compression using the transfer
+     * syntax stored inside the DICOM header. Given the fact that
+     * reading the header is a time-consuming operation (it implies
+     * the decoding of the DICOM image by Orthanc, whereas the "/tags"
+     * endpoint only reads the "DICOM-as-JSON" attachment), the
+     * "/header" REST call is delayed until it is really required.
+     **/
+
+    if (!hasCompression_)
+    {
+      compression_ = DetectImageCompression(orthanc, instanceId_);
+      hasCompression_ = true;
+    }
+
+    return compression_;
+  }
+
   
-  DicomPyramidInstance::DicomPyramidInstance(IOrthancConnection&  orthanc,
-                                             const std::string& instanceId) :
-    instanceId_(instanceId)
+  void DicomPyramidInstance::Load(OrthancPlugins::IOrthancConnection&  orthanc,
+                                  const std::string& instanceId)
   {
-    Json::Value dicom, header;
-    IOrthancConnection::RestApiGet(dicom, orthanc, "/instances/" + instanceId + "/tags?simplify");
-    IOrthancConnection::RestApiGet(header, orthanc, "/instances/" + instanceId + "/header?simplify");
+    using namespace OrthancPlugins;
 
-    if (DicomToolbox::GetMandatoryStringTag(dicom, "SOPClassUID") != "1.2.840.10008.5.1.4.1.1.77.1.6" ||
-        DicomToolbox::GetMandatoryStringTag(dicom, "Modality") != "SM")
+    DicomDatasetReader reader(new FullOrthancDataset(orthanc, "/instances/" + instanceId + "/tags"));
+
+    if (reader.GetMandatoryStringValue(DICOM_TAG_SOP_CLASS_UID) != "1.2.840.10008.5.1.4.1.1.77.1.6" ||
+        reader.GetMandatoryStringValue(DICOM_TAG_MODALITY) != "SM")
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    compression_ = DetectImageCompression(header);
-    format_ = DetectPixelFormat(dicom);
-    tileWidth_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "Columns");
-    tileHeight_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "Rows");
-    totalWidth_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "TotalPixelMatrixColumns");
-    totalHeight_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "TotalPixelMatrixRows");
+    hasCompression_ = false;
+    format_ = DetectPixelFormat(reader);
+    tileWidth_ = reader.GetUnsignedIntegerValue(DICOM_TAG_COLUMNS);
+    tileHeight_ = reader.GetUnsignedIntegerValue(DICOM_TAG_ROWS);
+    totalWidth_ = reader.GetUnsignedIntegerValue(DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS);
+    totalHeight_ = reader.GetUnsignedIntegerValue(DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS);
 
-    const Json::Value& frames = DicomToolbox::GetSequenceTag(dicom, "PerFrameFunctionalGroupsSequence");
+    size_t countFrames;
+    if (!reader.GetDataset().GetSequenceSize(countFrames, DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
 
-    if (frames.size() != DicomToolbox::GetUnsignedIntegerTag(dicom, "NumberOfFrames"))
+    if (countFrames != reader.GetUnsignedIntegerValue(DICOM_TAG_NUMBER_OF_FRAMES))
     {
       LOG(ERROR) << "Mismatch between the number of frames in instance: " << instanceId;
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
-    frames_.resize(frames.size());
+    frames_.resize(countFrames);
 
-    for (Json::Value::ArrayIndex i = 0; i < frames.size(); i++)
+    for (size_t i = 0; i < countFrames; i++)
     {
-      const Json::Value& frame = DicomToolbox::GetSequenceTag(frames[i], "PlanePositionSlideSequence");
-      if (frame.size() != 1)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
+      int xx = reader.GetIntegerValue(DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
+                                                DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE, 0,
+                                                DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX));
+
+      int yy = reader.GetIntegerValue(DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
+                                                DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE, 0,
+                                                DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX));
 
       // "-1", because coordinates are shifted by 1 in DICOM
-      int xx = DicomToolbox::GetIntegerTag(frame[0], "ColumnPositionInTotalImagePixelMatrix") - 1;
-      int yy = DicomToolbox::GetIntegerTag(frame[0], "RowPositionInTotalImagePixelMatrix") - 1;
+      xx -= 1;
+      yy -= 1;
 
       unsigned int x = static_cast<unsigned int>(xx);
       unsigned int y = static_cast<unsigned int>(yy);
@@ -156,6 +195,41 @@ namespace OrthancWSI
   }
 
 
+  DicomPyramidInstance::DicomPyramidInstance(OrthancPlugins::IOrthancConnection&  orthanc,
+                                             const std::string& instanceId,
+                                             bool useCache) :
+    instanceId_(instanceId),
+    hasCompression_(false)
+  {
+    if (useCache)
+    {
+      try
+      {
+        // Try and deserialized the cached information about this instance
+        std::string serialized;
+        orthanc.RestApiGet(serialized, "/instances/" + instanceId + "/metadata/" + SERIALIZED_METADATA);
+        Deserialize(serialized);
+        return;  // Success
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        // No cached information yet
+      }
+    }
+
+    // Compute information about this instance from scratch
+    Load(orthanc, instanceId);
+
+    if (useCache)
+    {
+      // Serialize the computed information and cache it as a metadata
+      std::string serialized, tmp;
+      Serialize(serialized);
+      orthanc.RestApiPut(tmp, "/instances/" + instanceId + "/metadata/" + SERIALIZED_METADATA, serialized);
+    }
+  }
+
+
   unsigned int DicomPyramidInstance::GetFrameLocationX(size_t frame) const
   {
     assert(frame < frames_.size());
@@ -168,4 +242,111 @@ namespace OrthancWSI
     assert(frame < frames_.size());
     return frames_[frame].second;
   }
+
+  
+  void DicomPyramidInstance::Serialize(std::string& result) const
+  {
+    Json::Value frames = Json::arrayValue;
+    for (size_t i = 0; i < frames_.size(); i++)
+    {
+      Json::Value frame = Json::arrayValue;
+      frame.append(frames_[i].first);
+      frame.append(frames_[i].second);
+
+      frames.append(frame);
+    }
+
+    Json::Value content;
+    content["Frames"] = frames;
+    content["TileHeight"] = tileHeight_;
+    content["TileWidth"] = tileWidth_;
+    content["TotalHeight"] = totalHeight_;
+    content["TotalWidth"] = totalWidth_;    
+
+    switch (format_)
+    {
+      case Orthanc::PixelFormat_RGB24:
+        content["PixelFormat"] = 0;
+        break;
+
+      case Orthanc::PixelFormat_Grayscale8:
+        content["PixelFormat"] = 1;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    Json::FastWriter writer;
+    result = writer.write(content);
+  }
+
+
+  void DicomPyramidInstance::Deserialize(const std::string& s)
+  {
+    hasCompression_ = false;
+
+    Json::Value content;
+    OrthancPlugins::IOrthancConnection::ParseJson(content, s);
+
+    if (content.type() != Json::objectValue ||
+        !content.isMember("Frames") ||
+        !content.isMember("PixelFormat") ||
+        !content.isMember("TileHeight") ||
+        !content.isMember("TileWidth") ||
+        !content.isMember("TotalHeight") ||
+        !content.isMember("TotalWidth") ||
+        content["Frames"].type() != Json::arrayValue ||
+        content["PixelFormat"].type() != Json::intValue ||
+        content["TileHeight"].type() != Json::intValue ||
+        content["TileWidth"].type() != Json::intValue ||
+        content["TotalHeight"].type() != Json::intValue ||
+        content["TotalWidth"].type() != Json::intValue ||
+        content["TileHeight"].asInt() < 0 ||
+        content["TileWidth"].asInt() < 0 ||
+        content["TotalHeight"].asInt() < 0 ||
+        content["TotalWidth"].asInt() < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    switch (content["PixelFormat"].asInt())
+    {
+      case 0:
+        format_ = Orthanc::PixelFormat_RGB24;
+        break;
+
+      case 1:
+        format_ = Orthanc::PixelFormat_Grayscale8;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    hasCompression_ = false;
+    tileHeight_ = static_cast<unsigned int>(content["TileHeight"].asInt());
+    tileWidth_ = static_cast<unsigned int>(content["TileWidth"].asInt());
+    totalHeight_ = static_cast<unsigned int>(content["TotalHeight"].asInt());
+    totalWidth_ = static_cast<unsigned int>(content["TotalWidth"].asInt());
+
+    const Json::Value f = content["Frames"];
+    frames_.resize(f.size());
+
+    for (Json::Value::ArrayIndex i = 0; i < f.size(); i++)
+    {
+      if (f[i].type() != Json::arrayValue ||
+          f[i].size() != 2 ||
+          f[i][0].type() != Json::intValue ||
+          f[i][1].type() != Json::intValue ||
+          f[i][0].asInt() < 0 ||
+          f[i][1].asInt() < 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      frames_[i].first = f[i][0].asInt();
+      frames_[i].second = f[i][1].asInt();
+    }
+  }
 }
diff --git a/Framework/Inputs/DicomPyramidInstance.h b/Framework/Inputs/DicomPyramidInstance.h
index 8a5657a..8aced5a 100644
--- a/Framework/Inputs/DicomPyramidInstance.h
+++ b/Framework/Inputs/DicomPyramidInstance.h
@@ -21,7 +21,7 @@
 #pragma once
 
 #include "../Enumerations.h"
-#include "../Messaging/IOrthancConnection.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
 
 #include <boost/noncopyable.hpp>
 #include <vector>
@@ -34,6 +34,7 @@ namespace OrthancWSI
     typedef std::pair<unsigned int, unsigned int>  FrameLocation;
 
     std::string                 instanceId_;
+    bool                        hasCompression_;
     ImageCompression            compression_;
     Orthanc::PixelFormat        format_;
     unsigned int                tileWidth_;
@@ -42,19 +43,22 @@ namespace OrthancWSI
     unsigned int                totalHeight_;
     std::vector<FrameLocation>  frames_;
 
+    void Load(OrthancPlugins::IOrthancConnection&  orthanc,
+              const std::string& instanceId);
+
+    void Deserialize(const std::string& content);
+
   public:
-    DicomPyramidInstance(IOrthancConnection&  orthanc,
-                         const std::string& instanceId);
+    DicomPyramidInstance(OrthancPlugins::IOrthancConnection&  orthanc,
+                         const std::string& instanceId,
+                         bool useCache);
 
     const std::string& GetInstanceId() const
     {
       return instanceId_;
     }
 
-    ImageCompression GetImageCompression() const
-    {
-      return compression_;
-    }
+    ImageCompression GetImageCompression(OrthancPlugins::IOrthancConnection& orthanc);
 
     Orthanc::PixelFormat GetPixelFormat() const
     {
@@ -89,5 +93,7 @@ namespace OrthancWSI
     unsigned int GetFrameLocationX(size_t frame) const;
 
     unsigned int GetFrameLocationY(size_t frame) const;
+
+    void Serialize(std::string& result) const;
   };
 }
diff --git a/Framework/Inputs/DicomPyramidLevel.cpp b/Framework/Inputs/DicomPyramidLevel.cpp
index 1626755..f76e7e8 100644
--- a/Framework/Inputs/DicomPyramidLevel.cpp
+++ b/Framework/Inputs/DicomPyramidLevel.cpp
@@ -21,28 +21,43 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "DicomPyramidLevel.h"
 
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 #include <boost/lexical_cast.hpp>
 
 namespace OrthancWSI
 {
-  void DicomPyramidLevel::RegisterFrame(const DicomPyramidInstance& instance,
+  DicomPyramidLevel::TileContent& DicomPyramidLevel::GetTileContent(unsigned int tileX,
+                                                                    unsigned int tileY)
+  {
+    if (tileX >= countTilesX_ ||
+        tileY >= countTilesY_)
+    {
+      LOG(ERROR) << "Tile location (" << tileX << "," << tileY << ") is outside the image";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    return tiles_[tileY * countTilesX_ + tileX];
+  }
+
+  void DicomPyramidLevel::RegisterFrame(DicomPyramidInstance& instance,
                                         unsigned int frame)
   {
-    TileLocation location(instance.GetFrameLocationX(frame), 
-                          instance.GetFrameLocationY(frame));
+    unsigned int tileX = instance.GetFrameLocationX(frame);
+    unsigned int tileY = instance.GetFrameLocationY(frame);
+    TileContent& tile = GetTileContent(tileX, tileY);
 
-    if (tiles_.find(location) != tiles_.end())
+    if (tile.instance_ != NULL)
     {
-      LOG(ERROR) << "Tile with location (" << location.first << "," 
-                 << location.second << ") is indexed twice in level of size "
+      LOG(ERROR) << "Tile with location (" << tileX << "," 
+                 << tileY << ") is indexed twice in level of size "
                  << totalWidth_ << "x" << totalHeight_;
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
-    tiles_[location] = std::make_pair(&instance, frame);
+    tile.instance_ = &instance;
+    tile.frame_ = frame;
   }
 
 
@@ -50,20 +65,21 @@ namespace OrthancWSI
                                      unsigned int tileX,
                                      unsigned int tileY) const
   {
-    Tiles::const_iterator found = tiles_.find(std::make_pair(tileX, tileY));
-    if (found == tiles_.end())
+    const TileContent& tmp = const_cast<DicomPyramidLevel&>(*this).GetTileContent(tileX, tileY);
+
+    if (tmp.instance_ == NULL)
     {
       return false;
     }
     else
     {
-      tile = found->second;
+      tile = tmp;
       return true;
     }
   }
 
 
-  DicomPyramidLevel::DicomPyramidLevel(const DicomPyramidInstance& instance) :
+  DicomPyramidLevel::DicomPyramidLevel(DicomPyramidInstance& instance) :
     totalWidth_(instance.GetTotalWidth()),
     totalHeight_(instance.GetTotalHeight()),
     tileWidth_(instance.GetTileWidth()),
@@ -74,12 +90,16 @@ namespace OrthancWSI
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
+
+    countTilesX_ = CeilingDivision(totalWidth_, tileWidth_);
+    countTilesY_ = CeilingDivision(totalHeight_, tileHeight_);
+    tiles_.resize(countTilesX_ * countTilesY_);
       
     AddInstance(instance);
   }
 
 
-  void DicomPyramidLevel::AddInstance(const DicomPyramidInstance& instance)
+  void DicomPyramidLevel::AddInstance(DicomPyramidInstance& instance)
   {
     if (instance.GetTotalWidth() != totalWidth_ ||
         instance.GetTotalHeight() != totalHeight_ ||
@@ -89,8 +109,6 @@ namespace OrthancWSI
       throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
     }
 
-    instances_.push_back(&instance);
-
     for (size_t frame = 0; frame < instance.GetFrameCount(); frame++)
     {
       RegisterFrame(instance, frame);
@@ -98,25 +116,25 @@ namespace OrthancWSI
   }
 
 
-  bool DicomPyramidLevel::DownloadRawTile(ImageCompression& compression /* out */,
+  bool DicomPyramidLevel::DownloadRawTile(std::string& raw /* out */,
                                           Orthanc::PixelFormat& format /* out */,
-                                          std::string& raw /* out */,
-                                          IOrthancConnection& orthanc,
+                                          ImageCompression& compression /* out */,
+                                          OrthancPlugins::IOrthancConnection& orthanc,
                                           unsigned int tileX,
                                           unsigned int tileY) const
   {
     TileContent tile;
     if (LookupTile(tile, tileX, tileY))
     {
-      assert(tile.first != NULL);
-      const DicomPyramidInstance& instance = *tile.first;
+      assert(tile.instance_ != NULL);
+      DicomPyramidInstance& instance = *tile.instance_;
 
       std::string uri = ("/instances/" + instance.GetInstanceId() + 
-                         "/frames/" + boost::lexical_cast<std::string>(tile.second) + "/raw");
+                         "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/raw");
 
       orthanc.RestApiGet(raw, uri);
 
-      compression = instance.GetImageCompression();
+      compression = instance.GetImageCompression(orthanc);
       format = instance.GetPixelFormat();
 
       return true;
diff --git a/Framework/Inputs/DicomPyramidLevel.h b/Framework/Inputs/DicomPyramidLevel.h
index 62d79d3..a64796f 100644
--- a/Framework/Inputs/DicomPyramidLevel.h
+++ b/Framework/Inputs/DicomPyramidLevel.h
@@ -29,19 +29,30 @@ namespace OrthancWSI
   class DicomPyramidLevel : public boost::noncopyable
   {
   private:
-    typedef std::pair<unsigned int, unsigned int>                 TileLocation;
-    typedef std::pair<const DicomPyramidInstance*, unsigned int>  TileContent;
-    typedef std::map<TileLocation, TileContent>                   Tiles;
-    typedef std::list<const DicomPyramidInstance*>                Instances;
-
-    unsigned int   totalWidth_;
-    unsigned int   totalHeight_;
-    unsigned int   tileWidth_;
-    unsigned int   tileHeight_;
-    Instances      instances_;
-    Tiles          tiles_;
-
-    void RegisterFrame(const DicomPyramidInstance& instance,
+    struct TileContent
+    {
+      DicomPyramidInstance*  instance_;
+      unsigned int           frame_;
+
+      TileContent() : 
+        instance_(NULL),
+        frame_(0)
+      {
+      }
+    };
+
+    unsigned int             totalWidth_;
+    unsigned int             totalHeight_;
+    unsigned int             tileWidth_;
+    unsigned int             tileHeight_;
+    unsigned int             countTilesX_;
+    unsigned int             countTilesY_;
+    std::vector<TileContent> tiles_;
+
+    TileContent& GetTileContent(unsigned int tileX,
+                                unsigned int tileY);
+
+    void RegisterFrame(DicomPyramidInstance& instance,
                        unsigned int frame);
 
     bool LookupTile(TileContent& tile,
@@ -49,9 +60,9 @@ namespace OrthancWSI
                     unsigned int tileY) const;
 
   public:
-    DicomPyramidLevel(const DicomPyramidInstance& instance);
+    DicomPyramidLevel(DicomPyramidInstance& instance);
 
-    void AddInstance(const DicomPyramidInstance& instance);
+    void AddInstance(DicomPyramidInstance& instance);
 
     unsigned int GetTotalWidth() const
     {
@@ -73,10 +84,10 @@ namespace OrthancWSI
       return tileHeight_;
     }
 
-    bool DownloadRawTile(ImageCompression& compression /* out */,
+    bool DownloadRawTile(std::string& raw /* out */,
                          Orthanc::PixelFormat& format /* out */,
-                         std::string& raw /* out */,
-                         IOrthancConnection& orthanc,
+                         ImageCompression& compression /* out */,
+                         OrthancPlugins::IOrthancConnection& orthanc,
                          unsigned int tileX,
                          unsigned int tileY) const;
   };
diff --git a/Framework/Inputs/HierarchicalTiff.cpp b/Framework/Inputs/HierarchicalTiff.cpp
index edc4919..a8505ec 100644
--- a/Framework/Inputs/HierarchicalTiff.cpp
+++ b/Framework/Inputs/HierarchicalTiff.cpp
@@ -21,8 +21,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "HierarchicalTiff.h"
 
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 #include <iostream>
 #include <algorithm>
@@ -245,6 +245,7 @@ namespace OrthancWSI
 
 
   bool HierarchicalTiff::ReadRawTile(std::string& tile,
+                                     ImageCompression& compression,
                                      unsigned int level,
                                      unsigned int tileX,
                                      unsigned int tileY)
@@ -253,6 +254,8 @@ namespace OrthancWSI
 
     CheckLevel(level);
 
+    compression = compression_;
+
     // Make the TIFF context point to the level of interest
     if (!TIFFSetDirectory(tiff_, levels_[level].directory_))
     {
diff --git a/Framework/Inputs/HierarchicalTiff.h b/Framework/Inputs/HierarchicalTiff.h
index 6d3b6b8..3cf8ef0 100644
--- a/Framework/Inputs/HierarchicalTiff.h
+++ b/Framework/Inputs/HierarchicalTiff.h
@@ -94,18 +94,19 @@ namespace OrthancWSI
     }
 
     virtual bool ReadRawTile(std::string& tile,
+                             ImageCompression& compression,
                              unsigned int level,
                              unsigned int tileX,
                              unsigned int tileY);
 
-    virtual ImageCompression GetImageCompression() const
+    virtual Orthanc::PixelFormat GetPixelFormat() const
     {
-      return compression_;
+      return pixelFormat_;
     }
 
-    virtual Orthanc::PixelFormat GetPixelFormat() const
+    ImageCompression GetImageCompression()
     {
-      return pixelFormat_;
+      return compression_;
     }
   };
 }
diff --git a/Framework/Inputs/ITiledPyramid.h b/Framework/Inputs/ITiledPyramid.h
index 7efa495..42b277c 100644
--- a/Framework/Inputs/ITiledPyramid.h
+++ b/Framework/Inputs/ITiledPyramid.h
@@ -22,7 +22,7 @@
 
 #include "../Enumerations.h"
 
-#include "../Orthanc/Core/Images/ImageAccessor.h"
+#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
 
 #include <boost/noncopyable.hpp>
 #include <string>
@@ -52,6 +52,7 @@ namespace OrthancWSI
     virtual unsigned int GetTileHeight() const = 0;
 
     virtual bool ReadRawTile(std::string& tile,
+                             ImageCompression& compression,
                              unsigned int level,
                              unsigned int tileX,
                              unsigned int tileY) = 0;
@@ -60,9 +61,6 @@ namespace OrthancWSI
                                                unsigned int tileX,
                                                unsigned int tileY) = 0;
 
-    // Only makes sense for images with raw access to tiles
-    virtual ImageCompression GetImageCompression() const = 0;
-
     virtual Orthanc::PixelFormat GetPixelFormat() const = 0;
   };
 }
diff --git a/Framework/Inputs/OpenSlideLibrary.cpp b/Framework/Inputs/OpenSlideLibrary.cpp
index b741f6c..babdfa9 100644
--- a/Framework/Inputs/OpenSlideLibrary.cpp
+++ b/Framework/Inputs/OpenSlideLibrary.cpp
@@ -21,8 +21,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "OpenSlideLibrary.h"
 
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/Images/Image.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/Images/Image.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Inputs/OpenSlideLibrary.h b/Framework/Inputs/OpenSlideLibrary.h
index ac77998..f48eba9 100644
--- a/Framework/Inputs/OpenSlideLibrary.h
+++ b/Framework/Inputs/OpenSlideLibrary.h
@@ -20,8 +20,8 @@
 
 #pragma once
 
-#include "../Orthanc/Plugins/Engine/SharedLibrary.h"
-#include "../Orthanc/Core/Images/ImageAccessor.h"
+#include "../../Resources/Orthanc/Plugins/Engine/SharedLibrary.h"
+#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
 
 #include <vector>
 
diff --git a/Framework/Inputs/OpenSlidePyramid.cpp b/Framework/Inputs/OpenSlidePyramid.cpp
index c816044..a9c95fa 100644
--- a/Framework/Inputs/OpenSlidePyramid.cpp
+++ b/Framework/Inputs/OpenSlidePyramid.cpp
@@ -21,9 +21,9 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "OpenSlidePyramid.h"
 
-#include "../Orthanc/Core/Images/ImageProcessing.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Inputs/PyramidWithRawTiles.cpp b/Framework/Inputs/PyramidWithRawTiles.cpp
index 5342491..90a9104 100644
--- a/Framework/Inputs/PyramidWithRawTiles.cpp
+++ b/Framework/Inputs/PyramidWithRawTiles.cpp
@@ -21,9 +21,9 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "PyramidWithRawTiles.h"
 
-#include "../Orthanc/Core/Images/PngReader.h"
-#include "../Orthanc/Core/Images/JpegReader.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Images/PngReader.h"
+#include "../../Resources/Orthanc/Core/Images/JpegReader.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 #include "../Jpeg2000Reader.h"
 
 namespace OrthancWSI
@@ -33,14 +33,16 @@ namespace OrthancWSI
                                                           unsigned int tileY)
   {
     std::string tile;
-    if (!ReadRawTile(tile, level, tileX, tileY))
+    ImageCompression compression;
+
+    if (!ReadRawTile(tile, compression, level, tileX, tileY))
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      return NULL;
     }
 
     std::auto_ptr<Orthanc::ImageAccessor> result;
 
-    switch (GetImageCompression())
+    switch (compression)
     {
       case ImageCompression_None:
         result.reset(new Orthanc::ImageAccessor);
diff --git a/Framework/Inputs/SingleLevelDecodedPyramid.cpp b/Framework/Inputs/SingleLevelDecodedPyramid.cpp
index 8bb55eb..6532198 100644
--- a/Framework/Inputs/SingleLevelDecodedPyramid.cpp
+++ b/Framework/Inputs/SingleLevelDecodedPyramid.cpp
@@ -22,7 +22,7 @@
 #include "SingleLevelDecodedPyramid.h"
 #include "../ImageToolbox.h"
 
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Inputs/TiledJpegImage.h b/Framework/Inputs/TiledJpegImage.h
index 4414d8d..c3585ad 100644
--- a/Framework/Inputs/TiledJpegImage.h
+++ b/Framework/Inputs/TiledJpegImage.h
@@ -22,7 +22,7 @@
 
 #include "SingleLevelDecodedPyramid.h"
 
-#include "../Orthanc/Core/Images/JpegReader.h"
+#include "../../Resources/Orthanc/Core/Images/JpegReader.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Inputs/TiledPngImage.h b/Framework/Inputs/TiledPngImage.h
index c8d1551..3d90678 100644
--- a/Framework/Inputs/TiledPngImage.h
+++ b/Framework/Inputs/TiledPngImage.h
@@ -22,7 +22,7 @@
 
 #include "SingleLevelDecodedPyramid.h"
 
-#include "../Orthanc/Core/Images/PngReader.h"
+#include "../../Resources/Orthanc/Core/Images/PngReader.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Inputs/TiledPyramidStatistics.cpp b/Framework/Inputs/TiledPyramidStatistics.cpp
index 405506f..da2fa29 100644
--- a/Framework/Inputs/TiledPyramidStatistics.cpp
+++ b/Framework/Inputs/TiledPyramidStatistics.cpp
@@ -21,7 +21,7 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "TiledPyramidStatistics.h"
 
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
 
 
 namespace OrthancWSI
@@ -43,11 +43,12 @@ namespace OrthancWSI
 
 
   bool TiledPyramidStatistics::ReadRawTile(std::string& tile,
+                                           ImageCompression& compression,
                                            unsigned int level,
                                            unsigned int tileX,
                                            unsigned int tileY)
   {
-    if (source_.ReadRawTile(tile, level, tileX, tileY))
+    if (source_.ReadRawTile(tile, compression, level, tileX, tileY))
     {
       boost::mutex::scoped_lock lock(mutex_);
       countRawAccesses_++;
diff --git a/Framework/Inputs/TiledPyramidStatistics.h b/Framework/Inputs/TiledPyramidStatistics.h
index 7b5917d..bdecde6 100644
--- a/Framework/Inputs/TiledPyramidStatistics.h
+++ b/Framework/Inputs/TiledPyramidStatistics.h
@@ -30,7 +30,7 @@ namespace OrthancWSI
   {
   private:
     boost::mutex   mutex_;
-    ITiledPyramid& source_;
+    ITiledPyramid& source_;  // This is a facade design pattern
     unsigned int   countRawAccesses_;
     unsigned int   countDecodedTiles_;
 
@@ -64,17 +64,13 @@ namespace OrthancWSI
       return source_.GetTileHeight();
     }
 
-    virtual ImageCompression GetImageCompression() const
-    {
-      return source_.GetImageCompression();
-    }
-
     virtual Orthanc::PixelFormat GetPixelFormat() const
     {
       return source_.GetPixelFormat();
     }
 
     virtual bool ReadRawTile(std::string& tile,
+                             ImageCompression& compression,
                              unsigned int level,
                              unsigned int tileX,
                              unsigned int tileY);
diff --git a/Framework/Jpeg2000Reader.cpp b/Framework/Jpeg2000Reader.cpp
index 41637e2..38579bd 100644
--- a/Framework/Jpeg2000Reader.cpp
+++ b/Framework/Jpeg2000Reader.cpp
@@ -21,8 +21,8 @@
 #include "PrecompiledHeadersWSI.h"
 #include "Jpeg2000Reader.h"
 
-#include "Orthanc/Core/OrthancException.h"
-#include "Orthanc/Core/Toolbox.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/SystemToolbox.h"
 #include "ImageToolbox.h"
 
 #include <cassert>
@@ -460,7 +460,7 @@ namespace OrthancWSI
     // TODO Use opj_stream_create_file_stream() ?
 
     std::string content;
-    Orthanc::Toolbox::ReadFile(content, filename);
+    Orthanc::SystemToolbox::ReadFile(content, filename);
   }
 
 
diff --git a/Framework/Jpeg2000Reader.h b/Framework/Jpeg2000Reader.h
index dd59358..beba04f 100644
--- a/Framework/Jpeg2000Reader.h
+++ b/Framework/Jpeg2000Reader.h
@@ -20,7 +20,7 @@
 
 #pragma once
 
-#include "Orthanc/Core/Images/Image.h"
+#include "../Resources/Orthanc/Core/Images/Image.h"
 #include <memory>
 
 namespace OrthancWSI
diff --git a/Framework/Jpeg2000Writer.cpp b/Framework/Jpeg2000Writer.cpp
index 2b0a977..0757efc 100644
--- a/Framework/Jpeg2000Writer.cpp
+++ b/Framework/Jpeg2000Writer.cpp
@@ -21,8 +21,8 @@
 #include "PrecompiledHeadersWSI.h"
 #include "Jpeg2000Writer.h"
 
-#include "Orthanc/Core/ChunkedBuffer.h"
-#include "Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/ChunkedBuffer.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
 
 #include <openjpeg.h>
 #include <string.h>
diff --git a/Framework/Jpeg2000Writer.h b/Framework/Jpeg2000Writer.h
index 268a9b3..315f66c 100644
--- a/Framework/Jpeg2000Writer.h
+++ b/Framework/Jpeg2000Writer.h
@@ -20,7 +20,7 @@
 
 #pragma once
 
-#include "Orthanc/Core/Images/IImageWriter.h"
+#include "../Resources/Orthanc/Core/Images/IImageWriter.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Messaging/CurlOrthancConnection.cpp b/Framework/Messaging/CurlOrthancConnection.cpp
deleted file mode 100644
index 55c669f..0000000
--- a/Framework/Messaging/CurlOrthancConnection.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "../PrecompiledHeadersWSI.h"
-#include "CurlOrthancConnection.h"
-
-#include "../Orthanc/Core/OrthancException.h"
-
-namespace OrthancWSI
-{
-  void CurlOrthancConnection::ApplyGet(std::string& result,
-                                       const std::string& uri)
-  {
-    Orthanc::HttpClient client(parameters_, uri);
-
-    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
-    client.SetRedirectionFollowed(false);  
-   
-    if (!client.Apply(result))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-
-  void CurlOrthancConnection::ApplyPost(std::string& result,
-                                        const std::string& uri,
-                                        const std::string& body)
-  {
-    Orthanc::HttpClient client(parameters_, uri);
-
-    client.SetMethod(Orthanc::HttpMethod_Post);
-    client.SetBody(body);
-
-    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
-    client.SetRedirectionFollowed(false);  
-   
-    if (!client.Apply(result))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-}
diff --git a/Framework/Messaging/CurlOrthancConnection.h b/Framework/Messaging/CurlOrthancConnection.h
deleted file mode 100644
index e9333a0..0000000
--- a/Framework/Messaging/CurlOrthancConnection.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "OrthancConnectionBase.h"
-
-#include "../Orthanc/Core/HttpClient.h"
-
-namespace OrthancWSI
-{
-  class CurlOrthancConnection : public OrthancConnectionBase
-  {
-  private:
-    Orthanc::WebServiceParameters  parameters_;
-
-  protected:
-    virtual void ApplyGet(std::string& result,
-                          const std::string& uri);
-
-    virtual void ApplyPost(std::string& result,
-                           const std::string& uri,
-                           const std::string& body);
-
-  public:
-    CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) :
-      parameters_(parameters)
-    {
-    }
-
-    const Orthanc::WebServiceParameters& GetParameters() const
-    {
-      return parameters_;
-    }
-  };
-}
diff --git a/Framework/Messaging/IOrthancConnection.cpp b/Framework/Messaging/IOrthancConnection.cpp
deleted file mode 100644
index d414cfa..0000000
--- a/Framework/Messaging/IOrthancConnection.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "../PrecompiledHeadersWSI.h"
-#include "IOrthancConnection.h"
-
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
-
-#include <json/reader.h>
-
-
-namespace OrthancWSI
-{
-  void IOrthancConnection::RestApiGet(Json::Value& result,
-                                      IOrthancConnection& orthanc,
-                                      const std::string& uri)
-  {
-    std::string content;
-    orthanc.RestApiGet(content, uri);
-
-    Json::Reader reader;
-    if (!reader.parse(content, result))
-    {
-      LOG(ERROR) << "Cannot parse a JSON file";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  void IOrthancConnection::RestApiPost(Json::Value& result,
-                                       IOrthancConnection& orthanc,
-                                       const std::string& uri,
-                                       const std::string& body)
-  {
-    std::string content;
-    orthanc.RestApiPost(content, uri, body);
-
-    Json::Reader reader;
-    if (!reader.parse(content, result))
-    {
-      LOG(ERROR) << "Cannot parse a JSON file";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-}
diff --git a/Framework/Messaging/IOrthancConnection.h b/Framework/Messaging/IOrthancConnection.h
deleted file mode 100644
index 8b9fc84..0000000
--- a/Framework/Messaging/IOrthancConnection.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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/value.h>
-
-namespace OrthancWSI
-{
-  // Derived classes must be thread-safe
-  class IOrthancConnection : public boost::noncopyable
-  {
-  public:
-    virtual ~IOrthancConnection()
-    {
-    }
-
-    virtual void RestApiGet(std::string& result,
-                            const std::string& uri) = 0;
-
-    virtual void RestApiPost(std::string& result, 
-                             const std::string& uri,
-                             const std::string& body) = 0;
-
-    static void RestApiGet(Json::Value& result,
-                           IOrthancConnection& orthanc,
-                           const std::string& uri);
-
-    static void RestApiPost(Json::Value& result,
-                            IOrthancConnection& orthanc,
-                            const std::string& uri,
-                            const std::string& body);
-  };
-}
diff --git a/Framework/Messaging/OrthancConnectionBase.cpp b/Framework/Messaging/OrthancConnectionBase.cpp
deleted file mode 100644
index 35fdb20..0000000
--- a/Framework/Messaging/OrthancConnectionBase.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "../PrecompiledHeadersWSI.h"
-#include "OrthancConnectionBase.h"
-
-namespace OrthancWSI
-{
-  void OrthancConnectionBase::RestApiGet(std::string& result,
-                                         const std::string& uri)
-  {
-    boost::mutex::scoped_lock  lock_;
-    ApplyGet(result, uri);
-  }
-
-
-  void OrthancConnectionBase::RestApiPost(std::string& result,
-                                          const std::string& uri,
-                                          const std::string& body)
-  {
-    boost::mutex::scoped_lock  lock_;
-    ApplyPost(result, uri, body);
-  }
-}
diff --git a/Framework/Messaging/OrthancConnectionBase.h b/Framework/Messaging/OrthancConnectionBase.h
deleted file mode 100644
index 0a71373..0000000
--- a/Framework/Messaging/OrthancConnectionBase.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "IOrthancConnection.h"
-
-#include <boost/thread/mutex.hpp>
-
-namespace OrthancWSI
-{
-  class OrthancConnectionBase : public IOrthancConnection
-  {
-  private:
-    boost::mutex  mutex_;
-
-  protected:
-    // Will be invoked in mutual exclusion
-    virtual void ApplyGet(std::string& result,
-                          const std::string& uri) = 0;
-
-    virtual void ApplyPost(std::string& result,
-                           const std::string& uri,
-                           const std::string& body) = 0;
-
-  public:
-    virtual void RestApiGet(std::string& result,
-                            const std::string& uri);
-
-    virtual void RestApiPost(std::string& result, 
-                             const std::string& uri,
-                             const std::string& body);
-  };
-}
diff --git a/Framework/Messaging/PluginOrthancConnection.cpp b/Framework/Messaging/PluginOrthancConnection.cpp
deleted file mode 100644
index 262cfab..0000000
--- a/Framework/Messaging/PluginOrthancConnection.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "../PrecompiledHeadersWSI.h"
-#include "PluginOrthancConnection.h"
-
-#include "../Orthanc/Core/OrthancException.h"
-
-namespace OrthancWSI
-{
-  class PluginOrthancConnection::MemoryBuffer : public boost::noncopyable
-  {
-  private:
-    OrthancPluginContext*      context_;
-    OrthancPluginMemoryBuffer  buffer_;
-
-    void Clear()
-    {
-      if (buffer_.data != NULL)
-      {
-        OrthancPluginFreeMemoryBuffer(context_, &buffer_);
-        buffer_.data = NULL;
-        buffer_.size = 0;
-      }
-    }
-
-
-  public:
-    MemoryBuffer(OrthancPluginContext* context) : 
-      context_(context)
-    {
-      buffer_.data = NULL;
-      buffer_.size = 0;
-    }
-
-    ~MemoryBuffer()
-    {
-      Clear();
-    }
-
-    void RestApiGet(const std::string& uri)
-    {
-      Clear();
-
-      OrthancPluginErrorCode error = OrthancPluginRestApiGet(context_, &buffer_, uri.c_str());
-
-      if (error == OrthancPluginErrorCode_Success)
-      {
-        // OK, success
-      }
-      else if (error == OrthancPluginErrorCode_UnknownResource ||
-               error == OrthancPluginErrorCode_InexistentItem)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
-      }
-    }
-
-    void RestApiPost(const std::string& uri,
-                     const std::string& body)
-    {
-      Clear();
-
-      OrthancPluginErrorCode error = OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body.c_str(), body.size());
-
-      if (error == OrthancPluginErrorCode_Success)
-      {
-        // OK, success
-      }
-      else if (error == OrthancPluginErrorCode_UnknownResource ||
-               error == OrthancPluginErrorCode_InexistentItem)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
-      }
-    }
-
-    void ToString(std::string& target) const
-    {
-      if (buffer_.size == 0)
-      {
-        target.clear();
-      }
-      else
-      {
-        target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
-      }
-    }
-  };
-
-
-
-  void PluginOrthancConnection::ApplyGet(std::string& result,
-                                         const std::string& uri)
-  {
-    MemoryBuffer buffer(context_);
-    buffer.RestApiGet(uri);
-    buffer.ToString(result);
-  }
-
-    
-  void PluginOrthancConnection::ApplyPost(std::string& result,
-                                          const std::string& uri,
-                                          const std::string& body)
-  {
-    MemoryBuffer buffer(context_);
-    buffer.RestApiPost(uri, body);
-    buffer.ToString(result);
-  }
-}
diff --git a/Framework/Messaging/PluginOrthancConnection.h b/Framework/Messaging/PluginOrthancConnection.h
deleted file mode 100644
index d2bdf43..0000000
--- a/Framework/Messaging/PluginOrthancConnection.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, 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 "OrthancConnectionBase.h"
-
-#include <orthanc/OrthancCPlugin.h>
-
-namespace OrthancWSI
-{
-  class PluginOrthancConnection : public OrthancConnectionBase
-  {
-  private:
-    class MemoryBuffer;
-
-    OrthancPluginContext*   context_;
-
-  protected:
-    virtual void ApplyGet(std::string& result,
-                          const std::string& uri);
-
-    virtual void ApplyPost(std::string& result,
-                           const std::string& uri,
-                           const std::string& body);
-
-  public:
-    PluginOrthancConnection(OrthancPluginContext* context) :
-      context_(context)
-    {
-    }
-  };
-}
diff --git a/Framework/Orthanc/Core/Uuid.cpp b/Framework/Orthanc/Core/Uuid.cpp
deleted file mode 100644
index 8681095..0000000
--- a/Framework/Orthanc/Core/Uuid.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "Uuid.h"
-
-// http://stackoverflow.com/a/1626302
-
-extern "C"
-{
-#ifdef WIN32
-#include <rpc.h>
-#else
-#include <uuid/uuid.h>
-#endif
-}
-
-#include <boost/filesystem.hpp>
-
-namespace Orthanc
-{
-  namespace Toolbox
-  {
-    std::string GenerateUuid()
-    {
-#ifdef WIN32
-      UUID uuid;
-      UuidCreate ( &uuid );
-
-      unsigned char * str;
-      UuidToStringA ( &uuid, &str );
-
-      std::string s( ( char* ) str );
-
-      RpcStringFreeA ( &str );
-#else
-      uuid_t uuid;
-      uuid_generate_random ( uuid );
-      char s[37];
-      uuid_unparse ( uuid, s );
-#endif
-      return s;
-    }
-
-
-    bool IsUuid(const std::string& str)
-    {
-      if (str.size() != 36)
-      {
-        return false;
-      }
-
-      for (size_t i = 0; i < str.length(); i++)
-      {
-        if (i == 8 || i == 13 || i == 18 || i == 23)
-        {
-          if (str[i] != '-')
-            return false;
-        }
-        else
-        {
-          if (!isalnum(str[i]))
-            return false;
-        }
-      }
-
-      return true;
-    }
-
-
-    bool StartsWithUuid(const std::string& str)
-    {
-      if (str.size() < 36)
-      {
-        return false;
-      }
-
-      if (str.size() == 36)
-      {
-        return IsUuid(str);
-      }
-
-      assert(str.size() > 36);
-      if (!isspace(str[36]))
-      {
-        return false;
-      }
-
-      return IsUuid(str.substr(0, 36));
-    }
-
-
-    static std::string CreateTemporaryPath(const char* extension)
-    {
-#if BOOST_HAS_FILESYSTEM_V3 == 1
-      boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path();
-#elif defined(__linux__)
-      boost::filesystem::path tmpDir("/tmp");
-#else
-#error Support your platform here
-#endif
-
-      // We use UUID to create unique path to temporary files
-      std::string filename = "Orthanc-" + Orthanc::Toolbox::GenerateUuid();
-
-      if (extension != NULL)
-      {
-        filename.append(extension);
-      }
-
-      tmpDir /= filename;
-      return tmpDir.string();
-    }
-
-
-    TemporaryFile::TemporaryFile() : 
-      path_(CreateTemporaryPath(NULL))
-    {
-    }
-
-
-    TemporaryFile::TemporaryFile(const char* extension) :
-      path_(CreateTemporaryPath(extension))
-    {
-    }
-
-
-    TemporaryFile::~TemporaryFile()
-    {
-      boost::filesystem::remove(path_);
-    }  
-  }
-}
diff --git a/Framework/Outputs/DicomPyramidWriter.cpp b/Framework/Outputs/DicomPyramidWriter.cpp
index 7d887fb..53db9b5 100644
--- a/Framework/Outputs/DicomPyramidWriter.cpp
+++ b/Framework/Outputs/DicomPyramidWriter.cpp
@@ -23,9 +23,9 @@
 
 #include "../DicomToolbox.h"
 
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/OrthancServer/FromDcmtkBridge.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/OrthancServer/FromDcmtkBridge.h"
 
 #include <dcmtk/dcmdata/dcdeftag.h>
 #include <boost/lexical_cast.hpp>
diff --git a/Framework/Outputs/DicomPyramidWriter.h b/Framework/Outputs/DicomPyramidWriter.h
index 48f3abc..f9fb8ee 100644
--- a/Framework/Outputs/DicomPyramidWriter.h
+++ b/Framework/Outputs/DicomPyramidWriter.h
@@ -22,7 +22,7 @@
 
 #include "PyramidWriterBase.h"
 #include "MultiframeDicomWriter.h"
-#include "../Messaging/IFileTarget.h"
+#include "../Targets/IFileTarget.h"
 #include "../ImagedVolumeParameters.h"
 
 namespace OrthancWSI
diff --git a/Framework/Outputs/HierarchicalTiffWriter.cpp b/Framework/Outputs/HierarchicalTiffWriter.cpp
index adb7918..5e2e5eb 100644
--- a/Framework/Outputs/HierarchicalTiffWriter.cpp
+++ b/Framework/Outputs/HierarchicalTiffWriter.cpp
@@ -21,20 +21,20 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "HierarchicalTiffWriter.h"
 
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Uuid.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/TemporaryFile.h"
 
 namespace OrthancWSI
 {
   class HierarchicalTiffWriter::PendingTile
   {
   private:
-    HierarchicalTiffWriter&          that_;
-    unsigned int                     level_;
-    unsigned int                     tileX_;
-    unsigned int                     tileY_;
-    Orthanc::Toolbox::TemporaryFile  file_;
+    HierarchicalTiffWriter&  that_;
+    unsigned int             level_;
+    unsigned int             tileX_;
+    unsigned int             tileY_;
+    Orthanc::TemporaryFile   file_;
       
   public:
     PendingTile(HierarchicalTiffWriter& that,
diff --git a/Framework/Outputs/IPyramidWriter.h b/Framework/Outputs/IPyramidWriter.h
index 0e4229e..04dac0c 100644
--- a/Framework/Outputs/IPyramidWriter.h
+++ b/Framework/Outputs/IPyramidWriter.h
@@ -21,7 +21,7 @@
 #pragma once
 
 #include "../Enumerations.h"
-#include "../Orthanc/Core/Images/ImageAccessor.h"
+#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
 
 #include <boost/noncopyable.hpp>
 
diff --git a/Framework/Outputs/InMemoryTiledImage.cpp b/Framework/Outputs/InMemoryTiledImage.cpp
index 1990e09..283328a 100644
--- a/Framework/Outputs/InMemoryTiledImage.cpp
+++ b/Framework/Outputs/InMemoryTiledImage.cpp
@@ -22,8 +22,8 @@
 #include "InMemoryTiledImage.h"
 
 #include "../ImageToolbox.h"
-#include "../Orthanc/Core/Logging.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 namespace OrthancWSI
 {
@@ -88,6 +88,7 @@ namespace OrthancWSI
 
 
   bool InMemoryTiledImage::ReadRawTile(std::string& tile,
+                                       ImageCompression& compression,
                                        unsigned int level,
                                        unsigned int tileX,
                                        unsigned int tileY)
diff --git a/Framework/Outputs/InMemoryTiledImage.h b/Framework/Outputs/InMemoryTiledImage.h
index da471ae..2352060 100644
--- a/Framework/Outputs/InMemoryTiledImage.h
+++ b/Framework/Outputs/InMemoryTiledImage.h
@@ -77,6 +77,7 @@ namespace OrthancWSI
     }
 
     virtual bool ReadRawTile(std::string& tile,
+                             ImageCompression& compression,
                              unsigned int level,
                              unsigned int tileX,
                              unsigned int tileY);
@@ -85,11 +86,6 @@ namespace OrthancWSI
                                                unsigned int tileX,
                                                unsigned int tileY);
 
-    virtual ImageCompression GetImageCompression() const
-    {
-      return ImageCompression_None;
-    }
-
     virtual Orthanc::PixelFormat GetPixelFormat() const
     {
       return format_;
diff --git a/Framework/Outputs/MultiframeDicomWriter.cpp b/Framework/Outputs/MultiframeDicomWriter.cpp
index c3dfb89..d727d40 100644
--- a/Framework/Outputs/MultiframeDicomWriter.cpp
+++ b/Framework/Outputs/MultiframeDicomWriter.cpp
@@ -21,8 +21,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "MultiframeDicomWriter.h"
 
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
 #include "../DicomToolbox.h"
 
 #include <dcmtk/dcmdata/dcuid.h>
diff --git a/Framework/Outputs/MultiframeDicomWriter.h b/Framework/Outputs/MultiframeDicomWriter.h
index 5aafc3e..b185e94 100644
--- a/Framework/Outputs/MultiframeDicomWriter.h
+++ b/Framework/Outputs/MultiframeDicomWriter.h
@@ -21,7 +21,7 @@
 #pragma once
 
 #include "../Enumerations.h"
-#include "../Orthanc/Core/ChunkedBuffer.h"
+#include "../../Resources/Orthanc/Core/ChunkedBuffer.h"
 
 #include <boost/noncopyable.hpp>
 #include <memory>
diff --git a/Framework/Outputs/PyramidWriterBase.cpp b/Framework/Outputs/PyramidWriterBase.cpp
index b7b91bc..09394dd 100644
--- a/Framework/Outputs/PyramidWriterBase.cpp
+++ b/Framework/Outputs/PyramidWriterBase.cpp
@@ -22,8 +22,8 @@
 #include "PyramidWriterBase.h"
 
 #include "../ImageToolbox.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/Outputs/TruncatedPyramidWriter.cpp b/Framework/Outputs/TruncatedPyramidWriter.cpp
index ec6f9ab..802c2dc 100644
--- a/Framework/Outputs/TruncatedPyramidWriter.cpp
+++ b/Framework/Outputs/TruncatedPyramidWriter.cpp
@@ -21,7 +21,7 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "TruncatedPyramidWriter.h"
 
-#include "../Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
 
 namespace OrthancWSI
 {
diff --git a/Framework/PrecompiledHeadersWSI.h b/Framework/PrecompiledHeadersWSI.h
index 4232c01..55d8766 100644
--- a/Framework/PrecompiledHeadersWSI.h
+++ b/Framework/PrecompiledHeadersWSI.h
@@ -22,13 +22,12 @@
 
 #if ORTHANC_USE_PRECOMPILED_HEADERS == 1
 
-#include "Orthanc/Core/PrecompiledHeaders.h"
+#include "../Resources/Orthanc/Core/PrecompiledHeaders.h"
 
 #include "DicomToolbox.h"
 #include "ImageToolbox.h"
 #include "Inputs/ITiledPyramid.h"
-#include "Messaging/IFileTarget.h"
-#include "Messaging/IOrthancConnection.h"
+#include "Targets/IFileTarget.h"
 #include "Outputs/IPyramidWriter.h"
 
 #endif
diff --git a/Framework/Messaging/FolderTarget.cpp b/Framework/Targets/FolderTarget.cpp
similarity index 88%
rename from Framework/Messaging/FolderTarget.cpp
rename to Framework/Targets/FolderTarget.cpp
index a293a9c..6451e4b 100644
--- a/Framework/Messaging/FolderTarget.cpp
+++ b/Framework/Targets/FolderTarget.cpp
@@ -21,8 +21,8 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "FolderTarget.h"
 
-#include "../Orthanc/Core/Toolbox.h"
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/SystemToolbox.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
 
 #include <stdio.h>
 
@@ -40,6 +40,6 @@ namespace OrthancWSI
     }
 
     LOG(INFO) << "Writing file " << path;
-    Orthanc::Toolbox::WriteFile(file, path);
+    Orthanc::SystemToolbox::WriteFile(file, path);
   }
 }
diff --git a/Framework/Messaging/FolderTarget.h b/Framework/Targets/FolderTarget.h
similarity index 100%
rename from Framework/Messaging/FolderTarget.h
rename to Framework/Targets/FolderTarget.h
diff --git a/Framework/Messaging/IFileTarget.h b/Framework/Targets/IFileTarget.h
similarity index 100%
rename from Framework/Messaging/IFileTarget.h
rename to Framework/Targets/IFileTarget.h
diff --git a/Framework/Messaging/OrthancTarget.cpp b/Framework/Targets/OrthancTarget.cpp
similarity index 57%
rename from Framework/Messaging/OrthancTarget.cpp
rename to Framework/Targets/OrthancTarget.cpp
index 68e4295..987af94 100644
--- a/Framework/Messaging/OrthancTarget.cpp
+++ b/Framework/Targets/OrthancTarget.cpp
@@ -21,15 +21,15 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "OrthancTarget.h"
 
-#include "CurlOrthancConnection.h"
 #include "../DicomToolbox.h"
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h"
 
 namespace OrthancWSI
 {
   OrthancTarget::OrthancTarget(const Orthanc::WebServiceParameters& parameters) :
-    orthanc_(new CurlOrthancConnection(parameters)),
+    orthanc_(new OrthancPlugins::OrthancHttpConnection(parameters)),
     first_(true)
   {
   }
@@ -38,16 +38,30 @@ namespace OrthancWSI
   void OrthancTarget::Write(const std::string& file)
   {
     Json::Value result;
-    IOrthancConnection::RestApiPost(result, *orthanc_, "/instances", file);
+    OrthancPlugins::IOrthancConnection::RestApiPost(result, *orthanc_, "/instances", file);
 
-    std::string instanceId = DicomToolbox::GetMandatoryStringTag(result, "ID");
+    if (result.type() != Json::objectValue ||
+        !result.isMember("ID") ||
+        result["ID"].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    std::string instanceId = result["ID"].asString();
 
     if (first_)
     {
       Json::Value instance;
-      IOrthancConnection::RestApiGet(instance, *orthanc_, "/instances/" + instanceId);
+      OrthancPlugins::IOrthancConnection::RestApiGet(instance, *orthanc_, "/instances/" + instanceId);
+
+      if (instance.type() != Json::objectValue ||
+          !instance.isMember("ParentSeries") ||
+          instance["ParentSeries"].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
 
-      std::string seriesId = DicomToolbox::GetMandatoryStringTag(instance, "ParentSeries");
+      std::string seriesId = instance["ParentSeries"].asString();
 
       LOG(WARNING) << "ID of the whole-slide image series in Orthanc: " << seriesId;
       first_ = false;
diff --git a/Framework/Messaging/OrthancTarget.h b/Framework/Targets/OrthancTarget.h
similarity index 79%
rename from Framework/Messaging/OrthancTarget.h
rename to Framework/Targets/OrthancTarget.h
index cc81671..15d333d 100644
--- a/Framework/Messaging/OrthancTarget.h
+++ b/Framework/Targets/OrthancTarget.h
@@ -21,8 +21,8 @@
 #pragma once
 
 #include "IFileTarget.h"
-#include "IOrthancConnection.h"
-#include "../Orthanc/Core/WebServiceParameters.h"
+#include "../../Resources/Orthanc/Core/WebServiceParameters.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
 
 #include <memory>
 
@@ -31,13 +31,13 @@ namespace OrthancWSI
   class OrthancTarget : public IFileTarget
   {
   private:
-    std::auto_ptr<IOrthancConnection>  orthanc_;
-    bool                               first_;
+    std::auto_ptr<OrthancPlugins::IOrthancConnection>  orthanc_;
+    bool  first_;
 
   public:
     OrthancTarget(const Orthanc::WebServiceParameters& parameters);
 
-    OrthancTarget(IOrthancConnection* orthanc) :   // Takes ownership
+    OrthancTarget(OrthancPlugins::IOrthancConnection* orthanc) :   // Takes ownership
       orthanc_(orthanc),
       first_(true)
     {
diff --git a/NEWS b/NEWS
index 0e56822..97f7cbf 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,16 @@ Pending changes in the mainline
 ===============================
 
 
+Version 0.2 (2016/11/28)
+========================
+
+* Huge speed-up in the whole-slide imaging Web viewer plugin:
+  - Reduction of the number of calls to the Orthanc REST API
+  - Cache pre-computed information for each instance as metadata
+  - Larger cache with LRU recycling to improve viewer performance
+* "OrthancWSIClearCache.py" companion script to clear the WSI cache
+* Various refactorings
+
 
 Version 0.1 (2016/10/28)
 ========================
diff --git a/Resources/CMake/LibTiffConfiguration.cmake b/Resources/CMake/LibTiffConfiguration.cmake
index d35580e..77ca942 100644
--- a/Resources/CMake/LibTiffConfiguration.cmake
+++ b/Resources/CMake/LibTiffConfiguration.cmake
@@ -19,7 +19,7 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_LIBTIFF)
 #    include <windows.h>
 #    define ssize_t SSIZE_T
 #  endif
-#  if !defined(snprintf)
+#  if !defined(snprintf) && (_MSC_VER < 1900)
 #    define snprintf _snprintf
 #  endif
 #endif
@@ -51,12 +51,23 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_LIBTIFF)
     -DHAVE_SNPRINTF=1
     -DJPEG_SUPPORT=1
     -DLZW_SUPPORT=1
-
-    -DTIFF_INT64_FORMAT="%lld"
-    -DTIFF_UINT64_FORMAT="%llu"
-    -DTIFF_SSIZE_FORMAT="%d"
     )
 
+  if (MSVC)
+    # The "%" must be escaped if using Visual Studio
+    add_definitions(
+      -DTIFF_INT64_FORMAT="%%lld"
+      -DTIFF_UINT64_FORMAT="%%llu"
+      -DTIFF_SSIZE_FORMAT="%%d"
+      )
+  else()
+    add_definitions(
+      -DTIFF_INT64_FORMAT="%lld"
+      -DTIFF_UINT64_FORMAT="%llu"
+      -DTIFF_SSIZE_FORMAT="%d"
+      )
+  endif()
+
   set(LIBTIFF_SOURCES
     #${LIBTIFF_SOURCES_DIR}/libtiff/mkg3states.c
     ${LIBTIFF_SOURCES_DIR}/libtiff/tif_aux.c
diff --git a/Resources/CMake/Version.cmake b/Resources/CMake/Version.cmake
index 9291846..f491a13 100644
--- a/Resources/CMake/Version.cmake
+++ b/Resources/CMake/Version.cmake
@@ -1,4 +1,4 @@
-set(ORTHANC_WSI_VERSION "0.1")
+set(ORTHANC_WSI_VERSION "0.2")
 
 add_definitions(
   -DORTHANC_WSI_VERSION="${ORTHANC_WSI_VERSION}"
diff --git a/Applications/CMakeLists.txt b/Resources/Graveyard/CMakeLists.txt
similarity index 70%
copy from Applications/CMakeLists.txt
copy to Resources/Graveyard/CMakeLists.txt
index 622ba23..19f572c 100644
--- a/Applications/CMakeLists.txt
+++ b/Resources/Graveyard/CMakeLists.txt
@@ -9,6 +9,7 @@ project(OrthancWSIApplications)
 # Generic parameters
 SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
 SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+SET(ENABLE_PROFILING OFF CACHE BOOL "Whether to enable the generation of profiling information with gprof")
 
 # Optional components
 SET(ENABLE_SSL OFF CACHE BOOL "Include support for SSL")
@@ -40,13 +41,14 @@ SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionar
 ## Configure mandatory third-party components
 #####################################################################
 
-SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
-SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc)
+SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
+SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Resources/Orthanc)
 SET(USE_OPENJPEG_JP2 ON)
 SET(ENABLE_JPEG OFF)           # Disable DCMTK's support for JPEG, that clashes with libtiff
 SET(ENABLE_JPEG_LOSSLESS OFF)  # Disable DCMTK's support for JPEG-LS
 SET(ENABLE_DCMTK_NETWORK OFF)  # Disable DCMTK's support for DICOM networking
 SET(STANDALONE_BUILD ON)       # Embed DCMTK's dictionaries for static builds
+SET(USE_DCMTK_361_PRIVATE_DIC OFF)  # No need for private tags
 
 include(CheckIncludeFiles)
 include(CheckIncludeFileCXX)
@@ -80,10 +82,11 @@ add_definitions(
   -DORTHANC_ENABLE_DCMTK=1
   -DORTHANC_ENABLE_LOGGING=1
   -DORTHANC_ENABLE_MD5=0
-  -DORTHANC_JPEG_ENABLED=0     # Disable DCMTK's support for JPEG
-  -DORTHANC_PKCS11_ENABLED=0
-  -DORTHANC_PLUGINS_ENABLED=1  # To enable class Orthanc::SharedLibrary
-  -DORTHANC_PUGIXML_ENABLED=0
+  -DORTHANC_ENABLE_JPEG=0     # Disable DCMTK's support for JPEG
+  -DORTHANC_ENABLE_PKCS11=0
+  -DORTHANC_ENABLE_PLUGINS=1  # To enable class Orthanc::SharedLibrary
+  -DORTHANC_ENABLE_PUGIXML=0
+  -DORTHANC_SANDBOXED=0
   )
 
 
@@ -93,10 +96,10 @@ add_definitions(
 
 if (ENABLE_SSL)
   set(ENABLE_PKCS11 OFF)
-  add_definitions(-DORTHANC_SSL_ENABLED=1)
+  add_definitions(-DORTHANC_ENABLE_SSL=1)
   include(${ORTHANC_ROOT}/Resources/CMake/OpenSslConfiguration.cmake)
 else()
-  add_definitions(-DORTHANC_SSL_ENABLED=0)
+  add_definitions(-DORTHANC_ENABLE_SSL=0)
 endif()
 
 
@@ -106,7 +109,6 @@ endif()
 #####################################################################
 
 set(ORTHANC_WSI_SOURCES
-  #${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp
   ${ORTHANC_WSI_DIR}/Framework/Algorithms/PyramidReader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Algorithms/ReconstructPyramidCommand.cpp
   ${ORTHANC_WSI_DIR}/Framework/Algorithms/TranscodeTileCommand.cpp
@@ -127,11 +129,8 @@ set(ORTHANC_WSI_SOURCES
   ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/CurlOrthancConnection.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/FolderTarget.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Targets/FolderTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Targets/OrthancTarget.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/DicomPyramidWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/HierarchicalTiffWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/InMemoryTiledImage.cpp
@@ -161,8 +160,9 @@ set(ORTHANC_CORE_SOURCES
   ${ORTHANC_ROOT}/Core/Logging.cpp
   ${ORTHANC_ROOT}/Core/MultiThreading/BagOfTasksProcessor.cpp
   ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp
+  ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
+  ${ORTHANC_ROOT}/Core/TemporaryFile.cpp
   ${ORTHANC_ROOT}/Core/Toolbox.cpp
-  ${ORTHANC_ROOT}/Core/Uuid.cpp
   ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
   ${ORTHANC_ROOT}/OrthancServer/FromDcmtkBridge.cpp
   ${ORTHANC_ROOT}/OrthancServer/ServerEnumerations.cpp
@@ -197,10 +197,10 @@ endif()
 
 
 #####################################################################
-## Create the static library containing the framework
+## Build a hello world
 #####################################################################
 
-add_library(OrthancWSIFramework STATIC
+add_executable(Hello
   ${ORTHANC_CORE_SOURCES}
   ${ORTHANC_WSI_SOURCES}
   ${AUTOGENERATED_SOURCES}
@@ -218,96 +218,23 @@ add_library(OrthancWSIFramework STATIC
 
   # Optional components
   ${OPENSSL_SOURCES}
-  )
-
-
-#####################################################################
-## Build the WSI DICOM-izer
-#####################################################################
-
-# Create the Windows resources, if need be
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  execute_process(
-    COMMAND 
-    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
-    ${ORTHANC_WSI_VERSION} OrthancWSIDicomizer OrthancWSIDicomizer.exe "Companion tool to Orthanc for whole-slide imaging"
-    ERROR_VARIABLE Failure
-    OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancWSIDicomizer.rc
-    )
-
-  if (Failure)
-    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
-  endif()
-
-  set(DICOMIZER_RESOURCES ${AUTOGENERATED_DIR}/OrthancWSIDicomizer.rc)
-endif()
 
-
-add_executable(OrthancWSIDicomizer
-  Dicomizer.cpp
-  ApplicationToolbox.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomTag.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancHttpConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp
+
+  Hello.cpp
+  ${ORTHANC_WSI_DIR}/Applications/ApplicationToolbox.cpp
+  
   ${DICOMIZER_RESOURCES}
   )
 
-target_link_libraries(OrthancWSIDicomizer OrthancWSIFramework ${DCMTK_LIBRARIES})
-
-install(
-  TARGETS OrthancWSIDicomizer
-  RUNTIME DESTINATION bin
-  )
-
-
-#####################################################################
-## Build the DICOM-to-TIFF conversion tool
-#####################################################################
-
-# Create the Windows resources, if need be
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  execute_process(
-    COMMAND 
-    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
-    ${ORTHANC_WSI_VERSION} OrthancWSIDicomToTiff OrthancWSIDicomToTiff.exe "Companion tool to Orthanc for whole-slide imaging"
-    ERROR_VARIABLE Failure
-    OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancWSIDicomToTiff.rc
-    )
-
-  if (Failure)
-    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
-  endif()
-
-  set(DICOM_TO_TIFF_RESOURCES ${AUTOGENERATED_DIR}/OrthancWSIDicomToTiff.rc)
-endif()
-
-
-add_executable(OrthancWSIDicomToTiff
-  DicomToTiff.cpp
-  ApplicationToolbox.cpp
-  ${DICOM_TO_TIFF_RESOURCES}
-  )
-
-target_link_libraries(OrthancWSIDicomToTiff OrthancWSIFramework ${DCMTK_LIBRARIES})
-
-install(
-  TARGETS OrthancWSIDicomToTiff
-  RUNTIME DESTINATION bin
-  )
-
+add_definitions(-DHAS_ORTHANC_EXCEPTION=1)
 
-#####################################################################
-## Generate the documentation if Doxygen is present
-#####################################################################
-
-find_package(Doxygen)
-if (DOXYGEN_FOUND)
-  configure_file(
-    ${ORTHANC_WSI_DIR}/Resources/OrthancWSI.doxygen
-    ${CMAKE_CURRENT_BINARY_DIR}/OrthancWSI.doxygen
-    @ONLY)
-
-  add_custom_target(doc
-    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancWSI.doxygen
-    COMMENT "Generating documentation with Doxygen" VERBATIM
-    )
-else()
-  message("Doxygen not found. The documentation will not be built.")
-endif()
+target_link_libraries(Hello ${DCMTK_LIBRARIES})
diff --git a/Resources/Graveyard/Hello.cpp b/Resources/Graveyard/Hello.cpp
new file mode 100644
index 0000000..4064608
--- /dev/null
+++ b/Resources/Graveyard/Hello.cpp
@@ -0,0 +1,85 @@
+
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <vector>
+#include <json/reader.h>
+#include <json/value.h>
+#include <boost/thread/mutex.hpp>
+
+#include "../../Framework/Inputs/DicomPyramid.h"
+#include "../../Applications/ApplicationToolbox.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.h"
+
+#include <stdio.h>
+
+namespace OrthancPlugins
+{
+  void Run()
+  {
+    OrthancHttpConnection orthanc;
+
+#if 0
+    //DicomDatasetReader reader(new SimplifiedOrthancDataset(orthanc, "/instances/2791b060-6ff103b3-8078bed0-5abbd75a-a5c675f7/tags?simplify"));
+    DicomDatasetReader reader(new FullOrthancDataset(orthanc, "/instances/2791b060-6ff103b3-8078bed0-5abbd75a-a5c675f7/tags"));
+
+    std::cout << reader.GetIntegerValue(DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS) << "x"
+              << reader.GetIntegerValue(DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS) << std::endl;
+    
+    std::string s;
+    printf("%d ", reader.GetDataset().GetStringValue(s, DICOM_TAG_SOP_CLASS_UID));
+    printf("[%s]\n", s.c_str());
+
+    size_t c;
+
+    {
+      DicomPath p(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE);
+      printf("%d ", reader.GetDataset().GetSequenceSize(c, p));
+      printf("%d\n", c);    
+    }
+
+    for (size_t i = 0; i < c; i++)
+    {
+      /*DicomPath p(DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX);
+        p.AddToPrefix(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i);
+        p.AddToPrefix(DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE, 0);
+
+        std::string x, y;
+        printf("%d %d ", i, reader.GetDataset().GetStringValue(x, p));
+        p.SetFinalTag(DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX);
+        printf("%d ", reader.GetDataset().GetStringValue(y, p));
+        printf("[%s,%s]\n", x.c_str(), y.c_str());*/
+      
+      std::cout << i << ": [" 
+                << reader.GetMandatoryStringValue(
+                  DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
+                            DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE, 0,
+                            DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX)) << ","
+                << reader.GetMandatoryStringValue(
+                  DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
+                            DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE, 0,
+                            DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX))
+                << "]" << std::endl;
+    }
+
+#else
+    OrthancWSI::DicomPyramid pyramid(orthanc, "09d0cca4-a8f0cd78-5480c690-ed14eb3b-a6614d14", true);
+    //OrthancWSI::DicomPyramid pyramid(orthanc, "4fdff9b9-8b81bc8f-04a3f903-4d44bd57-cc3bf42c");
+#endif
+  }
+
+}
+
+
+
+
+int main()
+{
+  OrthancWSI::ApplicationToolbox::GlobalInitialize();
+  OrthancPlugins::Run();
+  OrthancWSI::ApplicationToolbox::GlobalFinalize();
+  return 0;
+}
diff --git a/Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h b/Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h
new file mode 100644
index 0000000..a5bf7b2
--- /dev/null
+++ b/Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h
@@ -0,0 +1,346 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class LeastRecentlyUsedIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    void AddOrMakeMostRecent(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void MakeMostRecent(T id);
+
+    void MakeMostRecent(T id, Payload updatedPayload);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest();
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+
+    const T& GetOldest() const;
+    
+    const Payload& GetOldestPayload() const;
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload)
+  {
+    typename Index::iterator it = index_.find(id);
+
+    if (it != index_.end())
+    {
+      // Already existing. Make it most recent.
+      std::pair<T, Payload> item = *(it->second);
+      item.second = payload;
+      queue_.erase(it->second);
+      queue_.push_front(item);
+    }
+    else
+    {
+      // New item
+      queue_.push_front(std::make_pair(id, payload));
+    }
+
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    item.second = updatedPayload;
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest()
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().first;
+  }
+
+
+  template <typename T, typename Payload>
+  const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().second;
+  }
+}
diff --git a/Framework/Orthanc/Core/ChunkedBuffer.cpp b/Resources/Orthanc/Core/ChunkedBuffer.cpp
similarity index 100%
copy from Framework/Orthanc/Core/ChunkedBuffer.cpp
copy to Resources/Orthanc/Core/ChunkedBuffer.cpp
diff --git a/Framework/Orthanc/Core/ChunkedBuffer.h b/Resources/Orthanc/Core/ChunkedBuffer.h
similarity index 100%
copy from Framework/Orthanc/Core/ChunkedBuffer.h
copy to Resources/Orthanc/Core/ChunkedBuffer.h
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.cpp b/Resources/Orthanc/Core/DicomFormat/DicomArray.cpp
similarity index 100%
rename from Framework/Orthanc/Core/DicomFormat/DicomArray.cpp
rename to Resources/Orthanc/Core/DicomFormat/DicomArray.cpp
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.h b/Resources/Orthanc/Core/DicomFormat/DicomArray.h
similarity index 100%
copy from Framework/Orthanc/Core/DicomFormat/DicomArray.h
copy to Resources/Orthanc/Core/DicomFormat/DicomArray.h
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomElement.h b/Resources/Orthanc/Core/DicomFormat/DicomElement.h
similarity index 100%
rename from Framework/Orthanc/Core/DicomFormat/DicomElement.h
rename to Resources/Orthanc/Core/DicomFormat/DicomElement.h
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomMap.cpp b/Resources/Orthanc/Core/DicomFormat/DicomMap.cpp
similarity index 100%
rename from Framework/Orthanc/Core/DicomFormat/DicomMap.cpp
rename to Resources/Orthanc/Core/DicomFormat/DicomMap.cpp
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomMap.h b/Resources/Orthanc/Core/DicomFormat/DicomMap.h
similarity index 100%
rename from Framework/Orthanc/Core/DicomFormat/DicomMap.h
rename to Resources/Orthanc/Core/DicomFormat/DicomMap.h
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomTag.cpp b/Resources/Orthanc/Core/DicomFormat/DicomTag.cpp
similarity index 100%
rename from Framework/Orthanc/Core/DicomFormat/DicomTag.cpp
rename to Resources/Orthanc/Core/DicomFormat/DicomTag.cpp
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomTag.h b/Resources/Orthanc/Core/DicomFormat/DicomTag.h
similarity index 100%
rename from Framework/Orthanc/Core/DicomFormat/DicomTag.h
rename to Resources/Orthanc/Core/DicomFormat/DicomTag.h
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp b/Resources/Orthanc/Core/DicomFormat/DicomValue.cpp
similarity index 97%
copy from Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
copy to Resources/Orthanc/Core/DicomFormat/DicomValue.cpp
index 32a17b5..2a4c2f2 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
+++ b/Resources/Orthanc/Core/DicomFormat/DicomValue.cpp
@@ -81,7 +81,7 @@ namespace Orthanc
   }
 
   
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#if ORTHANC_ENABLE_BASE64 == 1
   void DicomValue::FormatDataUriScheme(std::string& target,
                                        const std::string& mime) const
   {
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.h b/Resources/Orthanc/Core/DicomFormat/DicomValue.h
similarity index 95%
copy from Framework/Orthanc/Core/DicomFormat/DicomValue.h
copy to Resources/Orthanc/Core/DicomFormat/DicomValue.h
index ad0ec54..c4844b7 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomValue.h
+++ b/Resources/Orthanc/Core/DicomFormat/DicomValue.h
@@ -35,6 +35,11 @@
 #include <string>
 #include <boost/noncopyable.hpp>
 
+#if !defined(ORTHANC_ENABLE_BASE64)
+#  error The macro ORTHANC_ENABLE_BASE64 must be defined
+#endif
+
+
 namespace Orthanc
 {
   class DicomValue : public boost::noncopyable
@@ -78,7 +83,7 @@ namespace Orthanc
     
     DicomValue* Clone() const;
 
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#if ORTHANC_ENABLE_BASE64 == 1
     void FormatDataUriScheme(std::string& target,
                              const std::string& mime) const;
 
diff --git a/Framework/Orthanc/Core/Endianness.h b/Resources/Orthanc/Core/Endianness.h
similarity index 100%
rename from Framework/Orthanc/Core/Endianness.h
rename to Resources/Orthanc/Core/Endianness.h
diff --git a/Framework/Orthanc/Core/EnumerationDictionary.h b/Resources/Orthanc/Core/EnumerationDictionary.h
similarity index 100%
rename from Framework/Orthanc/Core/EnumerationDictionary.h
rename to Resources/Orthanc/Core/EnumerationDictionary.h
diff --git a/Framework/Orthanc/Core/Enumerations.cpp b/Resources/Orthanc/Core/Enumerations.cpp
similarity index 99%
rename from Framework/Orthanc/Core/Enumerations.cpp
rename to Resources/Orthanc/Core/Enumerations.cpp
index 8ed74c2..5a72585 100644
--- a/Framework/Orthanc/Core/Enumerations.cpp
+++ b/Resources/Orthanc/Core/Enumerations.cpp
@@ -64,7 +64,7 @@ namespace Orthanc
         return "Parameter out of range";
 
       case ErrorCode_NotEnoughMemory:
-        return "Not enough memory";
+        return "The server hosting Orthanc is running out of memory";
 
       case ErrorCode_BadParameterType:
         return "Bad type for a parameter";
@@ -156,6 +156,9 @@ namespace Orthanc
       case ErrorCode_NotAcceptable:
         return "Cannot send a response which is acceptable according to the Accept HTTP header";
 
+      case ErrorCode_NullPointer:
+        return "Cannot handle a NULL pointer";
+
       case ErrorCode_SQLiteNotOpened:
         return "SQLite: The database is not opened";
 
diff --git a/Framework/Orthanc/Core/Enumerations.h b/Resources/Orthanc/Core/Enumerations.h
similarity index 99%
rename from Framework/Orthanc/Core/Enumerations.h
rename to Resources/Orthanc/Core/Enumerations.h
index 50f64c7..9b18661 100644
--- a/Framework/Orthanc/Core/Enumerations.h
+++ b/Resources/Orthanc/Core/Enumerations.h
@@ -52,7 +52,7 @@ namespace Orthanc
     ErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
     ErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
     ErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
-    ErrorCode_NotEnoughMemory = 4    /*!< Not enough memory */,
+    ErrorCode_NotEnoughMemory = 4    /*!< The server hosting Orthanc is running out of memory */,
     ErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
     ErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
     ErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
@@ -83,6 +83,7 @@ namespace Orthanc
     ErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
     ErrorCode_EmptyRequest = 33    /*!< The request is empty */,
     ErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    ErrorCode_NullPointer = 35    /*!< Cannot handle a NULL pointer */,
     ErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
     ErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
     ErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
diff --git a/Framework/Orthanc/Core/HttpClient.cpp b/Resources/Orthanc/Core/HttpClient.cpp
similarity index 97%
rename from Framework/Orthanc/Core/HttpClient.cpp
rename to Resources/Orthanc/Core/HttpClient.cpp
index f028d30..0999b84 100644
--- a/Framework/Orthanc/Core/HttpClient.cpp
+++ b/Resources/Orthanc/Core/HttpClient.cpp
@@ -37,6 +37,7 @@
 #include "OrthancException.h"
 #include "Logging.h"
 #include "ChunkedBuffer.h"
+#include "SystemToolbox.h"
 
 #include <string.h>
 #include <curl/curl.h>
@@ -44,7 +45,7 @@
 #include <boost/thread/mutex.hpp>
 
 
-#if ORTHANC_SSL_ENABLED == 1
+#if ORTHANC_ENABLE_SSL == 1
 // For OpenSSL initialization and finalization
 #  include <openssl/conf.h>
 #  include <openssl/engine.h>
@@ -54,7 +55,7 @@
 #endif
 
 
-#if ORTHANC_PKCS11_ENABLED == 1
+#if ORTHANC_ENABLE_PKCS11 == 1
 #  include "Pkcs11.h"
 #endif
 
@@ -161,7 +162,7 @@ namespace Orthanc
       return timeout_;
     }
 
-#if ORTHANC_PKCS11_ENABLED == 1
+#if ORTHANC_ENABLE_PKCS11 == 1
     bool IsPkcs11Initialized()
     {
       boost::mutex::scoped_lock lock(mutex_);
@@ -435,7 +436,7 @@ namespace Orthanc
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters));
     }
 
-#if ORTHANC_SSL_ENABLED == 1
+#if ORTHANC_ENABLE_SSL == 1
     // Setup HTTPS-related options
 
     if (verifyPeers_)
@@ -461,7 +462,7 @@ namespace Orthanc
 
     if (pkcs11Enabled_)
     {
-#if ORTHANC_PKCS11_ENABLED == 1
+#if ORTHANC_ENABLE_PKCS11 == 1
       if (GlobalParameters::GetInstance().IsPkcs11Initialized())
       {
         CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier()));
@@ -480,7 +481,7 @@ namespace Orthanc
     }
     else if (!clientCertificateFile_.empty())
     {
-#if ORTHANC_SSL_ENABLED == 1
+#if ORTHANC_ENABLE_SSL == 1
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
 
@@ -671,7 +672,7 @@ namespace Orthanc
   void HttpClient::ConfigureSsl(bool httpsVerifyPeers,
                                 const std::string& httpsVerifyCertificates)
   {
-#if ORTHANC_SSL_ENABLED == 1
+#if ORTHANC_ENABLE_SSL == 1
     if (httpsVerifyPeers)
     {
       if (httpsVerifyCertificates.empty())
@@ -696,7 +697,7 @@ namespace Orthanc
   
   void HttpClient::GlobalInitialize()
   {
-#if ORTHANC_SSL_ENABLED == 1
+#if ORTHANC_ENABLE_SSL == 1
     CheckCode(curl_global_init(CURL_GLOBAL_ALL));
 #else
     CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL));
@@ -708,7 +709,7 @@ namespace Orthanc
   {
     curl_global_cleanup();
 
-#if ORTHANC_PKCS11_ENABLED == 1
+#if ORTHANC_ENABLE_PKCS11 == 1
     Pkcs11::Finalize();
 #endif
   }
@@ -773,14 +774,14 @@ namespace Orthanc
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    if (!Toolbox::IsRegularFile(certificateFile))
+    if (!SystemToolbox::IsRegularFile(certificateFile))
     {
       LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
       throw OrthancException(ErrorCode_InexistentFile);
     }
 
     if (!certificateKeyFile.empty() && 
-        !Toolbox::IsRegularFile(certificateKeyFile))
+        !SystemToolbox::IsRegularFile(certificateKeyFile))
     {
       LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
       throw OrthancException(ErrorCode_InexistentFile);
@@ -796,7 +797,7 @@ namespace Orthanc
                                     const std::string& pin,
                                     bool verbose)
   {
-#if ORTHANC_PKCS11_ENABLED == 1
+#if ORTHANC_ENABLE_PKCS11 == 1
     LOG(INFO) << "Initializing PKCS#11 using " << module 
               << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)");
     GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose);    
@@ -809,7 +810,7 @@ namespace Orthanc
 
   void HttpClient::InitializeOpenSsl()
   {
-#if ORTHANC_SSL_ENABLED == 1
+#if ORTHANC_ENABLE_SSL == 1
     // https://wiki.openssl.org/index.php/Library_Initialization
     SSL_library_init();
     SSL_load_error_strings();
@@ -821,7 +822,7 @@ namespace Orthanc
 
   void HttpClient::FinalizeOpenSsl()
   {
- #if ORTHANC_SSL_ENABLED == 1
+ #if ORTHANC_ENABLE_SSL == 1
     // Finalize OpenSSL
     // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup
     FIPS_mode_set(0);
diff --git a/Framework/Orthanc/Core/HttpClient.h b/Resources/Orthanc/Core/HttpClient.h
similarity index 97%
rename from Framework/Orthanc/Core/HttpClient.h
rename to Resources/Orthanc/Core/HttpClient.h
index c699dc9..1b80f33 100644
--- a/Framework/Orthanc/Core/HttpClient.h
+++ b/Resources/Orthanc/Core/HttpClient.h
@@ -39,6 +39,15 @@
 #include <boost/shared_ptr.hpp>
 #include <json/json.h>
 
+#if !defined(ORTHANC_ENABLE_SSL)
+#  error The macro ORTHANC_ENABLE_SSL must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_PKCS11)
+#  error The macro ORTHANC_ENABLE_PKCS11 must be defined
+#endif
+
+
 namespace Orthanc
 {
   class HttpClient
diff --git a/Framework/Orthanc/Core/ICommand.h b/Resources/Orthanc/Core/ICommand.h
similarity index 100%
rename from Framework/Orthanc/Core/ICommand.h
rename to Resources/Orthanc/Core/ICommand.h
diff --git a/Framework/Orthanc/Core/IDynamicObject.h b/Resources/Orthanc/Core/IDynamicObject.h
similarity index 100%
rename from Framework/Orthanc/Core/IDynamicObject.h
rename to Resources/Orthanc/Core/IDynamicObject.h
diff --git a/Framework/Orthanc/Core/Images/IImageWriter.cpp b/Resources/Orthanc/Core/Images/IImageWriter.cpp
similarity index 89%
rename from Framework/Orthanc/Core/Images/IImageWriter.cpp
rename to Resources/Orthanc/Core/Images/IImageWriter.cpp
index 096adb3..b215080 100644
--- a/Framework/Orthanc/Core/Images/IImageWriter.cpp
+++ b/Resources/Orthanc/Core/Images/IImageWriter.cpp
@@ -32,11 +32,13 @@
 
 #include "IImageWriter.h"
 
-#include "../OrthancException.h"
-#include "../Toolbox.h"
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
 
 namespace Orthanc
 {
+#if ORTHANC_SANDBOXED == 0
   void IImageWriter::WriteToFileInternal(const std::string& path,
                                          unsigned int width,
                                          unsigned int height,
@@ -44,12 +46,9 @@ namespace Orthanc
                                          PixelFormat format,
                                          const void* buffer)
   {
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
     std::string compressed;
     WriteToMemoryInternal(compressed, width, height, pitch, format, buffer);
-    Toolbox::WriteFile(compressed, path);
-#else
-    throw OrthancException(ErrorCode_CannotWriteFile);  // Unavailable in sandboxed environments
-#endif
+    SystemToolbox::WriteFile(compressed, path);
   }
+#endif
 }
diff --git a/Framework/Orthanc/Core/Images/IImageWriter.h b/Resources/Orthanc/Core/Images/IImageWriter.h
similarity index 95%
rename from Framework/Orthanc/Core/Images/IImageWriter.h
rename to Resources/Orthanc/Core/Images/IImageWriter.h
index f2d1c97..ca27010 100644
--- a/Framework/Orthanc/Core/Images/IImageWriter.h
+++ b/Resources/Orthanc/Core/Images/IImageWriter.h
@@ -36,6 +36,10 @@
 
 #include <boost/noncopyable.hpp>
 
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
 namespace Orthanc
 {
   class IImageWriter : public boost::noncopyable
@@ -48,12 +52,14 @@ namespace Orthanc
                                        PixelFormat format,
                                        const void* buffer) = 0;
 
+#if ORTHANC_SANDBOXED == 0
     virtual void WriteToFileInternal(const std::string& path,
                                      unsigned int width,
                                      unsigned int height,
                                      unsigned int pitch,
                                      PixelFormat format,
                                      const void* buffer);
+#endif
 
   public:
     virtual ~IImageWriter()
@@ -67,11 +73,13 @@ namespace Orthanc
                             accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
     }
 
+#if ORTHANC_SANDBOXED == 0
     virtual void WriteToFile(const std::string& path,
                              const ImageAccessor& accessor)
     {
       WriteToFileInternal(path, accessor.GetWidth(), accessor.GetHeight(),
                           accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
     }
+#endif
   };
 }
diff --git a/Framework/Orthanc/Core/Images/Image.cpp b/Resources/Orthanc/Core/Images/Image.cpp
similarity index 100%
rename from Framework/Orthanc/Core/Images/Image.cpp
rename to Resources/Orthanc/Core/Images/Image.cpp
diff --git a/Framework/Orthanc/Core/Images/Image.h b/Resources/Orthanc/Core/Images/Image.h
similarity index 100%
rename from Framework/Orthanc/Core/Images/Image.h
rename to Resources/Orthanc/Core/Images/Image.h
diff --git a/Framework/Orthanc/Core/Images/ImageAccessor.cpp b/Resources/Orthanc/Core/Images/ImageAccessor.cpp
similarity index 100%
rename from Framework/Orthanc/Core/Images/ImageAccessor.cpp
rename to Resources/Orthanc/Core/Images/ImageAccessor.cpp
diff --git a/Framework/Orthanc/Core/Images/ImageAccessor.h b/Resources/Orthanc/Core/Images/ImageAccessor.h
similarity index 100%
rename from Framework/Orthanc/Core/Images/ImageAccessor.h
rename to Resources/Orthanc/Core/Images/ImageAccessor.h
diff --git a/Framework/Orthanc/Core/Images/ImageBuffer.cpp b/Resources/Orthanc/Core/Images/ImageBuffer.cpp
similarity index 100%
rename from Framework/Orthanc/Core/Images/ImageBuffer.cpp
rename to Resources/Orthanc/Core/Images/ImageBuffer.cpp
diff --git a/Framework/Orthanc/Core/Images/ImageBuffer.h b/Resources/Orthanc/Core/Images/ImageBuffer.h
similarity index 100%
copy from Framework/Orthanc/Core/Images/ImageBuffer.h
copy to Resources/Orthanc/Core/Images/ImageBuffer.h
diff --git a/Framework/Orthanc/Core/Images/ImageProcessing.cpp b/Resources/Orthanc/Core/Images/ImageProcessing.cpp
similarity index 100%
rename from Framework/Orthanc/Core/Images/ImageProcessing.cpp
rename to Resources/Orthanc/Core/Images/ImageProcessing.cpp
diff --git a/Framework/Orthanc/Core/Images/ImageProcessing.h b/Resources/Orthanc/Core/Images/ImageProcessing.h
similarity index 100%
rename from Framework/Orthanc/Core/Images/ImageProcessing.h
rename to Resources/Orthanc/Core/Images/ImageProcessing.h
diff --git a/Framework/Orthanc/Core/Images/JpegErrorManager.cpp b/Resources/Orthanc/Core/Images/JpegErrorManager.cpp
similarity index 100%
rename from Framework/Orthanc/Core/Images/JpegErrorManager.cpp
rename to Resources/Orthanc/Core/Images/JpegErrorManager.cpp
diff --git a/Framework/Orthanc/Core/Images/JpegErrorManager.h b/Resources/Orthanc/Core/Images/JpegErrorManager.h
similarity index 100%
rename from Framework/Orthanc/Core/Images/JpegErrorManager.h
rename to Resources/Orthanc/Core/Images/JpegErrorManager.h
diff --git a/Framework/Orthanc/Core/Images/JpegReader.cpp b/Resources/Orthanc/Core/Images/JpegReader.cpp
similarity index 96%
rename from Framework/Orthanc/Core/Images/JpegReader.cpp
rename to Resources/Orthanc/Core/Images/JpegReader.cpp
index c8e725e..9d6ea08 100644
--- a/Framework/Orthanc/Core/Images/JpegReader.cpp
+++ b/Resources/Orthanc/Core/Images/JpegReader.cpp
@@ -36,7 +36,11 @@
 #include "JpegErrorManager.h"
 #include "../OrthancException.h"
 #include "../Logging.h"
-#include "../Toolbox.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
 
 namespace Orthanc
 {
@@ -94,9 +98,10 @@ namespace Orthanc
   }
 
 
+#if ORTHANC_SANDBOXED == 0
   void JpegReader::ReadFromFile(const std::string& filename)
   {
-    FILE* fp = Toolbox::OpenFile(filename, FileMode_ReadBinary);
+    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
     if (!fp)
     {
       throw OrthancException(ErrorCode_InexistentFile);
@@ -135,6 +140,7 @@ namespace Orthanc
     jpeg_destroy_decompress(&cinfo);
     fclose(fp);
   }
+#endif
 
 
   void JpegReader::ReadFromMemory(const void* buffer,
diff --git a/Framework/Orthanc/Core/Images/JpegReader.h b/Resources/Orthanc/Core/Images/JpegReader.h
similarity index 94%
copy from Framework/Orthanc/Core/Images/JpegReader.h
copy to Resources/Orthanc/Core/Images/JpegReader.h
index 5cb5551..978058c 100644
--- a/Framework/Orthanc/Core/Images/JpegReader.h
+++ b/Resources/Orthanc/Core/Images/JpegReader.h
@@ -37,6 +37,10 @@
 #include <string>
 #include <boost/noncopyable.hpp>
 
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
 namespace Orthanc
 {
   class JpegReader : 
@@ -47,7 +51,9 @@ namespace Orthanc
     std::string  content_;
 
   public:
+#if ORTHANC_SANDBOXED == 0
     void ReadFromFile(const std::string& filename);
+#endif
 
     void ReadFromMemory(const void* buffer,
                         size_t size);
diff --git a/Framework/Orthanc/Core/Images/JpegWriter.cpp b/Resources/Orthanc/Core/Images/JpegWriter.cpp
similarity index 97%
rename from Framework/Orthanc/Core/Images/JpegWriter.cpp
rename to Resources/Orthanc/Core/Images/JpegWriter.cpp
index abe98fa..9f5ae8d 100644
--- a/Framework/Orthanc/Core/Images/JpegWriter.cpp
+++ b/Resources/Orthanc/Core/Images/JpegWriter.cpp
@@ -35,10 +35,12 @@
 
 #include "../OrthancException.h"
 #include "../Logging.h"
-#include "../Toolbox.h"
-
 #include "JpegErrorManager.h"
 
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
 #include <stdlib.h>
 #include <vector>
 
@@ -112,6 +114,7 @@ namespace Orthanc
   }
 
 
+#if ORTHANC_SANDBOXED == 0
   void JpegWriter::WriteToFileInternal(const std::string& filename,
                                        unsigned int width,
                                        unsigned int height,
@@ -119,7 +122,7 @@ namespace Orthanc
                                        PixelFormat format,
                                        const void* buffer)
   {
-    FILE* fp = Toolbox::OpenFile(filename, FileMode_WriteBinary);
+    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary);
     if (fp == NULL)
     {
       throw OrthancException(ErrorCode_CannotWriteFile);
@@ -155,6 +158,7 @@ namespace Orthanc
 
     fclose(fp);
   }
+#endif
 
 
   void JpegWriter::WriteToMemoryInternal(std::string& jpeg,
diff --git a/Framework/Orthanc/Core/Images/JpegWriter.h b/Resources/Orthanc/Core/Images/JpegWriter.h
similarity index 98%
rename from Framework/Orthanc/Core/Images/JpegWriter.h
rename to Resources/Orthanc/Core/Images/JpegWriter.h
index 5d18af3..ffb6098 100644
--- a/Framework/Orthanc/Core/Images/JpegWriter.h
+++ b/Resources/Orthanc/Core/Images/JpegWriter.h
@@ -46,12 +46,14 @@ namespace Orthanc
                                      PixelFormat format,
                                      const void* buffer);
 
+#if ORTHANC_SANDBOXED == 0
     virtual void WriteToMemoryInternal(std::string& jpeg,
                                        unsigned int width,
                                        unsigned int height,
                                        unsigned int pitch,
                                        PixelFormat format,
                                        const void* buffer);
+#endif
 
   private:
     uint8_t  quality_;
diff --git a/Framework/Orthanc/Core/Images/PngReader.cpp b/Resources/Orthanc/Core/Images/PngReader.cpp
similarity index 97%
rename from Framework/Orthanc/Core/Images/PngReader.cpp
rename to Resources/Orthanc/Core/Images/PngReader.cpp
index b93aee0..8315ca7 100644
--- a/Framework/Orthanc/Core/Images/PngReader.cpp
+++ b/Resources/Orthanc/Core/Images/PngReader.cpp
@@ -36,11 +36,16 @@
 #include "../OrthancException.h"
 #include "../Toolbox.h"
 
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
 #include <png.h>
 #include <string.h>  // For memcpy()
 
 namespace Orthanc
 {
+#if ORTHANC_SANDBOXED == 0
   namespace 
   {
     struct FileRabi
@@ -49,7 +54,7 @@ namespace Orthanc
 
       FileRabi(const char* filename)
       {
-        fp_ = Toolbox::OpenFile(filename, FileMode_ReadBinary);
+        fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
         if (!fp_)
         {
           throw OrthancException(ErrorCode_InexistentFile);
@@ -65,6 +70,7 @@ namespace Orthanc
       }
     };
   }
+#endif
 
 
   struct PngReader::PngRabi
@@ -206,6 +212,8 @@ namespace Orthanc
     AssignWritable(format, width, height, pitch, &data_[0]);
   }
 
+
+#if ORTHANC_SANDBOXED == 0
   void PngReader::ReadFromFile(const std::string& filename)
   {
     FileRabi f(filename.c_str());
@@ -230,6 +238,7 @@ namespace Orthanc
 
     Read(rabi);
   }
+#endif
 
 
   namespace
diff --git a/Framework/Orthanc/Core/Images/PngReader.h b/Resources/Orthanc/Core/Images/PngReader.h
similarity index 94%
rename from Framework/Orthanc/Core/Images/PngReader.h
rename to Resources/Orthanc/Core/Images/PngReader.h
index b6ad811..fd18d26 100644
--- a/Framework/Orthanc/Core/Images/PngReader.h
+++ b/Resources/Orthanc/Core/Images/PngReader.h
@@ -41,6 +41,10 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
 namespace Orthanc
 {
   class PngReader : 
@@ -59,7 +63,9 @@ namespace Orthanc
   public:
     PngReader();
 
+#if ORTHANC_SANDBOXED == 0
     void ReadFromFile(const std::string& filename);
+#endif
 
     void ReadFromMemory(const void* buffer,
                         size_t size);
diff --git a/Framework/Orthanc/Core/Images/PngWriter.cpp b/Resources/Orthanc/Core/Images/PngWriter.cpp
similarity index 97%
rename from Framework/Orthanc/Core/Images/PngWriter.cpp
rename to Resources/Orthanc/Core/Images/PngWriter.cpp
index 8a2d66a..5603bcf 100644
--- a/Framework/Orthanc/Core/Images/PngWriter.cpp
+++ b/Resources/Orthanc/Core/Images/PngWriter.cpp
@@ -40,6 +40,10 @@
 #include "../ChunkedBuffer.h"
 #include "../Toolbox.h"
 
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
 
 // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4
 // http://zarb.org/~gc/html/libpng.html
@@ -202,6 +206,7 @@ namespace Orthanc
   }
 
 
+#if ORTHANC_SANDBOXED == 0
   void PngWriter::WriteToFileInternal(const std::string& filename,
                                       unsigned int width,
                                       unsigned int height,
@@ -211,7 +216,7 @@ namespace Orthanc
   {
     Prepare(width, height, pitch, format, buffer);
 
-    FILE* fp = Toolbox::OpenFile(filename, FileMode_WriteBinary);
+    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary);
     if (!fp)
     {
       throw OrthancException(ErrorCode_CannotWriteFile);
@@ -229,7 +234,7 @@ namespace Orthanc
 
     fclose(fp);
   }
-
+#endif
 
 
   static void MemoryCallback(png_structp png_ptr, 
diff --git a/Framework/Orthanc/Core/Images/PngWriter.h b/Resources/Orthanc/Core/Images/PngWriter.h
similarity index 98%
rename from Framework/Orthanc/Core/Images/PngWriter.h
rename to Resources/Orthanc/Core/Images/PngWriter.h
index 591112f..7d8b87f 100644
--- a/Framework/Orthanc/Core/Images/PngWriter.h
+++ b/Resources/Orthanc/Core/Images/PngWriter.h
@@ -48,12 +48,14 @@ namespace Orthanc
                                      PixelFormat format,
                                      const void* buffer);
 
+#if ORTHANC_SANDBOXED == 0
     virtual void WriteToMemoryInternal(std::string& png,
                                        unsigned int width,
                                        unsigned int height,
                                        unsigned int pitch,
                                        PixelFormat format,
                                        const void* buffer);
+#endif
 
   private:
     struct PImpl;
diff --git a/Framework/Orthanc/Core/Logging.cpp b/Resources/Orthanc/Core/Logging.cpp
similarity index 70%
rename from Framework/Orthanc/Core/Logging.cpp
rename to Resources/Orthanc/Core/Logging.cpp
index 7bce92b..cffdb5b 100644
--- a/Framework/Orthanc/Core/Logging.cpp
+++ b/Resources/Orthanc/Core/Logging.cpp
@@ -83,6 +83,7 @@ namespace Orthanc
 #include "OrthancException.h"
 #include "Enumerations.h"
 #include "Toolbox.h"
+#include "SystemToolbox.h"
 
 #include <fstream>
 #include <boost/filesystem.hpp>
@@ -150,7 +151,7 @@ namespace Orthanc
 
       boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
       boost::filesystem::path root(directory);
-      boost::filesystem::path exe(Toolbox::GetPathToExecutable());
+      boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
       
       if (!boost::filesystem::exists(root) ||
           !boost::filesystem::is_directory(root))
@@ -166,7 +167,7 @@ namespace Orthanc
               now.time_of_day().hours(),
               now.time_of_day().minutes(),
               now.time_of_day().seconds(),
-              Toolbox::GetProcessId());
+              SystemToolbox::GetProcessId());
 
       std::string programName = exe.filename().replace_extension("").string();
 
@@ -318,113 +319,123 @@ namespace Orthanc
         return;
       }
 
-      LogLevel l = StringToLogLevel(level);
-      
-      if ((l == LogLevel_Info  && !loggingContext_->infoEnabled_) ||
-          (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
+      try
       {
-        // This logging level is disabled, directly exit and unlock
-        // the mutex to speed-up things. The stream is set to "/dev/null"
+        LogLevel l = StringToLogLevel(level);
+      
+        if ((l == LogLevel_Info  && !loggingContext_->infoEnabled_) ||
+            (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
+        {
+          // This logging level is disabled, directly exit and unlock
+          // the mutex to speed-up things. The stream is set to "/dev/null"
+          lock_.unlock();
+          return;
+        }
+
+        // Compute the header of the line, temporary release the lock as
+        // this is a time-consuming operation
         lock_.unlock();
-        return;
-      }
+        std::string header;
 
-      // Compute the header of the line, temporary release the lock as
-      // this is a time-consuming operation
-      lock_.unlock();
-      std::string header;
+        {
+          boost::filesystem::path path(file);
+          boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+          boost::posix_time::time_duration duration = now.time_of_day();
+
+          /**
+             From Google Log documentation:
+
+             "Log lines have this form:
+
+             Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+
+             where the fields are defined as follows:
+
+             L                A single character, representing the log level (eg 'I' for INFO)
+             mm               The month (zero padded; ie May is '05')
+             dd               The day (zero padded)
+             hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
+             threadid         The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
+             file             The file name
+             line             The line number
+             msg              The user-supplied message"
+
+             In this implementation, "threadid" is not printed.
+          **/
+
+          char date[32];
+          sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
+                  level[0],
+                  now.date().month().as_number(),
+                  now.date().day().as_number(),
+                  duration.hours(),
+                  duration.minutes(),
+                  duration.seconds(),
+                  static_cast<int>(duration.fractional_seconds()));
+
+          header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
+        }
 
-      {
-        boost::filesystem::path path(file);
-        boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
-        boost::posix_time::time_duration duration = now.time_of_day();
-
-        /**
-           From Google Log documentation:
-
-           "Log lines have this form:
-
-           Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
-
-           where the fields are defined as follows:
-
-           L                A single character, representing the log level (eg 'I' for INFO)
-           mm               The month (zero padded; ie May is '05')
-           dd               The day (zero padded)
-           hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
-           threadid         The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
-           file             The file name
-           line             The line number
-           msg              The user-supplied message"
-
-           In this implementation, "threadid" is not printed.
-         **/
-
-        char date[32];
-        sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
-                level[0],
-                now.date().month().as_number(),
-                now.date().day().as_number(),
-                duration.hours(),
-                duration.minutes(),
-                duration.seconds(),
-                static_cast<int>(duration.fractional_seconds()));
-
-        header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
-      }
 
+        // The header is computed, we now re-lock the mutex to access
+        // the stream objects. Pay attention that "loggingContext_",
+        // "infoEnabled_" or "traceEnabled_" might have changed while
+        // the mutex was unlocked.
+        lock_.lock();
 
-      // The header is computed, we now re-lock the mutex to access
-      // the stream objects. Pay attention that "loggingContext_",
-      // "infoEnabled_" or "traceEnabled_" might have changed while
-      // the mutex was unlocked.
-      lock_.lock();
+        if (loggingContext_.get() == NULL)
+        {
+          fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+          return;
+        }
 
-      if (loggingContext_.get() == NULL)
-      {
-        fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
-        return;
-      }
+        switch (l)
+        {
+          case LogLevel_Error:
+            stream_ = loggingContext_->error_;
+            break;
 
-      switch (l)
-      {
-        case LogLevel_Error:
-          stream_ = loggingContext_->error_;
-          break;
+          case LogLevel_Warning:
+            stream_ = loggingContext_->warning_;
+            break;
 
-        case LogLevel_Warning:
-          stream_ = loggingContext_->warning_;
-          break;
+          case LogLevel_Info:
+            if (loggingContext_->infoEnabled_)
+            {
+              stream_ = loggingContext_->info_;
+            }
 
-        case LogLevel_Info:
-          if (loggingContext_->infoEnabled_)
-          {
-            stream_ = loggingContext_->info_;
-          }
+            break;
 
-          break;
+          case LogLevel_Trace:
+            if (loggingContext_->traceEnabled_)
+            {
+              stream_ = loggingContext_->info_;
+            }
 
-        case LogLevel_Trace:
-          if (loggingContext_->traceEnabled_)
-          {
-            stream_ = loggingContext_->info_;
-          }
+            break;
 
-          break;
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
 
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
+        if (stream_ == &null_)
+        {
+          // The logging is disabled for this level. The stream is the
+          // "null_" member of this object, so we can release the global
+          // mutex.
+          lock_.unlock();
+        }
 
-      if (stream_ == &null_)
-      {
-        // The logging is disabled for this level. The stream is the
-        // "null_" member of this object, so we can release the global
-        // mutex.
-        lock_.unlock();
+        (*stream_) << header;
+      }
+      catch (...)
+      { 
+        // Something is going really wrong, probably running out of
+        // memory. Fallback to a degraded mode.
+        stream_ = loggingContext_->error_;
+        (*stream_) << "E???? ??:??:??.?????? ] ";
       }
-
-      (*stream_) << header;
     }
 
 
diff --git a/Framework/Orthanc/Core/Logging.h b/Resources/Orthanc/Core/Logging.h
similarity index 92%
rename from Framework/Orthanc/Core/Logging.h
rename to Resources/Orthanc/Core/Logging.h
index dd791d1..abec83f 100644
--- a/Framework/Orthanc/Core/Logging.h
+++ b/Resources/Orthanc/Core/Logging.h
@@ -34,6 +34,10 @@
 
 #include <iostream>
 
+#if !defined(ORTHANC_ENABLE_LOGGING)
+#  error The macro ORTHANC_ENABLE_LOGGING must be defined
+#endif
+
 namespace Orthanc
 {
   namespace Logging
@@ -110,6 +114,12 @@ namespace Orthanc
       {
         return (*stream_) << message;
       }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return (*stream_) << message;
+      }
     };
   }
 }
diff --git a/Framework/Orthanc/Core/MultiThreading/BagOfTasks.h b/Resources/Orthanc/Core/MultiThreading/BagOfTasks.h
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/BagOfTasks.h
rename to Resources/Orthanc/Core/MultiThreading/BagOfTasks.h
diff --git a/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp b/Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp
rename to Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp
diff --git a/Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h b/Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h
rename to Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h
diff --git a/Framework/Orthanc/Core/MultiThreading/Semaphore.cpp b/Resources/Orthanc/Core/MultiThreading/Semaphore.cpp
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/Semaphore.cpp
rename to Resources/Orthanc/Core/MultiThreading/Semaphore.cpp
diff --git a/Framework/Orthanc/Core/MultiThreading/Semaphore.h b/Resources/Orthanc/Core/MultiThreading/Semaphore.h
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/Semaphore.h
rename to Resources/Orthanc/Core/MultiThreading/Semaphore.h
diff --git a/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp b/Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp
rename to Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp
diff --git a/Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.h b/Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.h
similarity index 100%
rename from Framework/Orthanc/Core/MultiThreading/SharedMessageQueue.h
rename to Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.h
diff --git a/Framework/Orthanc/Core/OrthancException.h b/Resources/Orthanc/Core/OrthancException.h
similarity index 100%
copy from Framework/Orthanc/Core/OrthancException.h
copy to Resources/Orthanc/Core/OrthancException.h
diff --git a/Framework/Orthanc/Core/PrecompiledHeaders.cpp b/Resources/Orthanc/Core/PrecompiledHeaders.cpp
similarity index 100%
rename from Framework/Orthanc/Core/PrecompiledHeaders.cpp
rename to Resources/Orthanc/Core/PrecompiledHeaders.cpp
diff --git a/Framework/Orthanc/Core/PrecompiledHeaders.h b/Resources/Orthanc/Core/PrecompiledHeaders.h
similarity index 97%
rename from Framework/Orthanc/Core/PrecompiledHeaders.h
rename to Resources/Orthanc/Core/PrecompiledHeaders.h
index e22f012..1422e30 100644
--- a/Framework/Orthanc/Core/PrecompiledHeaders.h
+++ b/Resources/Orthanc/Core/PrecompiledHeaders.h
@@ -48,7 +48,7 @@
 
 #include <json/value.h>
 
-#if ORTHANC_PUGIXML_ENABLED == 1
+#if ORTHANC_ENABLE_PUGIXML == 1
 #include <pugixml.hpp>
 #endif
 
@@ -56,6 +56,5 @@
 #include "Logging.h"
 #include "OrthancException.h"
 #include "Toolbox.h"
-#include "Uuid.h"
 
 #endif
diff --git a/Resources/Orthanc/Core/SystemToolbox.cpp b/Resources/Orthanc/Core/SystemToolbox.cpp
new file mode 100644
index 0000000..e22a006
--- /dev/null
+++ b/Resources/Orthanc/Core/SystemToolbox.cpp
@@ -0,0 +1,539 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeaders.h"
+#include "SystemToolbox.h"
+
+
+#if BOOST_HAS_DATE_TIME == 1
+#  include <boost/date_time/posix_time/posix_time.hpp>
+#endif
+
+
+#if defined(_WIN32)
+#  include <windows.h>
+#  include <process.h>   // For "_spawnvp()" and "_getpid()"
+#else
+#  include <unistd.h>    // For "execvp()"
+#  include <sys/wait.h>  // For "waitpid()"
+#endif
+
+
+#if defined(__APPLE__) && defined(__MACH__)
+#  include <mach-o/dyld.h> /* _NSGetExecutablePath */
+#  include <limits.h>      /* PATH_MAX */
+#endif
+
+
+#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#  include <limits.h>      /* PATH_MAX */
+#  include <signal.h>
+#  include <unistd.h>
+#endif
+
+
+// Inclusions for UUID
+// http://stackoverflow.com/a/1626302
+
+extern "C"
+{
+#ifdef WIN32
+#  include <rpc.h>
+#else
+#  include <uuid/uuid.h>
+#endif
+}
+
+
+#include "Logging.h"
+#include "OrthancException.h"
+#include "Toolbox.h"
+
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+
+namespace Orthanc
+{
+  static bool finish_;
+  static ServerBarrierEvent barrierEvent_;
+
+#if defined(_WIN32)
+  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
+  {
+    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
+    finish_ = true;
+    return true;
+  }
+#else
+  static void SignalHandler(int signal)
+  {
+    if (signal == SIGHUP)
+    {
+      barrierEvent_ = ServerBarrierEvent_Reload;
+    }
+
+    finish_ = true;
+  }
+#endif
+
+
+  static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
+  {
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, true);
+#else
+    signal(SIGINT, SignalHandler);
+    signal(SIGQUIT, SignalHandler);
+    signal(SIGTERM, SignalHandler);
+    signal(SIGHUP, SignalHandler);
+#endif
+  
+    // Active loop that awakens every 100ms
+    finish_ = false;
+    barrierEvent_ = ServerBarrierEvent_Stop;
+    while (!(*stopFlag || finish_))
+    {
+      Toolbox::USleep(100 * 1000);
+    }
+
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, false);
+#else
+    signal(SIGINT, NULL);
+    signal(SIGQUIT, NULL);
+    signal(SIGTERM, NULL);
+    signal(SIGHUP, NULL);
+#endif
+
+    return barrierEvent_;
+  }
+
+
+  ServerBarrierEvent SystemToolbox::ServerBarrier(const bool& stopFlag)
+  {
+    return ServerBarrierInternal(&stopFlag);
+  }
+
+
+  ServerBarrierEvent SystemToolbox::ServerBarrier()
+  {
+    const bool stopFlag = false;
+    return ServerBarrierInternal(&stopFlag);
+  }
+
+
+  static std::streamsize GetStreamSize(std::istream& f)
+  {
+    // http://www.cplusplus.com/reference/iostream/istream/tellg/
+    f.seekg(0, std::ios::end);
+    std::streamsize size = f.tellg();
+    f.seekg(0, std::ios::beg);
+
+    return size;
+  }
+
+
+  void SystemToolbox::ReadFile(std::string& content,
+                               const std::string& path) 
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    std::streamsize size = GetStreamSize(f);
+    content.resize(size);
+    if (size != 0)
+    {
+      f.read(reinterpret_cast<char*>(&content[0]), size);
+    }
+
+    f.close();
+  }
+
+
+  bool SystemToolbox::ReadHeader(std::string& header,
+                                 const std::string& path,
+                                 size_t headerSize)
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    bool full = true;
+
+    {
+      std::streamsize size = GetStreamSize(f);
+      if (size <= 0)
+      {
+        headerSize = 0;
+        full = false;
+      }
+      else if (static_cast<size_t>(size) < headerSize)
+      {
+        headerSize = size;  // Truncate to the size of the file
+        full = false;
+      }
+    }
+
+    header.resize(headerSize);
+    if (headerSize != 0)
+    {
+      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
+    }
+
+    f.close();
+
+    return full;
+  }
+
+
+  void SystemToolbox::WriteFile(const void* content,
+                                size_t size,
+                                const std::string& path)
+  {
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::out | std::ofstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    if (size != 0)
+    {
+      f.write(reinterpret_cast<const char*>(content), size);
+
+      if (!f.good())
+      {
+        f.close();
+        throw OrthancException(ErrorCode_FileStorageCannotWrite);
+      }
+    }
+
+    f.close();
+  }
+
+
+  void SystemToolbox::WriteFile(const std::string& content,
+                                const std::string& path)
+  {
+    WriteFile(content.size() > 0 ? content.c_str() : NULL,
+              content.size(), path);
+  }
+
+
+  void SystemToolbox::RemoveFile(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (IsRegularFile(path))
+      {
+        boost::filesystem::remove(path);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_RegularFileExpected);
+      }
+    }
+  }
+
+
+  uint64_t SystemToolbox::GetFileSize(const std::string& path)
+  {
+    try
+    {
+      return static_cast<uint64_t>(boost::filesystem::file_size(path));
+    }
+    catch (boost::filesystem::filesystem_error&)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+  }
+
+
+  void SystemToolbox::MakeDirectory(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (!boost::filesystem::is_directory(path))
+      {
+        throw OrthancException(ErrorCode_DirectoryOverFile);
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path))
+      {
+        throw OrthancException(ErrorCode_MakeDirectory);
+      }
+    }
+  }
+
+
+  bool SystemToolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+
+
+#if defined(_WIN32)
+  static std::string GetPathToExecutableInternal()
+  {
+    // Yes, this is ugly, but there is no simple way to get the 
+    // required buffer size, so we use a big constant
+    std::vector<char> buffer(32768);
+    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+  static std::string GetPathToExecutableInternal()
+  {
+    std::vector<char> buffer(PATH_MAX + 1);
+    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
+    if (bytes == 0)
+    {
+      throw OrthancException(ErrorCode_PathToExecutable);
+    }
+
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__APPLE__) && defined(__MACH__)
+  static std::string GetPathToExecutableInternal()
+  {
+    char pathbuf[PATH_MAX + 1];
+    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));
+
+    _NSGetExecutablePath( pathbuf, &bufsize);
+
+    return std::string(pathbuf);
+  }
+
+#else
+#error Support your platform here
+#endif
+
+
+  std::string SystemToolbox::GetPathToExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p).string();
+  }
+
+
+  std::string SystemToolbox::GetDirectoryOfExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p.parent_path()).string();
+  }
+
+
+  void SystemToolbox::ExecuteSystemCommand(const std::string& command,
+                                           const std::vector<std::string>& arguments)
+  {
+    // Convert the arguments as a C array
+    std::vector<char*>  args(arguments.size() + 2);
+
+    args.front() = const_cast<char*>(command.c_str());
+
+    for (size_t i = 0; i < arguments.size(); i++)
+    {
+      args[i + 1] = const_cast<char*>(arguments[i].c_str());
+    }
+
+    args.back() = NULL;
+
+    int status;
+
+#if defined(_WIN32)
+    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
+    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));
+
+#else
+    int pid = fork();
+
+    if (pid == -1)
+    {
+      // Error in fork()
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Cannot fork a child process";
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+    else if (pid == 0)
+    {
+      // Execute the system command in the child process
+      execvp(command.c_str(), &args[0]);
+
+      // We should never get here
+      _exit(1);
+    }
+    else
+    {
+      // Wait for the system command to exit
+      waitpid(pid, &status, 0);
+    }
+#endif
+
+    if (status != 0)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "System command failed with status code " << status;
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+  }
+
+
+  int SystemToolbox::GetProcessId()
+  {
+#if defined(_WIN32)
+    return static_cast<int>(_getpid());
+#else
+    return static_cast<int>(getpid());
+#endif
+  }
+
+
+  bool SystemToolbox::IsRegularFile(const std::string& path)
+  {
+    namespace fs = boost::filesystem;
+
+    try
+    {
+      if (fs::exists(path))
+      {
+        fs::file_status status = fs::status(path);
+        return (status.type() == boost::filesystem::regular_file ||
+                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
+      }
+    }
+    catch (fs::filesystem_error&)
+    {
+    }
+
+    return false;
+  }
+
+
+  FILE* SystemToolbox::OpenFile(const std::string& path,
+                                FileMode mode)
+  {
+#if defined(_WIN32)
+    // TODO Deal with special characters by converting to the current locale
+#endif
+
+    const char* m;
+    switch (mode)
+    {
+      case FileMode_ReadBinary:
+        m = "rb";
+        break;
+
+      case FileMode_WriteBinary:
+        m = "wb";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    return fopen(path.c_str(), m);
+  }
+
+
+  std::string SystemToolbox::GenerateUuid()
+  {
+#ifdef WIN32
+    UUID uuid;
+    UuidCreate ( &uuid );
+
+    unsigned char * str;
+    UuidToStringA ( &uuid, &str );
+
+    std::string s( ( char* ) str );
+
+    RpcStringFreeA ( &str );
+#else
+    uuid_t uuid;
+    uuid_generate_random ( uuid );
+    char s[37];
+    uuid_unparse ( uuid, s );
+#endif
+    return s;
+  }
+
+
+#if BOOST_HAS_DATE_TIME == 1
+  std::string SystemToolbox::GetNowIsoString()
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    return boost::posix_time::to_iso_string(now);
+  }
+
+  void SystemToolbox::GetNowDicom(std::string& date,
+                                  std::string& time)
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    tm tm = boost::posix_time::to_tm(now);
+
+    char s[32];
+    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+    date.assign(s);
+
+    // TODO milliseconds
+    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
+    time.assign(s);
+  }
+#endif
+}
diff --git a/Framework/Orthanc/Core/OrthancException.h b/Resources/Orthanc/Core/SystemToolbox.h
similarity index 52%
rename from Framework/Orthanc/Core/OrthancException.h
rename to Resources/Orthanc/Core/SystemToolbox.h
index 5afa41f..e62b529 100644
--- a/Framework/Orthanc/Core/OrthancException.h
+++ b/Resources/Orthanc/Core/SystemToolbox.h
@@ -32,45 +32,71 @@
 
 #pragma once
 
-#include <stdint.h>
-#include <string>
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The namespace SystemToolbox cannot be used in sandboxed environments
+#endif
+
 #include "Enumerations.h"
 
+#include <vector>
+#include <string>
+#include <stdint.h>
+
 namespace Orthanc
 {
-  class OrthancException
+  namespace SystemToolbox
   {
-  protected:
-    ErrorCode  errorCode_;
-    HttpStatus httpStatus_;
-
-  public:
-    OrthancException(ErrorCode errorCode) : 
-      errorCode_(errorCode),
-      httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
-    {
-    }
-
-    OrthancException(ErrorCode errorCode,
-                     HttpStatus httpStatus) :
-      errorCode_(errorCode),
-      httpStatus_(httpStatus)
-    {
-    }
-
-    ErrorCode GetErrorCode() const
-    {
-      return errorCode_;
-    }
-
-    HttpStatus GetHttpStatus() const
-    {
-      return httpStatus_;
-    }
-
-    const char* What() const
-    {
-      return EnumerationToString(errorCode_);
-    }
-  };
+    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
+
+    ServerBarrierEvent ServerBarrier();
+
+    void ReadFile(std::string& content,
+                  const std::string& path);
+
+    bool ReadHeader(std::string& header,
+                    const std::string& path,
+                    size_t headerSize);
+
+    void WriteFile(const void* content,
+                   size_t size,
+                   const std::string& path);
+
+    void WriteFile(const std::string& content,
+                   const std::string& path);
+
+    void RemoveFile(const std::string& path);
+
+    uint64_t GetFileSize(const std::string& path);
+
+    void MakeDirectory(const std::string& path);
+
+    bool IsExistingFile(const std::string& path);
+
+    std::string GetPathToExecutable();
+
+    std::string GetDirectoryOfExecutable();
+
+    void ExecuteSystemCommand(const std::string& command,
+                              const std::vector<std::string>& arguments);
+
+    int GetProcessId();
+
+    bool IsRegularFile(const std::string& path);
+
+    FILE* OpenFile(const std::string& path,
+                   FileMode mode);
+
+    std::string GenerateUuid();
+
+#if BOOST_HAS_DATE_TIME == 1
+    std::string GetNowIsoString();
+
+    void GetNowDicom(std::string& date,
+                     std::string& time);
+#endif
+  }
 }
diff --git a/Framework/Orthanc/Core/Uuid.h b/Resources/Orthanc/Core/TemporaryFile.cpp
similarity index 58%
rename from Framework/Orthanc/Core/Uuid.h
rename to Resources/Orthanc/Core/TemporaryFile.cpp
index 88819a3..955489b 100644
--- a/Framework/Orthanc/Core/Uuid.h
+++ b/Resources/Orthanc/Core/TemporaryFile.cpp
@@ -30,57 +30,65 @@
  **/
 
 
-#pragma once
-
-#include <string>
-
-/**
- * GUID vs. UUID
- * The simple answer is: no difference, they are the same thing. Treat
- * them as a 16 byte (128 bits) value that is used as a unique
- * value. In Microsoft-speak they are called GUIDs, but call them
- * UUIDs when not using Microsoft-speak.
- * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
- **/
+#include "PrecompiledHeaders.h"
+#include "TemporaryFile.h"
 
+#include "SystemToolbox.h"
 #include "Toolbox.h"
 
+#include <boost/filesystem.hpp>
+
 namespace Orthanc
 {
-  namespace Toolbox
+  static std::string CreateTemporaryPath(const char* extension)
   {
-    std::string GenerateUuid();
-
-    bool IsUuid(const std::string& str);
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+    boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path();
+#elif defined(__linux__)
+    boost::filesystem::path tmpDir("/tmp");
+#else
+#error Support your platform here
+#endif
 
-    bool StartsWithUuid(const std::string& str);
+    // We use UUID to create unique path to temporary files
+    std::string filename = "Orthanc-" + Orthanc::SystemToolbox::GenerateUuid();
 
-    class TemporaryFile
+    if (extension != NULL)
     {
-    private:
-      std::string path_;
+      filename.append(extension);
+    }
 
-    public:
-      TemporaryFile();
+    tmpDir /= filename;
+    return tmpDir.string();
+  }
 
-      TemporaryFile(const char* extension);
 
-      ~TemporaryFile();
+  TemporaryFile::TemporaryFile() : 
+    path_(CreateTemporaryPath(NULL))
+  {
+  }
 
-      const std::string& GetPath() const
-      {
-        return path_;
-      }
 
-      void Write(const std::string& content)
-      {
-        Toolbox::WriteFile(content, path_);
-      }
+  TemporaryFile::TemporaryFile(const char* extension) :
+    path_(CreateTemporaryPath(extension))
+  {
+  }
 
-      void Read(std::string& content) const
-      {
-        Toolbox::ReadFile(content, path_);
-      }
-    };
+
+  TemporaryFile::~TemporaryFile()
+  {
+    boost::filesystem::remove(path_);
+  }
+
+
+  void TemporaryFile::Write(const std::string& content)
+  {
+    SystemToolbox::WriteFile(content, path_);
+  }
+
+
+  void TemporaryFile::Read(std::string& content) const
+  {
+    SystemToolbox::ReadFile(content, path_);
   }
 }
diff --git a/Framework/Orthanc/Core/ChunkedBuffer.h b/Resources/Orthanc/Core/TemporaryFile.h
similarity index 77%
rename from Framework/Orthanc/Core/ChunkedBuffer.h
rename to Resources/Orthanc/Core/TemporaryFile.h
index 552c1ec..1dd4f73 100644
--- a/Framework/Orthanc/Core/ChunkedBuffer.h
+++ b/Resources/Orthanc/Core/TemporaryFile.h
@@ -32,40 +32,37 @@
 
 #pragma once
 
-#include <list>
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class TemporaryFile cannot be used in sandboxed environments
+#endif
+
 #include <string>
 
 namespace Orthanc
 {
-  class ChunkedBuffer
+  class TemporaryFile
   {
   private:
-    typedef std::list<std::string*>  Chunks;
-    size_t numBytes_;
-    Chunks chunks_;
-  
-    void Clear();
+    std::string path_;
 
   public:
-    ChunkedBuffer() : numBytes_(0)
-    {
-    }
+    TemporaryFile();
 
-    ~ChunkedBuffer()
-    {
-      Clear();
-    }
+    TemporaryFile(const char* extension);
 
-    size_t GetNumBytes() const
+    ~TemporaryFile();
+
+    const std::string& GetPath() const
     {
-      return numBytes_;
+      return path_;
     }
 
-    void AddChunk(const void* chunkData,
-                  size_t chunkSize);
-
-    void AddChunk(const std::string& chunk);
+    void Write(const std::string& content);
 
-    void Flatten(std::string& result);
+    void Read(std::string& content) const;
   };
 }
diff --git a/Framework/Orthanc/Core/Toolbox.cpp b/Resources/Orthanc/Core/Toolbox.cpp
similarity index 69%
rename from Framework/Orthanc/Core/Toolbox.cpp
rename to Resources/Orthanc/Core/Toolbox.cpp
index ac1ad51..cb9bcb2 100644
--- a/Framework/Orthanc/Core/Toolbox.cpp
+++ b/Resources/Orthanc/Core/Toolbox.cpp
@@ -36,57 +36,31 @@
 #include "OrthancException.h"
 #include "Logging.h"
 
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/locale.hpp>
+#include <boost/uuid/sha1.hpp>
+
 #include <string>
 #include <stdint.h>
 #include <string.h>
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/fstream.hpp>
-#include <boost/uuid/sha1.hpp>
-#include <boost/lexical_cast.hpp>
 #include <algorithm>
 #include <ctype.h>
 
-#if BOOST_HAS_DATE_TIME == 1
-#include <boost/date_time/posix_time/posix_time.hpp>
-#endif
-
 #if BOOST_HAS_REGEX == 1
-#include <boost/regex.hpp> 
-#endif
-
-#if defined(_WIN32)
-#include <windows.h>
-#include <process.h>   // For "_spawnvp()" and "_getpid()"
-#else
-#include <unistd.h>    // For "execvp()"
-#include <sys/wait.h>  // For "waitpid()"
-#endif
-
-#if defined(__APPLE__) && defined(__MACH__)
-#include <mach-o/dyld.h> /* _NSGetExecutablePath */
-#include <limits.h>      /* PATH_MAX */
-#endif
-
-#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
-#include <limits.h>      /* PATH_MAX */
-#include <signal.h>
-#include <unistd.h>
+#  include <boost/regex.hpp> 
 #endif
 
 #if BOOST_HAS_LOCALE != 1
-#error Since version 0.7.6, Orthanc entirely relies on boost::locale
+#  error Since version 0.7.6, Orthanc entirely relies on boost::locale
 #endif
 
-#include <boost/locale.hpp>
-
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
-#include "../Resources/ThirdParty/md5/md5.h"
+#if ORTHANC_ENABLE_MD5 == 1
+#  include "../Resources/ThirdParty/md5/md5.h"
 #endif
 
-
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-#include "../Resources/ThirdParty/base64/base64.h"
+#if ORTHANC_ENABLE_BASE64 == 1
+#  include "../Resources/ThirdParty/base64/base64.h"
 #endif
 
 
@@ -103,9 +77,14 @@ extern "C"
 #endif
 
 
-#if ORTHANC_PUGIXML_ENABLED == 1
-#include "ChunkedBuffer.h"
-#include <pugixml.hpp>
+#if defined(_WIN32)
+#  include <windows.h>   // For ::Sleep
+#endif
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include "ChunkedBuffer.h"
+#  include <pugixml.hpp>
 #endif
 
 
@@ -123,75 +102,6 @@ namespace Orthanc
   }
 
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  static bool finish_;
-  static ServerBarrierEvent barrierEvent_;
-
-#if defined(_WIN32)
-  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
-  {
-    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
-    finish_ = true;
-    return true;
-  }
-#else
-  static void SignalHandler(int signal)
-  {
-    if (signal == SIGHUP)
-    {
-      barrierEvent_ = ServerBarrierEvent_Reload;
-    }
-
-    finish_ = true;
-  }
-#endif
-
-
-  static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
-  {
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, true);
-#else
-    signal(SIGINT, SignalHandler);
-    signal(SIGQUIT, SignalHandler);
-    signal(SIGTERM, SignalHandler);
-    signal(SIGHUP, SignalHandler);
-#endif
-  
-    // Active loop that awakens every 100ms
-    finish_ = false;
-    barrierEvent_ = ServerBarrierEvent_Stop;
-    while (!(*stopFlag || finish_))
-    {
-      Toolbox::USleep(100 * 1000);
-    }
-
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, false);
-#else
-    signal(SIGINT, NULL);
-    signal(SIGQUIT, NULL);
-    signal(SIGTERM, NULL);
-    signal(SIGHUP, NULL);
-#endif
-
-    return barrierEvent_;
-  }
-
-
-  ServerBarrierEvent Toolbox::ServerBarrier(const bool& stopFlag)
-  {
-    return ServerBarrierInternal(&stopFlag);
-  }
-
-  ServerBarrierEvent Toolbox::ServerBarrier()
-  {
-    const bool stopFlag = false;
-    return ServerBarrierInternal(&stopFlag);
-  }
-#endif  /* ORTHANC_SANDBOXED */
-
-
   void Toolbox::ToUpperCase(std::string& s)
   {
     std::transform(s.begin(), s.end(), s.begin(), toupper);
@@ -219,149 +129,6 @@ namespace Orthanc
   }
 
 
-  static std::streamsize GetStreamSize(std::istream& f)
-  {
-    // http://www.cplusplus.com/reference/iostream/istream/tellg/
-    f.seekg(0, std::ios::end);
-    std::streamsize size = f.tellg();
-    f.seekg(0, std::ios::beg);
-
-    return size;
-  }
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  void Toolbox::ReadFile(std::string& content,
-                         const std::string& path) 
-  {
-    if (!IsRegularFile(path))
-    {
-      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
-      throw OrthancException(ErrorCode_RegularFileExpected);
-    }
-
-    boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ifstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    std::streamsize size = GetStreamSize(f);
-    content.resize(size);
-    if (size != 0)
-    {
-      f.read(reinterpret_cast<char*>(&content[0]), size);
-    }
-
-    f.close();
-  }
-#endif
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  bool Toolbox::ReadHeader(std::string& header,
-                           const std::string& path,
-                           size_t headerSize)
-  {
-    if (!IsRegularFile(path))
-    {
-      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
-      throw OrthancException(ErrorCode_RegularFileExpected);
-    }
-
-    boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ifstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    bool full = true;
-
-    {
-      std::streamsize size = GetStreamSize(f);
-      if (size <= 0)
-      {
-        headerSize = 0;
-        full = false;
-      }
-      else if (static_cast<size_t>(size) < headerSize)
-      {
-        headerSize = size;  // Truncate to the size of the file
-        full = false;
-      }
-    }
-
-    header.resize(headerSize);
-    if (headerSize != 0)
-    {
-      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
-    }
-
-    f.close();
-
-    return full;
-  }
-#endif
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  void Toolbox::WriteFile(const void* content,
-                          size_t size,
-                          const std::string& path)
-  {
-    boost::filesystem::ofstream f;
-    f.open(path, std::ofstream::out | std::ofstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_CannotWriteFile);
-    }
-
-    if (size != 0)
-    {
-      f.write(reinterpret_cast<const char*>(content), size);
-
-      if (!f.good())
-      {
-        f.close();
-        throw OrthancException(ErrorCode_FileStorageCannotWrite);
-      }
-    }
-
-    f.close();
-  }
-#endif
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  void Toolbox::WriteFile(const std::string& content,
-                          const std::string& path)
-  {
-    WriteFile(content.size() > 0 ? content.c_str() : NULL,
-              content.size(), path);
-  }
-#endif
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  void Toolbox::RemoveFile(const std::string& path)
-  {
-    if (boost::filesystem::exists(path))
-    {
-      if (IsRegularFile(path))
-      {
-        boost::filesystem::remove(path);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_RegularFileExpected);
-      }
-    }
-  }
-#endif
-
-
   void Toolbox::SplitUriComponents(UriComponents& components,
                                    const std::string& uri)
   {
@@ -529,23 +296,7 @@ namespace Orthanc
   }
 
 
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  uint64_t Toolbox::GetFileSize(const std::string& path)
-  {
-    try
-    {
-      return static_cast<uint64_t>(boost::filesystem::file_size(path));
-    }
-    catch (boost::filesystem::filesystem_error&)
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-  }
-#endif
-
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+#if ORTHANC_ENABLE_MD5 == 1
   static char GetHexadecimalCharacter(uint8_t value)
   {
     assert(value < 16);
@@ -602,7 +353,7 @@ namespace Orthanc
 #endif
 
 
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#if ORTHANC_ENABLE_BASE64 == 1
   void Toolbox::EncodeBase64(std::string& result, 
                              const std::string& data)
   {
@@ -661,65 +412,6 @@ namespace Orthanc
 #endif
 
 
-
-#if defined(_WIN32)
-  static std::string GetPathToExecutableInternal()
-  {
-    // Yes, this is ugly, but there is no simple way to get the 
-    // required buffer size, so we use a big constant
-    std::vector<char> buffer(32768);
-    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
-    return std::string(&buffer[0]);
-  }
-
-#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
-  static std::string GetPathToExecutableInternal()
-  {
-    std::vector<char> buffer(PATH_MAX + 1);
-    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
-    if (bytes == 0)
-    {
-      throw OrthancException(ErrorCode_PathToExecutable);
-    }
-
-    return std::string(&buffer[0]);
-  }
-
-#elif defined(__APPLE__) && defined(__MACH__)
-  static std::string GetPathToExecutableInternal()
-  {
-    char pathbuf[PATH_MAX + 1];
-    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));
-
-    _NSGetExecutablePath( pathbuf, &bufsize);
-
-    return std::string(pathbuf);
-  }
-
-#elif defined(ORTHANC_SANDBOXED) && ORTHANC_SANDBOXED == 1
-  // Sandboxed Orthanc, no access to the executable
-
-#else
-#error Support your platform here
-#endif
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  std::string Toolbox::GetPathToExecutable()
-  {
-    boost::filesystem::path p(GetPathToExecutableInternal());
-    return boost::filesystem::absolute(p).string();
-  }
-
-
-  std::string Toolbox::GetDirectoryOfExecutable()
-  {
-    boost::filesystem::path p(GetPathToExecutableInternal());
-    return boost::filesystem::absolute(p.parent_path()).string();
-  }
-#endif
-
-
   static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
   {
     switch (sourceEncoding)
@@ -844,6 +536,23 @@ namespace Orthanc
   }
 
 
+  bool Toolbox::IsAsciiString(const void* data,
+                              size_t size)
+  {
+    const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+
+    for (size_t i = 0; i < size; i++, p++)
+    {
+      if (*p > 127 || (*p != 0 && iscntrl(*p)))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
   std::string Toolbox::ConvertToAscii(const std::string& source)
   {
     std::string result;
@@ -980,30 +689,6 @@ namespace Orthanc
   }
 
 
-#if BOOST_HAS_DATE_TIME == 1
-  std::string Toolbox::GetNowIsoString()
-  {
-    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-    return boost::posix_time::to_iso_string(now);
-  }
-
-  void Toolbox::GetNowDicom(std::string& date,
-                            std::string& time)
-  {
-    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-    tm tm = boost::posix_time::to_tm(now);
-
-    char s[32];
-    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
-    date.assign(s);
-
-    // TODO milliseconds
-    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
-    time.assign(s);
-  }
-#endif
-
-
   std::string Toolbox::StripSpaces(const std::string& source)
   {
     size_t first = 0;
@@ -1161,36 +846,7 @@ namespace Orthanc
   }
 
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  void Toolbox::MakeDirectory(const std::string& path)
-  {
-    if (boost::filesystem::exists(path))
-    {
-      if (!boost::filesystem::is_directory(path))
-      {
-        throw OrthancException(ErrorCode_DirectoryOverFile);
-      }
-    }
-    else
-    {
-      if (!boost::filesystem::create_directories(path))
-      {
-        throw OrthancException(ErrorCode_MakeDirectory);
-      }
-    }
-  }
-#endif
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  bool Toolbox::IsExistingFile(const std::string& path)
-  {
-    return boost::filesystem::exists(path);
-  }
-#endif
-
-
-#if ORTHANC_PUGIXML_ENABLED == 1
+#if ORTHANC_ENABLE_PUGIXML == 1
   class ChunkedBufferWriter : public pugi::xml_writer
   {
   private:
@@ -1312,66 +968,6 @@ namespace Orthanc
 #endif
 
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  void Toolbox::ExecuteSystemCommand(const std::string& command,
-                                     const std::vector<std::string>& arguments)
-  {
-    // Convert the arguments as a C array
-    std::vector<char*>  args(arguments.size() + 2);
-
-    args.front() = const_cast<char*>(command.c_str());
-
-    for (size_t i = 0; i < arguments.size(); i++)
-    {
-      args[i + 1] = const_cast<char*>(arguments[i].c_str());
-    }
-
-    args.back() = NULL;
-
-    int status;
-
-#if defined(_WIN32)
-    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
-    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));
-
-#else
-    int pid = fork();
-
-    if (pid == -1)
-    {
-      // Error in fork()
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "Cannot fork a child process";
-#endif
-
-      throw OrthancException(ErrorCode_SystemCommand);
-    }
-    else if (pid == 0)
-    {
-      // Execute the system command in the child process
-      execvp(command.c_str(), &args[0]);
-
-      // We should never get here
-      _exit(1);
-    }
-    else
-    {
-      // Wait for the system command to exit
-      waitpid(pid, &status, 0);
-    }
-#endif
-
-    if (status != 0)
-    {
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "System command failed with status code " << status;
-#endif
-
-      throw OrthancException(ErrorCode_SystemCommand);
-    }
-  }
-#endif
-
   
   bool Toolbox::IsInteger(const std::string& str)
   {
@@ -1479,66 +1075,6 @@ namespace Orthanc
       return str.compare(0, prefix.size(), prefix) == 0;
     }
   }
-
-
-  int Toolbox::GetProcessId()
-  {
-#if defined(_WIN32)
-    return static_cast<int>(_getpid());
-#else
-    return static_cast<int>(getpid());
-#endif
-  }
-
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-  bool Toolbox::IsRegularFile(const std::string& path)
-  {
-    namespace fs = boost::filesystem;
-
-    try
-    {
-      if (fs::exists(path))
-      {
-        fs::file_status status = fs::status(path);
-        return (status.type() == boost::filesystem::regular_file ||
-                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
-      }
-    }
-    catch (fs::filesystem_error&)
-    {
-    }
-
-    return false;
-  }
-#endif
-
-
-  FILE* Toolbox::OpenFile(const std::string& path,
-                          FileMode mode)
-  {
-#if defined(_WIN32)
-    // TODO Deal with special characters by converting to the current locale
-#endif
-
-    const char* m;
-    switch (mode)
-    {
-      case FileMode_ReadBinary:
-        m = "rb";
-        break;
-
-      case FileMode_WriteBinary:
-        m = "wb";
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    return fopen(path.c_str(), m);
-  }
-
   
 
   static bool IsUnreservedCharacter(char c)
@@ -1679,4 +1215,51 @@ namespace Orthanc
       return static_cast<unsigned int>(v);
     }
   }
+
+
+  bool Toolbox::IsUuid(const std::string& str)
+  {
+    if (str.size() != 36)
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < str.length(); i++)
+    {
+      if (i == 8 || i == 13 || i == 18 || i == 23)
+      {
+        if (str[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(str[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::StartsWithUuid(const std::string& str)
+  {
+    if (str.size() < 36)
+    {
+      return false;
+    }
+
+    if (str.size() == 36)
+    {
+      return IsUuid(str);
+    }
+
+    assert(str.size() > 36);
+    if (!isspace(str[36]))
+    {
+      return false;
+    }
+
+    return IsUuid(str.substr(0, 36));
+  }
 }
diff --git a/Framework/Orthanc/Core/Toolbox.h b/Resources/Orthanc/Core/Toolbox.h
similarity index 72%
rename from Framework/Orthanc/Core/Toolbox.h
rename to Resources/Orthanc/Core/Toolbox.h
index 98195bc..d32777a 100644
--- a/Framework/Orthanc/Core/Toolbox.h
+++ b/Resources/Orthanc/Core/Toolbox.h
@@ -39,6 +39,35 @@
 #include <string>
 #include <json/json.h>
 
+
+#if !defined(ORTHANC_ENABLE_BASE64)
+#  error The macro ORTHANC_ENABLE_BASE64 must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_MD5)
+#  error The macro ORTHANC_ENABLE_MD5 must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
+#endif
+
+#if !defined(BOOST_HAS_REGEX)
+#  error The macro BOOST_HAS_REGEX must be defined
+#endif
+
+
+/**
+ * NOTE: GUID vs. UUID
+ * The simple answer is: no difference, they are the same thing. Treat
+ * them as a 16 byte (128 bits) value that is used as a unique
+ * value. In Microsoft-speak they are called GUIDs, but call them
+ * UUIDs when not using Microsoft-speak.
+ * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
+ **/
+
+
+
 namespace Orthanc
 {
   typedef std::vector<std::string> UriComponents;
@@ -51,12 +80,6 @@ namespace Orthanc
   {
     void USleep(uint64_t microSeconds);
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
-
-    ServerBarrierEvent ServerBarrier();
-#endif
-
     void ToUpperCase(std::string& s);  // Inplace version
 
     void ToLowerCase(std::string& s);  // Inplace version
@@ -67,32 +90,6 @@ namespace Orthanc
     void ToLowerCase(std::string& result,
                      const std::string& source);
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    void ReadFile(std::string& content,
-                  const std::string& path);
-#endif
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    bool ReadHeader(std::string& header,
-                    const std::string& path,
-                    size_t headerSize);
-#endif
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    void WriteFile(const std::string& content,
-                   const std::string& path);
-#endif
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    void WriteFile(const void* content,
-                   size_t size,
-                   const std::string& path);
-#endif
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    void RemoveFile(const std::string& path);
-#endif
-
     void SplitUriComponents(UriComponents& components,
                             const std::string& uri);
   
@@ -108,11 +105,7 @@ namespace Orthanc
     std::string FlattenUri(const UriComponents& components,
                            size_t fromLevel = 0);
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    uint64_t GetFileSize(const std::string& path);
-#endif
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+#if ORTHANC_ENABLE_MD5 == 1
     void ComputeMD5(std::string& result,
                     const std::string& data);
 
@@ -133,7 +126,7 @@ namespace Orthanc
 
     bool IsSHA1(const std::string& s);
 
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#if ORTHANC_ENABLE_BASE64 == 1
     void DecodeBase64(std::string& result, 
                       const std::string& data);
 
@@ -151,29 +144,19 @@ namespace Orthanc
                              const std::string& content);
 #endif
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    std::string GetPathToExecutable();
-
-    std::string GetDirectoryOfExecutable();
-#endif
-
     std::string ConvertToUtf8(const std::string& source,
                               Encoding sourceEncoding);
 
     std::string ConvertFromUtf8(const std::string& source,
                                 Encoding targetEncoding);
 
+    bool IsAsciiString(const void* data,
+                       size_t size);
+
     std::string ConvertToAscii(const std::string& source);
 
     std::string StripSpaces(const std::string& source);
 
-#if BOOST_HAS_DATE_TIME == 1
-    std::string GetNowIsoString();
-
-    void GetNowDicom(std::string& date,
-                     std::string& time);
-#endif
-
     // In-place percent-decoding for URL
     void UrlDecode(std::string& s);
 
@@ -187,26 +170,13 @@ namespace Orthanc
                         const std::string& source,
                         char separator);
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    void MakeDirectory(const std::string& path);
-#endif
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    bool IsExistingFile(const std::string& path);
-#endif
-
-#if ORTHANC_PUGIXML_ENABLED == 1
+#if ORTHANC_ENABLE_PUGIXML == 1
     void JsonToXml(std::string& target,
                    const Json::Value& source,
                    const std::string& rootElement = "root",
                    const std::string& arrayElement = "item");
 #endif
 
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    void ExecuteSystemCommand(const std::string& command,
-                              const std::vector<std::string>& arguments);
-#endif
-
     bool IsInteger(const std::string& str);
 
     void CopyJsonWithoutComments(Json::Value& target,
@@ -215,15 +185,6 @@ namespace Orthanc
     bool StartsWith(const std::string& str,
                     const std::string& prefix);
 
-    int GetProcessId();
-
-#if !defined(ORTHANC_SANDBOXED) || ORTHANC_SANDBOXED != 1
-    bool IsRegularFile(const std::string& path);
-#endif
-
-    FILE* OpenFile(const std::string& path,
-                   FileMode mode);
-
     void UriEncode(std::string& target,
                    const std::string& source);
 
@@ -242,5 +203,9 @@ namespace Orthanc
     unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json,
                                              const std::string& key,
                                              unsigned int defaultValue);
+
+    bool IsUuid(const std::string& str);
+
+    bool StartsWithUuid(const std::string& str);
   }
 }
diff --git a/Framework/Orthanc/Core/WebServiceParameters.cpp b/Resources/Orthanc/Core/WebServiceParameters.cpp
similarity index 96%
rename from Framework/Orthanc/Core/WebServiceParameters.cpp
rename to Resources/Orthanc/Core/WebServiceParameters.cpp
index 52e0ea6..cef2e26 100644
--- a/Framework/Orthanc/Core/WebServiceParameters.cpp
+++ b/Resources/Orthanc/Core/WebServiceParameters.cpp
@@ -37,6 +37,10 @@
 #include "../Core/Toolbox.h"
 #include "../Core/OrthancException.h"
 
+#if ORTHANC_SANDBOXED == 0
+#  include "../Core/SystemToolbox.h"
+#endif
+
 #include <cassert>
 
 namespace Orthanc
@@ -57,6 +61,7 @@ namespace Orthanc
   }
 
 
+#if ORTHANC_SANDBOXED == 0
   void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
                                                   const std::string& certificateKeyFile,
                                                   const std::string& certificateKeyPassword)
@@ -66,14 +71,14 @@ namespace Orthanc
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    if (!Toolbox::IsRegularFile(certificateFile))
+    if (!SystemToolbox::IsRegularFile(certificateFile))
     {
       LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
       throw OrthancException(ErrorCode_InexistentFile);
     }
 
     if (!certificateKeyFile.empty() && 
-        !Toolbox::IsRegularFile(certificateKeyFile))
+        !SystemToolbox::IsRegularFile(certificateKeyFile))
     {
       LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
       throw OrthancException(ErrorCode_InexistentFile);
@@ -84,6 +89,7 @@ namespace Orthanc
     certificateKeyFile_ = certificateKeyFile;
     certificateKeyPassword_ = certificateKeyPassword;
   }
+#endif
 
 
   static void AddTrailingSlash(std::string& url)
@@ -171,12 +177,14 @@ namespace Orthanc
     SetUsername(GetStringMember(peer, "Username", ""));
     SetPassword(GetStringMember(peer, "Password", ""));
 
+#if ORTHANC_SANDBOXED == 0
     if (peer.isMember("CertificateFile"))
     {
       SetClientCertificate(GetStringMember(peer, "CertificateFile", ""),
                            GetStringMember(peer, "CertificateKeyFile", ""),
                            GetStringMember(peer, "CertificateKeyPassword", ""));
     }
+#endif
 
     if (peer.isMember("Pkcs11"))
     {
diff --git a/Framework/Orthanc/Core/WebServiceParameters.h b/Resources/Orthanc/Core/WebServiceParameters.h
similarity index 96%
rename from Framework/Orthanc/Core/WebServiceParameters.h
rename to Resources/Orthanc/Core/WebServiceParameters.h
index 40d6cf9..b6b373f 100644
--- a/Framework/Orthanc/Core/WebServiceParameters.h
+++ b/Resources/Orthanc/Core/WebServiceParameters.h
@@ -32,6 +32,10 @@
 
 #pragma once
 
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
 #include <string>
 #include <json/json.h>
 
@@ -88,9 +92,11 @@ namespace Orthanc
 
     void ClearClientCertificate();
 
+#if ORTHANC_SANDBOXED == 0
     void SetClientCertificate(const std::string& certificateFile,
                               const std::string& certificateKeyFile,
                               const std::string& certificateKeyPassword);
+#endif
 
     const std::string& GetCertificateFile() const
     {
diff --git a/Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp b/Resources/Orthanc/OrthancServer/FromDcmtkBridge.cpp
similarity index 85%
rename from Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp
rename to Resources/Orthanc/OrthancServer/FromDcmtkBridge.cpp
index fde86e2..243dcc0 100644
--- a/Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp
+++ b/Resources/Orthanc/OrthancServer/FromDcmtkBridge.cpp
@@ -39,8 +39,9 @@
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
 #include "../Core/Logging.h"
+#include "../Core/SystemToolbox.h"
 #include "../Core/Toolbox.h"
-#include "../Core/Uuid.h"
+#include "../Core/TemporaryFile.h"
 #include "../Core/OrthancException.h"
 
 #include <list>
@@ -118,7 +119,7 @@ namespace Orthanc
     std::string content;
     EmbeddedResources::GetFileResource(content, resource);
 
-    Toolbox::TemporaryFile tmp;
+    TemporaryFile tmp;
     tmp.Write(content);
 
     if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
@@ -144,7 +145,6 @@ namespace Orthanc
       throw OrthancException(ErrorCode_InternalError);
     }
   }
-                            
 #endif
 
 
@@ -178,7 +178,7 @@ namespace Orthanc
   }
 
 
-  void FromDcmtkBridge::InitializeDictionary()
+  void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary)
   {
     {
       DictionaryLocker locker;
@@ -195,7 +195,16 @@ namespace Orthanc
       //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE);
 
       LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM);
-      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE);
+
+      if (loadPrivateDictionary)
+      {
+        LOG(INFO) << "Loading the embedded dictionary of private tags";
+        LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE);
+      }
+      else
+      {
+        LOG(INFO) << "The dictionary of private tags has not been loaded";
+      }
 
 #elif defined(__linux__) || defined(__FreeBSD_kernel__)
       std::string path = DCMTK_DICTIONARY_DIR;
@@ -207,7 +216,15 @@ namespace Orthanc
       }
 
       LoadExternalDictionary(*locker, path, "dicom.dic");
-      LoadExternalDictionary(*locker, path, "private.dic");
+
+      if (loadPrivateDictionary)
+      {
+        LoadExternalDictionary(*locker, path, "private.dic");
+      }
+      else
+      {
+        LOG(INFO) << "The dictionary of private tags has not been loaded";
+      }
 
 #else
 #error Support your platform here
@@ -237,7 +254,8 @@ namespace Orthanc
                                               ValueRepresentation vr,
                                               const std::string& name,
                                               unsigned int minMultiplicity,
-                                              unsigned int maxMultiplicity)
+                                              unsigned int maxMultiplicity,
+                                              const std::string& privateCreator)
   {
     if (minMultiplicity < 1)
     {
@@ -261,20 +279,68 @@ namespace Orthanc
               << name << " (multiplicity: " << minMultiplicity << "-" 
               << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
 
-    std::auto_ptr<DcmDictEntry>  entry(new DcmDictEntry(tag.GetGroup(),
-                                                        tag.GetElement(),
-                                                        evr, name.c_str(),
-                                                        static_cast<int>(minMultiplicity),
-                                                        static_cast<int>(maxMultiplicity),
-                                                        NULL    /* version */,
-                                                        OFTrue  /* doCopyString */,
-                                                        NULL    /* private creator */));
+    std::auto_ptr<DcmDictEntry>  entry;
+    if (privateCreator.empty())
+    {
+      if (tag.GetGroup() % 2 == 1)
+      {
+        char buf[128];
+        sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), "
+                "but no private creator was associated with it", 
+                tag.GetGroup(), tag.GetElement());
+        LOG(WARNING) << buf;
+      }
+
+      entry.reset(new DcmDictEntry(tag.GetGroup(),
+                                   tag.GetElement(),
+                                   evr, name.c_str(),
+                                   static_cast<int>(minMultiplicity),
+                                   static_cast<int>(maxMultiplicity),
+                                   NULL    /* version */,
+                                   OFTrue  /* doCopyString */,
+                                   NULL    /* private creator */));
+    }
+    else
+    {
+      // "Private Data Elements have an odd Group Number that is not
+      // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or
+      // (FFFF,eeee)."
+      if (tag.GetGroup() % 2 == 0 /* even */ ||
+          tag.GetGroup() == 0x0001 ||
+          tag.GetGroup() == 0x0003 ||
+          tag.GetGroup() == 0x0005 ||
+          tag.GetGroup() == 0x0007 ||
+          tag.GetGroup() == 0xffff)
+      {
+        char buf[128];
+        sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009",
+                tag.GetGroup(), tag.GetElement());
+        LOG(ERROR) << buf;
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      entry.reset(new DcmDictEntry(tag.GetGroup(),
+                                   tag.GetElement(),
+                                   evr, name.c_str(),
+                                   static_cast<int>(minMultiplicity),
+                                   static_cast<int>(maxMultiplicity),
+                                   "private" /* version */,
+                                   OFTrue    /* doCopyString */,
+                                   privateCreator.c_str()));
+    }
 
     entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
     entry->setElementRangeRestriction(DcmDictRange_Unspecified);
 
     {
       DictionaryLocker locker;
+
+      if (locker->findEntry(name.c_str()))
+      {
+        LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\"";
+        throw OrthancException(ErrorCode_AlreadyExistingTag);
+      }
+
       locker->addEntry(entry.release());
     }
   }
@@ -314,10 +380,10 @@ namespace Orthanc
   }
 
 
-  void FromDcmtkBridge::Convert(DicomMap& target, 
-                                DcmItem& dataset,
-                                unsigned int maxStringLength,
-                                Encoding defaultEncoding)
+  void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
+                                            DcmItem& dataset,
+                                            unsigned int maxStringLength,
+                                            Encoding defaultEncoding)
   {
     Encoding encoding = DetectEncoding(dataset, defaultEncoding);
 
@@ -374,7 +440,7 @@ namespace Orthanc
         if (maxStringLength != 0 &&
             utf8.size() > maxStringLength)
         {
-          return new DicomValue;  // Create a NULL value
+          return new DicomValue;  // Too long, create a NULL value
         }
         else
         {
@@ -383,6 +449,47 @@ namespace Orthanc
       }
     }
 
+
+    if (element.getVR() == EVR_UN)
+    {
+      // Unknown value representation: Lookup in the dictionary. This
+      // is notably the case for private tags registered with the
+      // "Dictionary" configuration option.
+      DictionaryLocker locker;
+      
+      const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), 
+                                                    element.getTag().getPrivateCreator());
+      if (entry != NULL && 
+          entry->getVR().isaString())
+      {
+        Uint8* data = NULL;
+
+        // At (*), we do not try and convert to UTF-8, as nothing says
+        // the encoding of the private tag is the same as that of the
+        // remaining of the DICOM dataset. Only go for ASCII strings.
+
+        if (element.getUint8Array(data) == EC_Normal &&
+            Toolbox::IsAsciiString(data, element.getLength()))   // (*)
+        {
+          if (data == NULL)
+          {
+            return new DicomValue("", false);   // Empty string
+          }
+          else if (maxStringLength != 0 &&
+                   element.getLength() > maxStringLength)
+          {
+            return new DicomValue;  // Too long, create a NULL value
+          }
+          else
+          {
+            std::string s(reinterpret_cast<const char*>(data), element.getLength());
+            return new DicomValue(s, false);
+          }
+        }
+      }
+    }
+
+
     try
     {
       // http://support.dcmtk.org/docs/dcvr_8h-source.html
@@ -429,7 +536,7 @@ namespace Orthanc
         }
     
         /**
-         * Numberic types
+         * Numeric types
          **/ 
       
         case EVR_SL:  // signed long
@@ -571,7 +678,7 @@ namespace Orthanc
     }
 
     // This code gives access to the name of the private tags
-    const std::string tagName = FromDcmtkBridge::GetName(tag);
+    std::string tagName = FromDcmtkBridge::GetTagName(element);
     
     switch (format)
     {
@@ -692,20 +799,12 @@ namespace Orthanc
   }                              
 
 
-  static void DatasetToJson(Json::Value& parent,
-                            DcmItem& item,
-                            DicomToJsonFormat format,
-                            DicomToJsonFlags flags,
-                            unsigned int maxStringLength,
-                            Encoding encoding);
-
-
-  void FromDcmtkBridge::ToJson(Json::Value& parent,
-                               DcmElement& element,
-                               DicomToJsonFormat format,
-                               DicomToJsonFlags flags,
-                               unsigned int maxStringLength,
-                               Encoding encoding)
+  void FromDcmtkBridge::ElementToJson(Json::Value& parent,
+                                      DcmElement& element,
+                                      DicomToJsonFormat format,
+                                      DicomToJsonFlags flags,
+                                      unsigned int maxStringLength,
+                                      Encoding encoding)
   {
     if (parent.type() == Json::nullValue)
     {
@@ -717,7 +816,8 @@ namespace Orthanc
 
     if (element.isLeaf())
     {
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, maxStringLength, encoding));
+      // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding));
       LeafValueToJson(target, *v, format, flags, maxStringLength);
     }
     else
@@ -740,12 +840,12 @@ namespace Orthanc
   }
 
 
-  static void DatasetToJson(Json::Value& parent,
-                            DcmItem& item,
-                            DicomToJsonFormat format,
-                            DicomToJsonFlags flags,
-                            unsigned int maxStringLength,
-                            Encoding encoding)
+  void FromDcmtkBridge::DatasetToJson(Json::Value& parent,
+                                      DcmItem& item,
+                                      DicomToJsonFormat format,
+                                      DicomToJsonFlags flags,
+                                      unsigned int maxStringLength,
+                                      Encoding encoding)
   {
     assert(parent.type() == Json::objectValue);
 
@@ -790,47 +890,53 @@ namespace Orthanc
         }
       }
 
-      FromDcmtkBridge::ToJson(parent, *element, format, flags, maxStringLength, encoding);
+      FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding);
     }
   }
 
 
-  void FromDcmtkBridge::ToJson(Json::Value& target, 
-                               DcmDataset& dataset,
-                               DicomToJsonFormat format,
-                               DicomToJsonFlags flags,
-                               unsigned int maxStringLength,
-                               Encoding defaultEncoding)
+  void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, 
+                                           DcmDataset& dataset,
+                                           DicomToJsonFormat format,
+                                           DicomToJsonFlags flags,
+                                           unsigned int maxStringLength,
+                                           Encoding defaultEncoding)
   {
+    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
+
     target = Json::objectValue;
-    DatasetToJson(target, dataset, format, flags, maxStringLength, DetectEncoding(dataset, defaultEncoding));
+    DatasetToJson(target, dataset, format, flags, maxStringLength, encoding);
   }
 
 
-  void FromDcmtkBridge::ToJson(Json::Value& target, 
-                               DcmMetaInfo& dataset,
-                               DicomToJsonFormat format,
-                               DicomToJsonFlags flags,
-                               unsigned int maxStringLength)
+  void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, 
+                                            DcmMetaInfo& dataset,
+                                            DicomToJsonFormat format,
+                                            DicomToJsonFlags flags,
+                                            unsigned int maxStringLength)
   {
     target = Json::objectValue;
     DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii);
   }
 
 
-  std::string FromDcmtkBridge::GetName(const DicomTag& t)
+
+  static std::string GetTagNameInternal(DcmTag& tag)
   {
-    // Some patches for important tags because of different DICOM
-    // dictionaries between DCMTK versions
-    std::string n = t.GetMainTagsName();
-    if (n.size() != 0)
     {
-      return n;
+      // Some patches for important tags because of different DICOM
+      // dictionaries between DCMTK versions
+      DicomTag tmp(tag.getGroup(), tag.getElement());
+      std::string n = tmp.GetMainTagsName();
+      if (n.size() != 0)
+      {
+        return n;
+      }
+      // End of patches
     }
-    // End of patches
 
 #if 0
-    DcmTagKey tag(t.GetGroup(), t.GetElement());
+    // This version explicitly calls the dictionary
     const DcmDataDictionary& dict = dcmDataDict.rdlock();
     const DcmDictEntry* entry = dict.findEntry(tag, NULL);
 
@@ -843,7 +949,6 @@ namespace Orthanc
     dcmDataDict.unlock();
     return s;
 #else
-    DcmTag tag(t.GetGroup(), t.GetElement());
     const char* name = tag.getTagName();
     if (name == NULL)
     {
@@ -857,6 +962,31 @@ namespace Orthanc
   }
 
 
+  std::string FromDcmtkBridge::GetTagName(const DicomTag& t,
+                                          const std::string& privateCreator)
+  {
+    DcmTag tag(t.GetGroup(), t.GetElement());
+
+    if (!privateCreator.empty())
+    {
+      tag.setPrivateCreator(privateCreator.c_str());
+    }
+
+    return GetTagNameInternal(tag);
+  }
+
+
+  std::string FromDcmtkBridge::GetTagName(const DcmElement& element)
+  {
+    // Copy the tag to ensure const-correctness of DcmElement. Note
+    // that the private creator information is also copied.
+    DcmTag tag(element.getTag());  
+
+    return GetTagNameInternal(tag);
+  }
+
+
+
   DicomTag FromDcmtkBridge::ParseTag(const char* name)
   {
     if (strlen(name) == 9 &&
@@ -941,23 +1071,26 @@ namespace Orthanc
     for (DicomMap::Map::const_iterator 
            it = values.map_.begin(); it != values.map_.end(); ++it)
     {
+      // TODO Inject PrivateCreator if some is available in the DicomMap?
+      const std::string tagName = GetTagName(it->first, "");
+
       if (simplify)
       {
         if (it->second->IsNull())
         {
-          result[GetName(it->first)] = Json::nullValue;
+          result[tagName] = Json::nullValue;
         }
         else
         {
           // TODO IsBinary
-          result[GetName(it->first)] = it->second->GetContent();
+          result[tagName] = it->second->GetContent();
         }
       }
       else
       {
         Json::Value value = Json::objectValue;
 
-        value["Name"] = GetName(it->first);
+        value["Name"] = tagName;
 
         if (it->second->IsNull())
         {
@@ -987,7 +1120,7 @@ namespace Orthanc
         // The "PatientID" field is of type LO (Long String), 64
         // Bytes Maximum. An UUID is of length 36, thus it can be used
         // as a random PatientID.
-        return Toolbox::GenerateUuid();
+        return SystemToolbox::GenerateUuid();
 
       case ResourceType_Instance:
         return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
diff --git a/Framework/Orthanc/OrthancServer/FromDcmtkBridge.h b/Resources/Orthanc/OrthancServer/FromDcmtkBridge.h
similarity index 68%
rename from Framework/Orthanc/OrthancServer/FromDcmtkBridge.h
rename to Resources/Orthanc/OrthancServer/FromDcmtkBridge.h
index eff3b81..398f061 100644
--- a/Framework/Orthanc/OrthancServer/FromDcmtkBridge.h
+++ b/Resources/Orthanc/OrthancServer/FromDcmtkBridge.h
@@ -34,6 +34,7 @@
 
 #include "ServerEnumerations.h"
 
+#include "../Core/DicomFormat/DicomElement.h"
 #include "../Core/DicomFormat/DicomMap.h"
 
 #include <dcmtk/dcmdata/dcdatset.h>
@@ -42,27 +43,64 @@
 #include <dcmtk/dcmdata/dcfilefo.h>
 #include <json/json.h>
 
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+#  include <gtest/gtest_prod.h>
+#endif
+
+
 namespace Orthanc
 {
-  class FromDcmtkBridge
+  class FromDcmtkBridge : public boost::noncopyable
   {
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+    FRIEND_TEST(FromDcmtkBridge, FromJson);
+#endif
+
+    friend class ParsedDicomFile;
+    friend class Configuration;
+
+  private:
+    FromDcmtkBridge();  // Pure static class
+
+    static void ExtractDicomSummary(DicomMap& target, 
+                                    DcmItem& dataset,
+                                    unsigned int maxStringLength,
+                                    Encoding defaultEncoding);
+
+    static void DatasetToJson(Json::Value& parent,
+                              DcmItem& item,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength,
+                              Encoding encoding);
+
+    static void ElementToJson(Json::Value& parent,
+                              DcmElement& element,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength,
+                              Encoding dicomEncoding);
+
+    static void ExtractDicomAsJson(Json::Value& target, 
+                                   DcmDataset& dataset,
+                                   DicomToJsonFormat format,
+                                   DicomToJsonFlags flags,
+                                   unsigned int maxStringLength,
+                                   Encoding defaultEncoding);
+
   public:
-    static void InitializeDictionary();
+    static void InitializeDictionary(bool loadPrivateDictionary);
 
     static void RegisterDictionaryTag(const DicomTag& tag,
                                       ValueRepresentation vr,
                                       const std::string& name,
                                       unsigned int minMultiplicity,
-                                      unsigned int maxMultiplicity);
+                                      unsigned int maxMultiplicity,
+                                      const std::string& privateCreator);
 
     static Encoding DetectEncoding(DcmItem& dataset,
                                    Encoding defaultEncoding);
 
-    static void Convert(DicomMap& target, 
-                        DcmItem& dataset,
-                        unsigned int maxStringLength,
-                        Encoding defaultEncoding);
-
     static DicomTag Convert(const DcmTag& tag);
 
     static DicomTag GetTag(const DcmElement& element);
@@ -74,27 +112,21 @@ namespace Orthanc
                                           unsigned int maxStringLength,
                                           Encoding encoding);
 
-    static void ToJson(Json::Value& parent,
-                       DcmElement& element,
-                       DicomToJsonFormat format,
-                       DicomToJsonFlags flags,
-                       unsigned int maxStringLength,
-                       Encoding dicomEncoding);
-
-    static void ToJson(Json::Value& target, 
-                       DcmDataset& dataset,
-                       DicomToJsonFormat format,
-                       DicomToJsonFlags flags,
-                       unsigned int maxStringLength,
-                       Encoding defaultEncoding);
-
-    static void ToJson(Json::Value& target, 
-                       DcmMetaInfo& header,
-                       DicomToJsonFormat format,
-                       DicomToJsonFlags flags,
-                       unsigned int maxStringLength);
-
-    static std::string GetName(const DicomTag& tag);
+    static void ExtractHeaderAsJson(Json::Value& target, 
+                                    DcmMetaInfo& header,
+                                    DicomToJsonFormat format,
+                                    DicomToJsonFlags flags,
+                                    unsigned int maxStringLength);
+
+    static std::string GetTagName(const DicomTag& tag,
+                                  const std::string& privateCreator);
+
+    static std::string GetTagName(const DcmElement& element);
+
+    static std::string GetTagName(const DicomElement& element)
+    {
+      return GetTagName(element.GetTag(), "");
+    }
 
     static DicomTag ParseTag(const char* name);
 
diff --git a/Framework/Orthanc/OrthancServer/PrecompiledHeadersServer.h b/Resources/Orthanc/OrthancServer/PrecompiledHeadersServer.h
similarity index 100%
rename from Framework/Orthanc/OrthancServer/PrecompiledHeadersServer.h
rename to Resources/Orthanc/OrthancServer/PrecompiledHeadersServer.h
diff --git a/Framework/Orthanc/OrthancServer/ServerEnumerations.cpp b/Resources/Orthanc/OrthancServer/ServerEnumerations.cpp
similarity index 100%
rename from Framework/Orthanc/OrthancServer/ServerEnumerations.cpp
rename to Resources/Orthanc/OrthancServer/ServerEnumerations.cpp
diff --git a/Framework/Orthanc/OrthancServer/ServerEnumerations.h b/Resources/Orthanc/OrthancServer/ServerEnumerations.h
similarity index 100%
rename from Framework/Orthanc/OrthancServer/ServerEnumerations.h
rename to Resources/Orthanc/OrthancServer/ServerEnumerations.h
diff --git a/Framework/Orthanc/OrthancServer/ToDcmtkBridge.cpp b/Resources/Orthanc/OrthancServer/ToDcmtkBridge.cpp
similarity index 100%
rename from Framework/Orthanc/OrthancServer/ToDcmtkBridge.cpp
rename to Resources/Orthanc/OrthancServer/ToDcmtkBridge.cpp
diff --git a/Framework/Orthanc/OrthancServer/ToDcmtkBridge.h b/Resources/Orthanc/OrthancServer/ToDcmtkBridge.h
similarity index 100%
rename from Framework/Orthanc/OrthancServer/ToDcmtkBridge.h
rename to Resources/Orthanc/OrthancServer/ToDcmtkBridge.h
diff --git a/Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp b/Resources/Orthanc/Plugins/Engine/SharedLibrary.cpp
similarity index 99%
rename from Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp
rename to Resources/Orthanc/Plugins/Engine/SharedLibrary.cpp
index 7fefc49..1adc878 100644
--- a/Framework/Orthanc/Plugins/Engine/SharedLibrary.cpp
+++ b/Resources/Orthanc/Plugins/Engine/SharedLibrary.cpp
@@ -33,7 +33,7 @@
 #include "../../OrthancServer/PrecompiledHeadersServer.h"
 #include "SharedLibrary.h"
 
-#if ORTHANC_PLUGINS_ENABLED != 1
+#if ORTHANC_ENABLE_PLUGINS != 1
 #error The plugin support is disabled
 #endif
 
diff --git a/Framework/Orthanc/Plugins/Engine/SharedLibrary.h b/Resources/Orthanc/Plugins/Engine/SharedLibrary.h
similarity index 98%
copy from Framework/Orthanc/Plugins/Engine/SharedLibrary.h
copy to Resources/Orthanc/Plugins/Engine/SharedLibrary.h
index 5a7913a..2bf9a69 100644
--- a/Framework/Orthanc/Plugins/Engine/SharedLibrary.h
+++ b/Resources/Orthanc/Plugins/Engine/SharedLibrary.h
@@ -32,7 +32,7 @@
 
 #pragma once
 
-#if ORTHANC_PLUGINS_ENABLED == 1
+#if ORTHANC_ENABLE_PLUGINS == 1
 
 #include "../../Core/OrthancException.h"
 
diff --git a/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp b/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp
new file mode 100644
index 0000000..b354bb1
--- /dev/null
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomDatasetReader.h"
+
+#include "OrthancPluginCppWrapper.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancPlugins
+{
+  // This function is copied-pasted from "../../../Core/Toolbox.cpp",
+  // in order to avoid the dependency of plugins against the Orthanc core
+  static std::string StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  DicomDatasetReader::DicomDatasetReader(IDicomDataset* dataset) :  // takes ownership
+    dataset_(dataset)
+  {
+    if (dataset == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+  
+
+  std::string DicomDatasetReader::GetMandatoryStringValue(const DicomPath& path) const
+  {
+    std::string s;
+    if (dataset_->GetStringValue(s, path))
+    {
+      return s;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_InexistentTag);
+    }
+  }
+
+
+  int DicomDatasetReader::GetIntegerValue(const DicomPath& path)
+  {
+    try
+    {
+      std::string s = StripSpaces(GetMandatoryStringValue(path));
+      return boost::lexical_cast<int>(s);
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);        
+    }
+  }
+
+
+  unsigned int DicomDatasetReader::GetUnsignedIntegerValue(const DicomPath& path)
+  {
+    int value = GetIntegerValue(path);
+
+    if (value >= 0)
+    {
+      return static_cast<unsigned int>(value);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.h b/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h
similarity index 77%
copy from Framework/Orthanc/Core/DicomFormat/DicomArray.h
copy to Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h
index a223685..0074071 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomArray.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h
@@ -32,35 +32,29 @@
 
 #pragma once
 
-#include "DicomElement.h"
-#include "DicomMap.h"
+#include "IDicomDataset.h"
 
-#include <vector>
+#include <memory>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class DicomArray : public boost::noncopyable
+  class DicomDatasetReader : public boost::noncopyable
   {
   private:
-    typedef std::vector<DicomElement*>  Elements;
-
-    Elements  elements_;
+    std::auto_ptr<IDicomDataset>  dataset_;
 
   public:
-    DicomArray(const DicomMap& map);
-
-    ~DicomArray();
+    DicomDatasetReader(IDicomDataset* dataset);  // takes ownership
 
-    size_t GetSize() const
+    IDicomDataset& GetDataset() const
     {
-      return elements_.size();
+      return *dataset_;
     }
 
-    const DicomElement& GetElement(size_t i) const
-    {
-      return *elements_[i];
-    }
+    std::string GetMandatoryStringValue(const DicomPath& path) const;
+
+    int GetIntegerValue(const DicomPath& path);
 
-    void Print(FILE* fp) const;
+    unsigned int GetUnsignedIntegerValue(const DicomPath& path);
   };
 }
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp b/Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp
similarity index 57%
copy from Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
copy to Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp
index 32a17b5..3437e60 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp
@@ -30,64 +30,57 @@
  **/
 
 
-#include "../PrecompiledHeaders.h"
-#include "DicomValue.h"
+#include "DicomPath.h"
 
-#include "../OrthancException.h"
-#include "../Toolbox.h"
+#include "OrthancPluginCppWrapper.h"
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  DicomValue::DicomValue(const DicomValue& other) : 
-    type_(other.type_),
-    content_(other.content_)
+  const DicomPath::Prefix& DicomPath::GetPrefixItem(size_t depth) const
   {
-  }
-
-
-  DicomValue::DicomValue(const std::string& content,
-                         bool isBinary) :
-    type_(isBinary ? Type_Binary : Type_String),
-    content_(content)
-  {
-  }
-  
-  
-  DicomValue::DicomValue(const char* data,
-                         size_t size,
-                         bool isBinary) :
-    type_(isBinary ? Type_Binary : Type_String)
-  {
-    content_.assign(data, size);
-  }
-    
-  
-  const std::string& DicomValue::GetContent() const
-  {
-    if (type_ == Type_Null)
+    if (depth >= prefix_.size())
     {
-      throw OrthancException(ErrorCode_BadParameterType);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
     else
     {
-      return content_;
+      return prefix_[depth];
     }
   }
 
 
-  DicomValue* DicomValue::Clone() const
+  DicomPath::DicomPath(const DicomTag& sequence,
+                       size_t index,
+                       const DicomTag& tag) :
+    finalTag_(tag)
   {
-    return new DicomValue(*this);
+    AddToPrefix(sequence, index);
   }
 
-  
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-  void DicomValue::FormatDataUriScheme(std::string& target,
-                                       const std::string& mime) const
+
+  DicomPath::DicomPath(const DicomTag& sequence1,
+                       size_t index1,
+                       const DicomTag& sequence2,
+                       size_t index2,
+                       const DicomTag& tag) :
+    finalTag_(tag)
   {
-    Toolbox::EncodeBase64(target, GetContent());
-    target.insert(0, "data:" + mime + ";base64,");
+    AddToPrefix(sequence1, index1);
+    AddToPrefix(sequence2, index2);
   }
-#endif
 
+
+  DicomPath::DicomPath(const DicomTag& sequence1,
+                       size_t index1,
+                       const DicomTag& sequence2,
+                       size_t index2,
+                       const DicomTag& sequence3,
+                       size_t index3,
+                       const DicomTag& tag) :
+    finalTag_(tag)
+  {
+    AddToPrefix(sequence1, index1);
+    AddToPrefix(sequence2, index2);
+    AddToPrefix(sequence3, index3);
+  }
 }
diff --git a/Framework/Orthanc/Core/Images/ImageBuffer.h b/Resources/Orthanc/Plugins/Samples/Common/DicomPath.h
similarity index 56%
rename from Framework/Orthanc/Core/Images/ImageBuffer.h
rename to Resources/Orthanc/Plugins/Samples/Common/DicomPath.h
index 21b1b15..f14f051 100644
--- a/Framework/Orthanc/Core/Images/ImageBuffer.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomPath.h
@@ -32,83 +32,76 @@
 
 #pragma once
 
-#include "ImageAccessor.h"
+#include "DicomTag.h"
 
 #include <vector>
-#include <stdint.h>
-#include <boost/noncopyable.hpp>
+#include <stddef.h>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class ImageBuffer : public boost::noncopyable
+  class DicomPath
   {
   private:
-    bool changed_;
+    typedef std::pair<DicomTag, size_t>  Prefix;
 
-    bool forceMinimalPitch_;  // Currently unused
-    PixelFormat format_;
-    unsigned int width_;
-    unsigned int height_;
-    unsigned int pitch_;
-    void *buffer_;
+    std::vector<Prefix>  prefix_;
+    DicomTag             finalTag_;
 
-    void Initialize();
-    
-    void Allocate();
-
-    void Deallocate();
+    const Prefix& GetPrefixItem(size_t depth) const;
 
   public:
-    ImageBuffer(PixelFormat format,
-                unsigned int width,
-                unsigned int height,
-                bool forceMinimalPitch);
-
-    ImageBuffer()
+    DicomPath(const DicomTag& finalTag) :
+    finalTag_(finalTag)
     {
-      Initialize();
     }
 
-    ~ImageBuffer()
+    DicomPath(const DicomTag& sequence,
+              size_t index,
+              const DicomTag& tag);
+
+    DicomPath(const DicomTag& sequence1,
+              size_t index1,
+              const DicomTag& sequence2,
+              size_t index2,
+              const DicomTag& tag);
+
+    DicomPath(const DicomTag& sequence1,
+              size_t index1,
+              const DicomTag& sequence2,
+              size_t index2,
+              const DicomTag& sequence3,
+              size_t index3,
+              const DicomTag& tag);
+
+    void AddToPrefix(const DicomTag& tag,
+                     size_t position)
     {
-      Deallocate();
+      prefix_.push_back(std::make_pair(tag, position));
     }
 
-    PixelFormat GetFormat() const
+    size_t GetPrefixLength() const
     {
-      return format_;
+      return prefix_.size();
     }
-
-    void SetFormat(PixelFormat format);
-
-    unsigned int GetWidth() const
+    
+    DicomTag GetPrefixTag(size_t depth) const
     {
-      return width_;
+      return GetPrefixItem(depth).first;
     }
 
-    void SetWidth(unsigned int width);
-
-    unsigned int GetHeight() const
+    size_t GetPrefixIndex(size_t depth) const
     {
-      return height_;
+      return GetPrefixItem(depth).second;
     }
-
-    void SetHeight(unsigned int height);
-
-    unsigned int GetBytesPerPixel() const
+    
+    const DicomTag& GetFinalTag() const
     {
-      return ::Orthanc::GetBytesPerPixel(format_);
+      return finalTag_;
     }
 
-    ImageAccessor GetAccessor();
-
-    ImageAccessor GetConstAccessor();
-
-    bool IsMinimalPitchForced() const
+    void SetFinalTag(const DicomTag& tag)
     {
-      return forceMinimalPitch_;
+      finalTag_ = tag;
     }
-
-    void AcquireOwnership(ImageBuffer& other);
   };
 }
diff --git a/Resources/Orthanc/Plugins/Samples/Common/DicomTag.cpp b/Resources/Orthanc/Plugins/Samples/Common/DicomTag.cpp
new file mode 100644
index 0000000..040d885
--- /dev/null
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomTag.cpp
@@ -0,0 +1,110 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomTag.h"
+
+#include "OrthancPluginCppWrapper.h"
+
+namespace OrthancPlugins
+{
+  const char* DicomTag::GetName() const
+  {
+    if (*this == DICOM_TAG_BITS_STORED)
+    {
+      return "BitsStored";
+    }
+    else if (*this == DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX)
+    {
+      return "ColumnPositionInTotalImagePixelMatrix";
+    }
+    else if (*this == DICOM_TAG_COLUMNS)
+    {
+      return "Columns";
+    }
+    else if (*this == DICOM_TAG_MODALITY)
+    {
+      return "Modality";
+    }
+    else if (*this == DICOM_TAG_NUMBER_OF_FRAMES)
+    {
+      return "NumberOfFrames";
+    }
+    else if (*this == DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE)
+    {
+      return "PerFrameFunctionalGroupsSequence";
+    }
+    else if (*this == DICOM_TAG_PHOTOMETRIC_INTERPRETATION)
+    {
+      return "PhotometricInterpretation";
+    }
+    else if (*this == DICOM_TAG_PIXEL_REPRESENTATION)
+    {
+      return "PixelRepresentation";
+    }
+    else if (*this == DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE)
+    {
+      return "PlanePositionSlideSequence";
+    }
+    else if (*this == DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX)
+    {
+      return "RowPositionInTotalImagePixelMatrix";
+    }
+    else if (*this == DICOM_TAG_ROWS)
+    {
+      return "Rows";
+    }
+    else if (*this == DICOM_TAG_SOP_CLASS_UID)
+    {
+      return "SOPClassUID";
+    }
+    else if (*this == DICOM_TAG_SAMPLES_PER_PIXEL)
+    {
+      return "SamplesPerPixel";
+    }
+    else if (*this == DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS)
+    {
+      return "TotalPixelMatrixColumns";
+    }
+    else if (*this == DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS)
+    {
+      return "TotalPixelMatrixRows";
+    }
+    else if (*this == DICOM_TAG_TRANSFER_SYNTAX_UID)
+    {
+      return "TransferSyntaxUID";
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_NotImplemented);
+    }
+  }
+}
diff --git a/Resources/Orthanc/Plugins/Samples/Common/DicomTag.h b/Resources/Orthanc/Plugins/Samples/Common/DicomTag.h
new file mode 100644
index 0000000..660d078
--- /dev/null
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomTag.h
@@ -0,0 +1,95 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <stdint.h>
+
+namespace OrthancPlugins
+{
+  class DicomTag
+  {
+  private:
+    uint16_t  group_;
+    uint16_t  element_;
+
+    DicomTag();  // Forbidden
+
+  public:
+    DicomTag(uint16_t group,
+             uint16_t element) :
+      group_(group),
+      element_(element)
+    {
+    }
+
+    uint16_t GetGroup() const
+    {
+      return group_;
+    }
+
+    uint16_t GetElement() const
+    {
+      return element_;
+    }
+
+    const char* GetName() const;
+
+    bool operator== (const DicomTag& other) const
+    {
+      return group_ == other.group_ && element_ == other.element_;
+    }
+
+    bool operator!= (const DicomTag& other) const
+    {
+      return !(*this == other);
+    }
+  };
+
+
+  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
+  static const DicomTag DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021e);
+  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060);
+  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE(0x5200, 0x9230);
+  static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE(0x0048, 0x021a);
+  static const DicomTag DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021f);
+  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
+  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+  static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS(0x0048, 0x0006);
+  static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS(0x0048, 0x0007);
+  static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010);
+}
diff --git a/Framework/Orthanc/Plugins/Samples/Common/ExportedSymbols.list b/Resources/Orthanc/Plugins/Samples/Common/ExportedSymbols.list
similarity index 100%
rename from Framework/Orthanc/Plugins/Samples/Common/ExportedSymbols.list
rename to Resources/Orthanc/Plugins/Samples/Common/ExportedSymbols.list
diff --git a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp
new file mode 100644
index 0000000..638654b
--- /dev/null
+++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp
@@ -0,0 +1,196 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "FullOrthancDataset.h"
+
+#include "OrthancPluginCppWrapper.h"
+
+namespace OrthancPlugins
+{
+  static const Json::Value* AccessTag(const Json::Value& dataset,
+                                      const DicomTag& tag) 
+  {
+    if (dataset.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+
+    char name[16];
+    sprintf(name, "%04x,%04x", tag.GetGroup(), tag.GetElement());
+
+    if (!dataset.isMember(name))
+    {
+      return NULL;
+    }
+
+    const Json::Value& value = dataset[name];
+    if (value.type() != Json::objectValue ||
+        !value.isMember("Name") ||
+        !value.isMember("Type") ||
+        !value.isMember("Value") ||
+        value["Name"].type() != Json::stringValue ||
+        value["Type"].type() != Json::stringValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+
+    return &value;
+  }
+
+
+  static const Json::Value& GetSequenceContent(const Json::Value& sequence)
+  {
+    assert(sequence.type() == Json::objectValue);
+    assert(sequence.isMember("Type"));
+    assert(sequence.isMember("Value"));
+
+    const Json::Value& value = sequence["Value"];
+      
+    if (sequence["Type"].asString() != "Sequence" ||
+        value.type() != Json::arrayValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      return value;
+    }
+  }
+
+
+  static bool GetStringInternal(std::string& result,
+                                const Json::Value& tag)
+  {
+    assert(tag.type() == Json::objectValue);
+    assert(tag.isMember("Type"));
+    assert(tag.isMember("Value"));
+
+    const Json::Value& value = tag["Value"];
+      
+    if (tag["Type"].asString() != "String" ||
+        value.type() != Json::stringValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      result = value.asString();
+      return true;
+    }
+  }
+
+
+  const Json::Value* FullOrthancDataset::LookupPath(const DicomPath& path) const
+  {
+    const Json::Value* content = &root_;
+                                  
+    for (unsigned int depth = 0; depth < path.GetPrefixLength(); depth++)
+    {
+      const Json::Value* sequence = AccessTag(*content, path.GetPrefixTag(depth));
+      if (sequence == NULL)
+      {
+        return NULL;
+      }
+
+      const Json::Value& nextContent = GetSequenceContent(*sequence);
+
+      size_t index = path.GetPrefixIndex(depth);
+      if (index >= nextContent.size())
+      {
+        return NULL;
+      }
+      else
+      {
+        content = &nextContent[static_cast<Json::Value::ArrayIndex>(index)];
+      }
+    }
+
+    return AccessTag(*content, path.GetFinalTag());
+  }
+
+
+  void FullOrthancDataset::CheckRoot() const
+  {
+    if (root_.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+  }
+
+
+  FullOrthancDataset::FullOrthancDataset(IOrthancConnection& orthanc,
+                                         const std::string& uri)
+  {
+    IOrthancConnection::RestApiGet(root_, orthanc, uri);
+    CheckRoot();
+  }
+
+
+  FullOrthancDataset::FullOrthancDataset(const std::string& content)
+  {
+    IOrthancConnection::ParseJson(root_, content);
+    CheckRoot();
+  }
+
+
+  bool FullOrthancDataset::GetStringValue(std::string& result,
+                                          const DicomPath& path) const
+  {
+    const Json::Value* value = LookupPath(path);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return GetStringInternal(result, *value);
+    }
+  }
+
+
+  bool FullOrthancDataset::GetSequenceSize(size_t& size,
+                                           const DicomPath& path) const
+  {
+    const Json::Value* sequence = LookupPath(path);
+
+    if (sequence == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      size = GetSequenceContent(*sequence).size();
+      return true;
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.h b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h
similarity index 71%
copy from Framework/Orthanc/Core/DicomFormat/DicomArray.h
copy to Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h
index a223685..18ab444 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomArray.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h
@@ -32,35 +32,32 @@
 
 #pragma once
 
-#include "DicomElement.h"
-#include "DicomMap.h"
+#include "IOrthancConnection.h"
+#include "IDicomDataset.h"
 
-#include <vector>
+#include <json/value.h>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class DicomArray : public boost::noncopyable
+  class FullOrthancDataset : public IDicomDataset
   {
   private:
-    typedef std::vector<DicomElement*>  Elements;
+    Json::Value   root_;
 
-    Elements  elements_;
+    const Json::Value* LookupPath(const DicomPath& path) const;
 
-  public:
-    DicomArray(const DicomMap& map);
+    void CheckRoot() const;
 
-    ~DicomArray();
+  public:
+    FullOrthancDataset(IOrthancConnection& orthanc,
+                       const std::string& uri);
 
-    size_t GetSize() const
-    {
-      return elements_.size();
-    }
+    FullOrthancDataset(const std::string& content);
 
-    const DicomElement& GetElement(size_t i) const
-    {
-      return *elements_[i];
-    }
+    virtual bool GetStringValue(std::string& result,
+                                const DicomPath& path) const;
 
-    void Print(FILE* fp) const;
+    virtual bool GetSequenceSize(size_t& size,
+                                 const DicomPath& path) const;
   };
 }
diff --git a/Framework/Orthanc/Core/Images/JpegReader.h b/Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h
similarity index 81%
rename from Framework/Orthanc/Core/Images/JpegReader.h
rename to Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h
index 5cb5551..7fc549e 100644
--- a/Framework/Orthanc/Core/Images/JpegReader.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h
@@ -32,26 +32,24 @@
 
 #pragma once
 
-#include "ImageAccessor.h"
+#include "DicomPath.h"
 
-#include <string>
 #include <boost/noncopyable.hpp>
+#include <string>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class JpegReader : 
-    public ImageAccessor,
-    public boost::noncopyable
+  class IDicomDataset : public boost::noncopyable
   {
-  private:
-    std::string  content_;
-
   public:
-    void ReadFromFile(const std::string& filename);
+    virtual ~IDicomDataset()
+    {
+    }
 
-    void ReadFromMemory(const void* buffer,
-                        size_t size);
+    virtual bool GetStringValue(std::string& result,
+                                const DicomPath& path) const = 0;
 
-    void ReadFromMemory(const std::string& buffer);
+    virtual bool GetSequenceSize(size_t& size,
+                                 const DicomPath& path) const = 0;
   };
 }
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp b/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp
similarity index 54%
rename from Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
rename to Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp
index 32a17b5..5a66912 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomValue.cpp
+++ b/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp
@@ -30,64 +30,54 @@
  **/
 
 
-#include "../PrecompiledHeaders.h"
-#include "DicomValue.h"
+#include "IOrthancConnection.h"
 
-#include "../OrthancException.h"
-#include "../Toolbox.h"
-
-namespace Orthanc
-{
-  DicomValue::DicomValue(const DicomValue& other) : 
-    type_(other.type_),
-    content_(other.content_)
-  {
-  }
+#include "OrthancPluginCppWrapper.h"
 
+#include <json/reader.h>
 
-  DicomValue::DicomValue(const std::string& content,
-                         bool isBinary) :
-    type_(isBinary ? Type_Binary : Type_String),
-    content_(content)
-  {
-  }
-  
-  
-  DicomValue::DicomValue(const char* data,
-                         size_t size,
-                         bool isBinary) :
-    type_(isBinary ? Type_Binary : Type_String)
+namespace OrthancPlugins
+{
+  void IOrthancConnection::ParseJson(Json::Value& result,
+                                     const std::string& content)
   {
-    content_.assign(data, size);
-  }
+    Json::Reader reader;
     
-  
-  const std::string& DicomValue::GetContent() const
-  {
-    if (type_ == Type_Null)
-    {
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-    else
+    if (!reader.parse(content, result))
     {
-      return content_;
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
   }
 
 
-  DicomValue* DicomValue::Clone() const
+  void IOrthancConnection::RestApiGet(Json::Value& result,
+                                      IOrthancConnection& orthanc,
+                                      const std::string& uri)
   {
-    return new DicomValue(*this);
+    std::string content;
+    orthanc.RestApiGet(content, uri);
+    ParseJson(result, content);
   }
 
-  
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-  void DicomValue::FormatDataUriScheme(std::string& target,
-                                       const std::string& mime) const
+
+  void IOrthancConnection::RestApiPost(Json::Value& result,
+                                       IOrthancConnection& orthanc,
+                                       const std::string& uri,
+                                       const std::string& body)
   {
-    Toolbox::EncodeBase64(target, GetContent());
-    target.insert(0, "data:" + mime + ";base64,");
+    std::string content;
+    orthanc.RestApiPost(content, uri, body);
+    ParseJson(result, content);
   }
-#endif
 
+
+  void IOrthancConnection::RestApiPut(Json::Value& result,
+                                      IOrthancConnection& orthanc,
+                                      const std::string& uri,
+                                      const std::string& body)
+  {
+    std::string content;
+    orthanc.RestApiPut(content, uri, body);
+    ParseJson(result, content);
+  }
 }
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomValue.h b/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h
similarity index 56%
rename from Framework/Orthanc/Core/DicomFormat/DicomValue.h
rename to Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h
index ad0ec54..f64dd5a 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomValue.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h
@@ -32,60 +32,49 @@
 
 #pragma once
 
-#include <string>
+#include "DicomPath.h"
+
 #include <boost/noncopyable.hpp>
+#include <string>
+#include <json/value.h>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class DicomValue : public boost::noncopyable
+  class IOrthancConnection : public boost::noncopyable
   {
-  private:
-    enum Type
+  public:
+    virtual ~IOrthancConnection()
     {
-      Type_Null,
-      Type_String,
-      Type_Binary
-    };
+    }
 
-    Type         type_;
-    std::string  content_;
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri) = 0;
 
-    DicomValue(const DicomValue& other);
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body) = 0;
 
-  public:
-    DicomValue() : type_(Type_Null)
-    {
-    }
-    
-    DicomValue(const std::string& content,
-               bool isBinary);
-    
-    DicomValue(const char* data,
-               size_t size,
-               bool isBinary);
-    
-    const std::string& GetContent() const;
+    virtual void RestApiPut(std::string& result,
+                            const std::string& uri,
+                            const std::string& body) = 0;
 
-    bool IsNull() const
-    {
-      return type_ == Type_Null;
-    }
+    virtual void RestApiDelete(const std::string& uri) = 0;
 
-    bool IsBinary() const
-    {
-      return type_ == Type_Binary;
-    }
-    
-    DicomValue* Clone() const;
+    static void ParseJson(Json::Value& result,
+                          const std::string& content);
 
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-    void FormatDataUriScheme(std::string& target,
-                             const std::string& mime) const;
+    static void RestApiGet(Json::Value& result,
+                           IOrthancConnection& orthanc,
+                           const std::string& uri);
 
-    void FormatDataUriScheme(std::string& target) const
-    {
-      FormatDataUriScheme(target, "application/octet-stream");
-    }
-#endif
+    static void RestApiPost(Json::Value& result,
+                            IOrthancConnection& orthanc,
+                            const std::string& uri,
+                            const std::string& body);
+
+    static void RestApiPut(Json::Value& result,
+                           IOrthancConnection& orthanc,
+                           const std::string& uri,
+                           const std::string& body);
   };
 }
diff --git a/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp b/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp
new file mode 100644
index 0000000..a1f9846
--- /dev/null
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp
@@ -0,0 +1,107 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancHttpConnection.h"
+
+namespace OrthancPlugins
+{
+  void OrthancHttpConnection::Setup()
+  {
+    url_ = client_.GetUrl();
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client_.SetRedirectionFollowed(false);  
+  }
+
+
+  OrthancHttpConnection::OrthancHttpConnection() :
+    client_(Orthanc::WebServiceParameters(), "")
+  {
+    Setup();
+  }
+
+
+  OrthancHttpConnection::OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters) :
+    client_(parameters, "")
+  {
+    Setup();
+  }
+
+
+  void OrthancHttpConnection::RestApiGet(std::string& result,
+                                         const std::string& uri)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    client_.SetMethod(Orthanc::HttpMethod_Get);
+    client_.SetUrl(url_ + uri);
+    client_.ApplyAndThrowException(result);
+  }
+
+
+  void OrthancHttpConnection::RestApiPost(std::string& result,
+                                          const std::string& uri,
+                                          const std::string& body)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    client_.SetMethod(Orthanc::HttpMethod_Post);
+    client_.SetUrl(url_ + uri);
+    client_.SetBody(body);
+    client_.ApplyAndThrowException(result);
+  }
+
+
+  void OrthancHttpConnection::RestApiPut(std::string& result,
+                                         const std::string& uri,
+                                         const std::string& body)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    client_.SetMethod(Orthanc::HttpMethod_Put);
+    client_.SetUrl(url_ + uri);
+    client_.SetBody(body);
+    client_.ApplyAndThrowException(result);
+  }
+
+
+  void OrthancHttpConnection::RestApiDelete(const std::string& uri)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::string result;
+
+    client_.SetMethod(Orthanc::HttpMethod_Delete);
+    client_.SetUrl(url_ + uri);
+    client_.ApplyAndThrowException(result);
+  }
+}
diff --git a/Framework/Orthanc/Plugins/Engine/SharedLibrary.h b/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h
similarity index 61%
rename from Framework/Orthanc/Plugins/Engine/SharedLibrary.h
rename to Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h
index 5a7913a..c1e69bd 100644
--- a/Framework/Orthanc/Plugins/Engine/SharedLibrary.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h
@@ -32,47 +32,44 @@
 
 #pragma once
 
-#if ORTHANC_PLUGINS_ENABLED == 1
+#include "IOrthancConnection.h"
 
-#include "../../Core/OrthancException.h"
+#if HAS_ORTHANC_EXCEPTION != 1
+#  error The macro HAS_ORTHANC_EXCEPTION must be set to 1 if using this header
+#endif
 
-#include <boost/noncopyable.hpp>
+#include "../../../Core/HttpClient.h"
 
-#if defined(_WIN32)
-#include <windows.h>
-#endif
+#include <boost/thread/mutex.hpp>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class SharedLibrary : public boost::noncopyable
+  // This class is thread-safe
+  class OrthancHttpConnection : public IOrthancConnection
   {
-  public:
-#if defined(_WIN32)
-    typedef FARPROC FunctionPointer;
-#else
-    typedef void* FunctionPointer;
-#endif
-
   private:
-    std::string path_;
-    void *handle_;
+    boost::mutex         mutex_;
+    Orthanc::HttpClient  client_;
+    std::string          url_;
 
-    FunctionPointer GetFunctionInternal(const std::string& name);
+    void Setup();
 
   public:
-    SharedLibrary(const std::string& path);
+    OrthancHttpConnection();
+
+    OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters);
 
-    ~SharedLibrary();
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri);
 
-    const std::string& GetPath() const
-    {
-      return path_;
-    }
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body);
 
-    bool HasFunction(const std::string& name);
+    virtual void RestApiPut(std::string& result,
+                            const std::string& uri,
+                            const std::string& body);
 
-    FunctionPointer GetFunction(const std::string& name);
+    virtual void RestApiDelete(const std::string& uri);
   };
 }
-
-#endif
diff --git a/Framework/Orthanc/Core/ChunkedBuffer.cpp b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.cpp
similarity index 51%
rename from Framework/Orthanc/Core/ChunkedBuffer.cpp
rename to Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.cpp
index 5d2c2c8..fb82353 100644
--- a/Framework/Orthanc/Core/ChunkedBuffer.cpp
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.cpp
@@ -30,73 +30,69 @@
  **/
 
 
-#include "PrecompiledHeaders.h"
-#include "ChunkedBuffer.h"
+#include "OrthancPluginConnection.h"
 
-#include <cassert>
-#include <string.h>
+#include "OrthancPluginCppWrapper.h"
 
-
-namespace Orthanc
+namespace OrthancPlugins
 {
-  void ChunkedBuffer::Clear()
+  void OrthancPluginConnection::RestApiGet(std::string& result,
+                                           const std::string& uri) 
   {
-    numBytes_ = 0;
+    OrthancPlugins::MemoryBuffer buffer(context_);
 
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
+    if (buffer.RestApiGet(uri, false))
+    {
+      buffer.ToString(result);
+    }
+    else
     {
-      delete *it;
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_UnknownResource);
     }
   }
 
 
-  void ChunkedBuffer::AddChunk(const void* chunkData,
-                               size_t chunkSize)
+  void OrthancPluginConnection::RestApiPost(std::string& result,
+                                            const std::string& uri,
+                                            const std::string& body)
   {
-    if (chunkSize == 0)
+    OrthancPlugins::MemoryBuffer buffer(context_);
+
+    if (buffer.RestApiPost(uri, body.c_str(), body.size(), false))
     {
-      return;
+      buffer.ToString(result);
     }
     else
     {
-      assert(chunkData != NULL);
-      chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize));
-      numBytes_ += chunkSize;
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_UnknownResource);
     }
   }
 
 
-  void ChunkedBuffer::AddChunk(const std::string& chunk)
+  void OrthancPluginConnection::RestApiPut(std::string& result,
+                                           const std::string& uri,
+                                           const std::string& body)
   {
-    if (chunk.size() > 0)
+    OrthancPlugins::MemoryBuffer buffer(context_);
+
+    if (buffer.RestApiPut(uri, body.c_str(), body.size(), false))
     {
-      AddChunk(&chunk[0], chunk.size());
+      buffer.ToString(result);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_UnknownResource);
     }
   }
 
 
-  void ChunkedBuffer::Flatten(std::string& result)
+  void OrthancPluginConnection::RestApiDelete(const std::string& uri)
   {
-    result.resize(numBytes_);
+    OrthancPlugins::MemoryBuffer buffer(context_);
 
-    size_t pos = 0;
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
+    if (!::OrthancPlugins::RestApiDelete(context_, uri, false))
     {
-      assert(*it != NULL);
-
-      size_t s = (*it)->size();
-      if (s != 0)
-      {
-        memcpy(&result[pos], (*it)->c_str(), s);
-        pos += s;
-      }
-
-      delete *it;
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_UnknownResource);
     }
-
-    chunks_.clear();
-    numBytes_ = 0;
   }
 }
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.h b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.h
similarity index 68%
copy from Framework/Orthanc/Core/DicomFormat/DicomArray.h
copy to Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.h
index a223685..f4b06ff 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomArray.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.h
@@ -32,35 +32,35 @@
 
 #pragma once
 
-#include "DicomElement.h"
-#include "DicomMap.h"
+#include "IOrthancConnection.h"
 
-#include <vector>
+#include <orthanc/OrthancCPlugin.h>
 
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class DicomArray : public boost::noncopyable
+  // This class is thread-safe
+  class OrthancPluginConnection : public IOrthancConnection
   {
   private:
-    typedef std::vector<DicomElement*>  Elements;
-
-    Elements  elements_;
+    OrthancPluginContext*   context_;
 
   public:
-    DicomArray(const DicomMap& map);
-
-    ~DicomArray();
-
-    size_t GetSize() const
+    OrthancPluginConnection(OrthancPluginContext* context) :
+    context_(context)
     {
-      return elements_.size();
     }
 
-    const DicomElement& GetElement(size_t i) const
-    {
-      return *elements_[i];
-    }
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri);
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body);
+
+    virtual void RestApiPut(std::string& result,
+                            const std::string& uri,
+                            const std::string& body);
 
-    void Print(FILE* fp) const;
+    virtual void RestApiDelete(const std::string& uri);
   };
 }
diff --git a/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
similarity index 90%
rename from Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
rename to Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
index 50412d7..4b5650e 100644
--- a/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
@@ -38,9 +38,10 @@
 
 namespace OrthancPlugins
 {
-  const char* PluginException::GetErrorDescription(OrthancPluginContext* context) const
+  const char* GetErrorDescription(OrthancPluginContext* context,
+                                  OrthancPluginErrorCode code)
   {
-    const char* description = OrthancPluginGetErrorDescription(context, code_);
+    const char* description = OrthancPluginGetErrorDescription(context, code);
     if (description)
     {
       return description;
@@ -52,13 +53,15 @@ namespace OrthancPlugins
   }
 
   
+#if HAS_ORTHANC_EXCEPTION == 0
   void PluginException::Check(OrthancPluginErrorCode code)
   {
     if (code != OrthancPluginErrorCode_Success)
     {
-      throw PluginException(code);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(code);
     }
   }
+#endif
 
 
   void MemoryBuffer::Check(OrthancPluginErrorCode code)
@@ -68,7 +71,7 @@ namespace OrthancPlugins
       // Prevent using garbage information
       buffer_.data = NULL;
       buffer_.size = 0;
-      throw PluginException(code);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(code);
     }
   }
 
@@ -122,7 +125,7 @@ namespace OrthancPlugins
     if (buffer_.data == NULL ||
         buffer_.size == 0)
     {
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_InternalError);
     }
 
     const char* tmp = reinterpret_cast<const char*>(buffer_.data);
@@ -131,7 +134,7 @@ namespace OrthancPlugins
     if (!reader.parse(tmp, tmp + buffer_.size, target))
     {
       OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
   }
 
@@ -163,7 +166,7 @@ namespace OrthancPlugins
     }
     else
     {
-      throw PluginException(error);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(error);
     }
   }
 
@@ -197,7 +200,7 @@ namespace OrthancPlugins
     }
     else
     {
-      throw PluginException(error);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(error);
     }
   }
 
@@ -231,7 +234,7 @@ namespace OrthancPlugins
     }
     else
     {
-      throw PluginException(error);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(error);
     }
   }
 
@@ -309,14 +312,14 @@ namespace OrthancPlugins
     if (str_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot convert an empty memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_InternalError);
     }
 
     Json::Reader reader;
     if (!reader.parse(str_, target))
     {
       OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
   }
   
@@ -329,7 +332,7 @@ namespace OrthancPlugins
     if (str.GetContent() == NULL)
     {
       OrthancPluginLogError(context, "Cannot access the Orthanc configuration");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_InternalError);
     }
 
     str.ToJson(configuration_);
@@ -337,7 +340,7 @@ namespace OrthancPlugins
     if (configuration_.type() != Json::objectValue)
     {
       OrthancPluginLogError(context, "Unable to read the Orthanc configuration");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_InternalError);
     }
   }
 
@@ -346,7 +349,7 @@ namespace OrthancPlugins
   {
     if (context_ == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_Plugin);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_Plugin);
     }
     else
     {
@@ -368,6 +371,15 @@ namespace OrthancPlugins
   }
 
 
+  bool OrthancConfiguration::IsSection(const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    return (configuration_.isMember(key) &&
+            configuration_[key].type() == Json::objectValue);
+  }
+
+
   void OrthancConfiguration::GetSection(OrthancConfiguration& target,
                                         const std::string& key) const
   {
@@ -390,7 +402,7 @@ namespace OrthancPlugins
           OrthancPluginLogError(context_, s.c_str());
         }
 
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
       }
 
       target.configuration_ = configuration_[key];
@@ -416,7 +428,7 @@ namespace OrthancPlugins
         OrthancPluginLogError(context_, s.c_str());
       }
 
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
 
     target = configuration_[key].asString();
@@ -451,7 +463,7 @@ namespace OrthancPlugins
           OrthancPluginLogError(context_, s.c_str());
         }
 
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
   }
 
@@ -473,7 +485,7 @@ namespace OrthancPlugins
         OrthancPluginLogError(context_, s.c_str());
       }
 
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
     else
     {
@@ -501,7 +513,7 @@ namespace OrthancPlugins
         OrthancPluginLogError(context_, s.c_str());
       }
 
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
 
     target = configuration_[key].asBool();
@@ -540,7 +552,7 @@ namespace OrthancPlugins
           OrthancPluginLogError(context_, s.c_str());
         }
 
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
     }
   }
 
@@ -635,7 +647,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Trying to access a NULL image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -646,7 +658,7 @@ namespace OrthancPlugins
   {
     if (context == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -658,7 +670,7 @@ namespace OrthancPlugins
   {
     if (context == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
   }
   
@@ -671,7 +683,7 @@ namespace OrthancPlugins
   {
     if (context == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
     else
     {
@@ -688,7 +700,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot uncompress a PNG image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -701,7 +713,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot uncompress a JPEG image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -715,7 +727,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot uncompress a DICOM image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -903,7 +915,7 @@ namespace OrthancPlugins
     }
     else
     {
-      throw PluginException(error);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(error);
     }
   }
 
diff --git a/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
similarity index 94%
rename from Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
rename to Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
index 6a6ab91..8f0bb7a 100644
--- a/Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
@@ -37,18 +37,30 @@
 #include <boost/lexical_cast.hpp>
 #include <json/value.h>
 
+#if !defined(HAS_ORTHANC_EXCEPTION)
+#  error The macro HAS_ORTHANC_EXCEPTION must be defined
+#endif
+
+
 #if HAS_ORTHANC_EXCEPTION == 1
 #  include "../../../Core/OrthancException.h"
+#  define ORTHANC_PLUGINS_THROW_EXCEPTION(code)  throw ::Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(code))
+#else
+#  define ORTHANC_PLUGINS_THROW_EXCEPTION(code)  throw ::OrthancPlugins::PluginException(code)
 #endif
 
 
+
 namespace OrthancPlugins
 {
   typedef void (*RestCallback) (OrthancPluginRestOutput* output,
                                 const char* url,
                                 const OrthancPluginHttpRequest* request);
 
+  const char* GetErrorDescription(OrthancPluginContext* context,
+                                  OrthancPluginErrorCode code);
 
+#if HAS_ORTHANC_EXCEPTION == 0
   class PluginException
   {
   private:
@@ -64,10 +76,14 @@ namespace OrthancPlugins
       return code_;
     }
 
-    const char* GetErrorDescription(OrthancPluginContext* context) const;
+    const char* What(OrthancPluginContext* context) const
+    {
+      return ::OrthancPlugins::GetErrorDescription(context, code_);
+    }
 
     static void Check(OrthancPluginErrorCode code);
   };
+#endif
 
 
   class MemoryBuffer : public boost::noncopyable
@@ -191,7 +207,7 @@ namespace OrthancPlugins
   {
   private:
     OrthancPluginContext*  context_;
-    Json::Value            configuration_;
+    Json::Value            configuration_;  // Necessarily a Json::objectValue
     std::string            path_;
 
     std::string GetPath(const std::string& key) const;
@@ -210,6 +226,8 @@ namespace OrthancPlugins
       return configuration_;
     }
 
+    bool IsSection(const std::string& key) const;
+
     void GetSection(OrthancConfiguration& target,
                     const std::string& key) const;
 
@@ -357,10 +375,6 @@ namespace OrthancPlugins
                      const std::string& uri,
                      bool applyPlugins);
 
-  bool RestApiDelete(OrthancPluginContext* context,
-                     const std::string& uri,
-                     bool applyPlugins);
-
   inline void LogError(OrthancPluginContext* context,
                        const std::string& message)
   {
@@ -406,15 +420,16 @@ namespace OrthancPlugins
         Callback(output, url, request);
         return OrthancPluginErrorCode_Success;
       }
-      catch (OrthancPlugins::PluginException& e)
-      {
-        return e.GetErrorCode();
-      }
 #if HAS_ORTHANC_EXCEPTION == 1
       catch (Orthanc::OrthancException& e)
       {
         return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
       }
+#else
+      catch (OrthancPlugins::PluginException& e)
+      {
+        return e.GetErrorCode();
+      }
 #endif
       catch (boost::bad_lexical_cast&)
       {
diff --git a/Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp b/Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp
new file mode 100644
index 0000000..1fa2bea
--- /dev/null
+++ b/Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp
@@ -0,0 +1,156 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SimplifiedOrthancDataset.h"
+
+#include "OrthancPluginCppWrapper.h"
+
+namespace OrthancPlugins
+{
+  const Json::Value* SimplifiedOrthancDataset::LookupPath(const DicomPath& path) const
+  {
+    const Json::Value* content = &root_;
+                                  
+    for (unsigned int depth = 0; depth < path.GetPrefixLength(); depth++)
+    {
+      const char* name = path.GetPrefixTag(depth).GetName();
+      if (content->type() != Json::objectValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+      }
+
+      if (!content->isMember(name))
+      {
+        return NULL;
+      }
+
+      const Json::Value& sequence = (*content) [name];
+      if (sequence.type() != Json::arrayValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+      }
+
+      size_t index = path.GetPrefixIndex(depth);
+      if (index >= sequence.size())
+      {
+        return NULL;
+      }
+      else
+      {
+        content = &sequence[static_cast<Json::Value::ArrayIndex>(index)];
+      }
+    }
+
+    const char* name = path.GetFinalTag().GetName();
+
+    if (content->type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+    if (!content->isMember(name))
+    {
+      return NULL;
+    }
+    else
+    {
+      return &((*content) [name]);
+    }
+  }
+
+
+  void SimplifiedOrthancDataset::CheckRoot() const
+  {
+    if (root_.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+  }
+
+
+  SimplifiedOrthancDataset::SimplifiedOrthancDataset(IOrthancConnection& orthanc,
+                                                     const std::string& uri)
+  {
+    IOrthancConnection::RestApiGet(root_, orthanc, uri);
+    CheckRoot();
+  }
+
+
+  SimplifiedOrthancDataset::SimplifiedOrthancDataset(const std::string& content)
+  {
+    IOrthancConnection::ParseJson(root_, content);
+    CheckRoot();
+  }
+
+
+  bool SimplifiedOrthancDataset::GetStringValue(std::string& result,
+                                                const DicomPath& path) const
+  {
+    const Json::Value* value = LookupPath(path);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else if (value->type() != Json::stringValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      result = value->asString();
+      return true;
+    }
+  }
+
+
+  bool SimplifiedOrthancDataset::GetSequenceSize(size_t& size,
+                                                 const DicomPath& path) const
+  {
+    const Json::Value* sequence = LookupPath(path);
+
+    if (sequence == NULL)
+    {
+      // Inexistent path
+      return false;
+    }
+    else if (sequence->type() != Json::arrayValue)
+    {
+      // Not a sequence
+      ORTHANC_PLUGINS_THROW_EXCEPTION(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      size = sequence->size();
+      return true;
+    }
+  }
+}
diff --git a/Framework/Orthanc/Core/DicomFormat/DicomArray.h b/Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.h
similarity index 71%
rename from Framework/Orthanc/Core/DicomFormat/DicomArray.h
rename to Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.h
index a223685..a71e1e0 100644
--- a/Framework/Orthanc/Core/DicomFormat/DicomArray.h
+++ b/Resources/Orthanc/Plugins/Samples/Common/SimplifiedOrthancDataset.h
@@ -32,35 +32,30 @@
 
 #pragma once
 
-#include "DicomElement.h"
-#include "DicomMap.h"
+#include "IOrthancConnection.h"
+#include "IDicomDataset.h"
 
-#include <vector>
-
-namespace Orthanc
+namespace OrthancPlugins
 {
-  class DicomArray : public boost::noncopyable
+  class SimplifiedOrthancDataset : public IDicomDataset
   {
   private:
-    typedef std::vector<DicomElement*>  Elements;
+    Json::Value   root_;
 
-    Elements  elements_;
+    const Json::Value* LookupPath(const DicomPath& path) const;
 
-  public:
-    DicomArray(const DicomMap& map);
+    void CheckRoot() const;
 
-    ~DicomArray();
+  public:
+    SimplifiedOrthancDataset(IOrthancConnection& orthanc,
+                             const std::string& uri);
 
-    size_t GetSize() const
-    {
-      return elements_.size();
-    }
+    SimplifiedOrthancDataset(const std::string& content);
 
-    const DicomElement& GetElement(size_t i) const
-    {
-      return *elements_[i];
-    }
+    virtual bool GetStringValue(std::string& result,
+                                const DicomPath& path) const;
 
-    void Print(FILE* fp) const;
+    virtual bool GetSequenceSize(size_t& size,
+                                 const DicomPath& path) const;
   };
 }
diff --git a/Framework/Orthanc/Plugins/Samples/Common/VersionScript.map b/Resources/Orthanc/Plugins/Samples/Common/VersionScript.map
similarity index 100%
rename from Framework/Orthanc/Plugins/Samples/Common/VersionScript.map
rename to Resources/Orthanc/Plugins/Samples/Common/VersionScript.map
diff --git a/Framework/Orthanc/README.txt b/Resources/Orthanc/README.txt
similarity index 100%
rename from Framework/Orthanc/README.txt
rename to Resources/Orthanc/README.txt
diff --git a/Framework/Orthanc/Resources/CMake/AutoGeneratedCode.cmake b/Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/AutoGeneratedCode.cmake
rename to Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake
diff --git a/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake b/Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake
similarity index 99%
rename from Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake
index efe0e70..3596427 100644
--- a/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake
+++ b/Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake
@@ -125,10 +125,11 @@ if (BOOST_STATIC)
     add_definitions(
       -DBOOST_HAS_FILESYSTEM_V3=0
       -D__INTEGRITY=1
-      -DORTHANC_SANDBOXED=1
       )
   else()
-    add_definitions(-DBOOST_HAS_FILESYSTEM_V3=1)
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=1
+      )
     list(APPEND BOOST_SOURCES
       ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
       ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
diff --git a/Framework/Orthanc/Resources/CMake/Compiler.cmake b/Resources/Orthanc/Resources/CMake/Compiler.cmake
similarity index 84%
rename from Framework/Orthanc/Resources/CMake/Compiler.cmake
rename to Resources/Orthanc/Resources/CMake/Compiler.cmake
index 52f4fab..fa7577d 100644
--- a/Framework/Orthanc/Resources/CMake/Compiler.cmake
+++ b/Resources/Orthanc/Resources/CMake/Compiler.cmake
@@ -41,7 +41,17 @@ elseif (MSVC)
     -D_CRT_SECURE_NO_WARNINGS=1
     -D_CRT_SECURE_NO_DEPRECATE=1
     )
-  include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+
+  if (MSVC_VERSION LESS 1600)
+    # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >=
+    # 1600), Microsoft ships a standard-compliant <stdint.h>
+    # header. For earlier versions of Visual Studio, give access to a
+    # compatibility header.
+    # http://stackoverflow.com/a/70630/881731
+    # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links
+    include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+  endif()
+
   link_libraries(netapi32)
 endif()
 
@@ -154,6 +164,23 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
 endif()
 
 
+if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
+  if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+    message(WARNING "Enabling profiling on a non-debug build will not produce full information")
+  endif()
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pg")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
+  else()
+    message(FATAL_ERROR "Don't know how to enable profiling on your configuration")
+  endif()
+endif()
+
+
 if (STATIC_BUILD)
   add_definitions(-DORTHANC_STATIC=1)
 else()
diff --git a/Framework/Orthanc/Resources/CMake/DcmtkConfiguration.cmake b/Resources/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
similarity index 92%
rename from Framework/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
index 6d48ccb..96d07cb 100644
--- a/Framework/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
+++ b/Resources/Orthanc/Resources/CMake/DcmtkConfiguration.cmake
@@ -35,6 +35,24 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
 
   DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}")
 
+  
+  if (FirstRun AND
+      NOT USE_DCMTK_361)
+    if (USE_DCMTK_361_PRIVATE_DIC)
+      # If using DCMTK 3.6.0, backport the "private.dic" file from DCMTK
+      # 3.6.1 snapshot. This adds support for more private tags, and
+      # fixes some import problems with Philips MRI Achieva.
+      message("Using the dictionary of private tags from DCMTK 3.6.1")
+      configure_file(
+        ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.1-private.dic
+        ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic
+        COPYONLY)
+    else()
+      message("Using the dictionary of private tags from DCMTK 3.6.0")
+    endif()
+  endif()
+
+
   IF (CMAKE_CROSSCOMPILING)
     SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.")
   ENDIF()
@@ -181,9 +199,15 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
   list(REMOVE_ITEM DCMTK_SOURCES 
     ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc
     ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
-    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc
     )
 
+  if (NOT USE_DCMTK_361)
+    # Removing this file is required with DCMTK 3.6.0
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc
+      )
+  endif()
+
   #set_source_files_properties(${DCMTK_SOURCES}
   #  PROPERTIES COMPILE_DEFINITIONS
   #  "PACKAGE_VERSION=\"${DCMTK_PACKAGE_VERSION}\";PACKAGE_VERSION_NUMBER=\"${DCMTK_VERSION_NUMBER}\"")
diff --git a/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake b/Resources/Orthanc/Resources/CMake/DownloadPackage.cmake
similarity index 88%
rename from Framework/Orthanc/Resources/CMake/DownloadPackage.cmake
rename to Resources/Orthanc/Resources/CMake/DownloadPackage.cmake
index b529cef..2a73cda 100644
--- a/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake
+++ b/Resources/Orthanc/Resources/CMake/DownloadPackage.cmake
@@ -15,16 +15,18 @@ endmacro()
 ## Setup the patch command-line tool
 ##
 
-if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-  set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
-  if (NOT EXISTS ${PATCH_EXECUTABLE})
-    message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
-  endif()
+if (NOT ORTHANC_DISABLE_PATCH)
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
+    if (NOT EXISTS ${PATCH_EXECUTABLE})
+      message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
+    endif()
 
-else ()
-  find_program(PATCH_EXECUTABLE patch)
-  if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
+  else ()
+    find_program(PATCH_EXECUTABLE patch)
+    if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
+    endif()
   endif()
 endif()
 
@@ -74,7 +76,9 @@ macro(DownloadPackage MD5 Url TargetDirectory)
 	message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
       endif()
 
-      file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}")
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" 
+        SHOW_PROGRESS EXPECTED_MD5 "${MD5}"
+        TIMEOUT 60 INACTIVITY_TIMEOUT 60)
     else()
       message("Using local copy of ${Url}")
     endif()
diff --git a/Framework/Orthanc/Resources/CMake/JsonCppConfiguration.cmake b/Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
diff --git a/Framework/Orthanc/Resources/CMake/LibCurlConfiguration.cmake b/Resources/Orthanc/Resources/CMake/LibCurlConfiguration.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/LibCurlConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/LibCurlConfiguration.cmake
diff --git a/Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake b/Resources/Orthanc/Resources/CMake/LibJpegConfiguration.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/LibJpegConfiguration.cmake
diff --git a/Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake b/Resources/Orthanc/Resources/CMake/LibPngConfiguration.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/LibPngConfiguration.cmake
diff --git a/Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake b/Resources/Orthanc/Resources/CMake/OpenSslConfiguration.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/OpenSslConfiguration.cmake
diff --git a/Framework/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake b/Resources/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake
rename to Resources/Orthanc/Resources/CMake/VisualStudioPrecompiledHeaders.cmake
diff --git a/Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake b/Resources/Orthanc/Resources/CMake/ZlibConfiguration.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake
rename to Resources/Orthanc/Resources/CMake/ZlibConfiguration.cmake
diff --git a/Framework/Orthanc/Resources/EmbedResources.py b/Resources/Orthanc/Resources/EmbedResources.py
similarity index 98%
rename from Framework/Orthanc/Resources/EmbedResources.py
rename to Resources/Orthanc/Resources/EmbedResources.py
index d914d15..ad5cf61 100755
--- a/Framework/Orthanc/Resources/EmbedResources.py
+++ b/Resources/Orthanc/Resources/EmbedResources.py
@@ -159,6 +159,10 @@ header.write("""
 #include <string>
 #include <list>
 
+#if defined(_MSC_VER)
+#  pragma warning(disable: 4065)  // "Switch statement contains 'default' but no 'case' labels"
+#endif
+
 namespace %s
 {
   namespace EmbeddedResources
diff --git a/Framework/Orthanc/Resources/MinGW-W64-Toolchain32.cmake b/Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/MinGW-W64-Toolchain32.cmake
rename to Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake
diff --git a/Framework/Orthanc/Resources/MinGW-W64-Toolchain64.cmake b/Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/MinGW-W64-Toolchain64.cmake
rename to Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake
diff --git a/Framework/Orthanc/Resources/MinGWToolchain.cmake b/Resources/Orthanc/Resources/MinGWToolchain.cmake
similarity index 100%
rename from Framework/Orthanc/Resources/MinGWToolchain.cmake
rename to Resources/Orthanc/Resources/MinGWToolchain.cmake
diff --git a/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch b/Resources/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch
similarity index 100%
rename from Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch
rename to Resources/Orthanc/Resources/Patches/dcmtk-3.6.0-mingw64.patch
diff --git a/Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch b/Resources/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch
similarity index 100%
rename from Framework/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch
rename to Resources/Orthanc/Resources/Patches/dcmtk-3.6.0-speed.patch
diff --git a/Framework/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch b/Resources/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch
similarity index 100%
rename from Framework/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch
rename to Resources/Orthanc/Resources/Patches/dcmtk-3.6.1-speed.patch
diff --git a/Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h b/Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
similarity index 100%
rename from Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
rename to Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
diff --git a/Framework/Orthanc/Resources/ThirdParty/base64/base64.cpp b/Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp
similarity index 100%
rename from Framework/Orthanc/Resources/ThirdParty/base64/base64.cpp
rename to Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp
diff --git a/Framework/Orthanc/Resources/ThirdParty/base64/base64.h b/Resources/Orthanc/Resources/ThirdParty/base64/base64.h
similarity index 100%
rename from Framework/Orthanc/Resources/ThirdParty/base64/base64.h
rename to Resources/Orthanc/Resources/ThirdParty/base64/base64.h
diff --git a/Framework/Orthanc/Resources/WindowsResources.py b/Resources/Orthanc/Resources/WindowsResources.py
similarity index 100%
rename from Framework/Orthanc/Resources/WindowsResources.py
rename to Resources/Orthanc/Resources/WindowsResources.py
diff --git a/Framework/Orthanc/Resources/WindowsResources.rc b/Resources/Orthanc/Resources/WindowsResources.rc
similarity index 100%
rename from Framework/Orthanc/Resources/WindowsResources.rc
rename to Resources/Orthanc/Resources/WindowsResources.rc
diff --git a/Framework/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h b/Resources/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h
similarity index 100%
rename from Framework/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h
rename to Resources/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h
diff --git a/Resources/OrthancWSIClearCache.py b/Resources/OrthancWSIClearCache.py
new file mode 100755
index 0000000..b7e3591
--- /dev/null
+++ b/Resources/OrthancWSIClearCache.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, 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/>.
+
+
+import base64
+import httplib2
+import json
+import os
+import sys
+
+if len(sys.argv) != 3 and len(sys.argv) != 5:
+    print("""
+Script to reinitialize the cache of the whole-slide imaging plugin for
+Orthanc. Please make sure that Orthanc is running before starting this
+script.
+
+Usage: %s [hostname] [HTTP port]
+Usage: %s [hostname] [HTTP port] [username] [password]
+For instance: %s 127.0.0.1 8042
+""" % (sys.argv[0], sys.argv[0], sys.argv[0]))
+    exit(-1)
+
+
+METADATA=4200
+
+
+def RunHttpRequest(uri, method, body = None):
+    http = httplib2.Http()
+    headers = { }
+
+    if len(sys.argv) == 5:
+        username = sys.argv[4]
+        password = sys.argv[5]
+
+        # h.add_credentials(username, password)
+
+        # This is a custom reimplementation of the
+        # "Http.add_credentials()" method for Basic HTTP Access
+        # Authentication (for some weird reason, this method does not
+        # always work)
+        # http://en.wikipedia.org/wiki/Basic_access_authentication
+        headers['authorization'] = 'Basic ' + base64.b64encode(username + ':' + password)       
+
+    url = 'http://%s:%d/%s' % (sys.argv[1], int(sys.argv[2]), uri)
+    resp, content = http.request(url, method,
+                                 body = body,
+                                 headers = headers)
+
+    if resp.status != 200:
+        raise Exception('Cannot %s on URL %s, HTTP status %d '
+                        '(Is Orthanc running? Is there a password?)' % 
+                        (method, url, resp.status))
+    else:
+        return content.decode('utf8')
+
+
+for instance in json.loads(RunHttpRequest('/instances', 'GET')):
+    print('Clearing cache for instance %s' % instance)
+    RunHttpRequest('/instances/%s/metadata/%s' % (instance, METADATA), 'DELETE')
+
+print('The WSI cache was successfully cleared')
diff --git a/Resources/SyncOrthancFolder.py b/Resources/SyncOrthancFolder.py
index eb1bc9e..528206e 100755
--- a/Resources/SyncOrthancFolder.py
+++ b/Resources/SyncOrthancFolder.py
@@ -10,11 +10,12 @@ import os
 import stat
 import urllib2
 
-TARGET = os.path.join(os.path.dirname(__file__), '..', 'Framework', 'Orthanc')
+TARGET = os.path.join(os.path.dirname(__file__), '..', 'Resources', 'Orthanc')
 PLUGIN_SDK_VERSION = '1.0.0'
 REPOSITORY = 'http://bitbucket.org/sjodogne/orthanc/raw'
 
 FILES = [
+    'Core/Cache/LeastRecentlyUsedIndex.h',
     'Core/ChunkedBuffer.cpp',
     'Core/ChunkedBuffer.h',
     'Core/DicomFormat/DicomArray.cpp',
@@ -66,10 +67,12 @@ FILES = [
     'Core/OrthancException.h',
     'Core/PrecompiledHeaders.cpp',
     'Core/PrecompiledHeaders.h',
+    'Core/SystemToolbox.cpp',
+    'Core/SystemToolbox.h',
+    'Core/TemporaryFile.cpp',
+    'Core/TemporaryFile.h',
     'Core/Toolbox.cpp',
     'Core/Toolbox.h',
-    'Core/Uuid.cpp',
-    'Core/Uuid.h',
     'Core/WebServiceParameters.cpp',
     'Core/WebServiceParameters.h',
     'OrthancServer/FromDcmtkBridge.cpp',
@@ -85,6 +88,23 @@ FILES = [
     'Plugins/Samples/Common/OrthancPluginCppWrapper.cpp',
     'Plugins/Samples/Common/OrthancPluginCppWrapper.h',
     'Plugins/Samples/Common/VersionScript.map',
+    'Plugins/Samples/Common/DicomDatasetReader.cpp',
+    'Plugins/Samples/Common/DicomDatasetReader.h',
+    'Plugins/Samples/Common/DicomPath.cpp',
+    'Plugins/Samples/Common/DicomPath.h',
+    'Plugins/Samples/Common/DicomTag.cpp',
+    'Plugins/Samples/Common/DicomTag.h',
+    'Plugins/Samples/Common/FullOrthancDataset.cpp',
+    'Plugins/Samples/Common/FullOrthancDataset.h',
+    'Plugins/Samples/Common/IDicomDataset.h',
+    'Plugins/Samples/Common/IOrthancConnection.cpp',
+    'Plugins/Samples/Common/IOrthancConnection.h',
+    'Plugins/Samples/Common/OrthancHttpConnection.cpp',
+    'Plugins/Samples/Common/OrthancHttpConnection.h',
+    'Plugins/Samples/Common/OrthancPluginConnection.cpp',
+    'Plugins/Samples/Common/OrthancPluginConnection.h',
+    'Plugins/Samples/Common/SimplifiedOrthancDataset.cpp',
+    'Plugins/Samples/Common/SimplifiedOrthancDataset.h',
     'Resources/CMake/AutoGeneratedCode.cmake',
     'Resources/CMake/BoostConfiguration.cmake',
     'Resources/CMake/Compiler.cmake',
diff --git a/TODO b/TODO
index 2c738bb..c4af315 100644
--- a/TODO
+++ b/TODO
@@ -6,15 +6,16 @@ Orthanc for Whole-Slide Imaging
 General
 -------
 
-* Support sparse tiling (both in encoder and decoder)
+* Thoroughly test sparse tiling (both in encoder and decoder)
 * Display physical scale in Web viewer
+* Viewer: Configure how missing tiles are displayed for sparse tiling
 
 
 -----------
 Performance
 -----------
 
-* Larger cache with LRU recycling to improve viewer performance
+* Check out rapidjson: https://github.com/miloyip/nativejson-benchmark
 
 
 -------------
@@ -29,5 +30,4 @@ Documentation
 Packaging
 ---------
 
-* DebianMed (ITP #842168: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=842168)
-* Provide precompiled Windows binaries
+* Debian: Rename "Framework/Orthanc/" as "Resources/Orthanc/" in "debian/copyright"
diff --git a/ViewerPlugin/CMakeLists.txt b/ViewerPlugin/CMakeLists.txt
index f6b4d75..6f8ce3f 100644
--- a/ViewerPlugin/CMakeLists.txt
+++ b/ViewerPlugin/CMakeLists.txt
@@ -30,7 +30,7 @@ SET(OPENLAYERS_JS "" CACHE FILEPATH "Path to the system version of OpenLayers Ja
 #####################################################################
 
 SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
-SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc)
+SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Resources/Orthanc)
 
 SET(USE_OPENJPEG_JP2 ON)
 
@@ -63,6 +63,8 @@ add_definitions(
   -DORTHANC_ENABLE_DCMTK=0
   -DORTHANC_ENABLE_LOGGING=0
   -DORTHANC_ENABLE_MD5=0
+  -DORTHANC_ENABLE_PUGIXML=0
+  -DORTHANC_SANDBOXED=0
   -DHAS_ORTHANC_EXCEPTION=1
   )
 
@@ -156,6 +158,7 @@ EmbedResources(
 #####################################################################
 
 set(ORTHANC_WSI_SOURCES
+  DicomPyramidCache.cpp
   Plugin.cpp
   ${ORTHANC_WSI_DIR}/Framework/DicomToolbox.cpp
   ${ORTHANC_WSI_DIR}/Framework/Enumerations.cpp
@@ -166,9 +169,6 @@ set(ORTHANC_WSI_SOURCES
   ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp
   )
 
 set(ORTHANC_CORE_SOURCES
@@ -186,7 +186,13 @@ set(ORTHANC_CORE_SOURCES
   ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp
   ${ORTHANC_ROOT}/Core/Logging.cpp
   ${ORTHANC_ROOT}/Core/MultiThreading/Semaphore.cpp
+  ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
   ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginConnection.cpp
   ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
   )
 
@@ -199,7 +205,7 @@ if (MSVC)
   add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1)
 
   ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
-    "PrecompiledHeaders.h" "${ORTHANC_WSI_DIR}/Framework/Orthanc/Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES)
+    "PrecompiledHeaders.h" "${ORTHANC_WSI_DIR}/Resources/Orthanc/Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES)
 
   ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS(
     "PrecompiledHeadersWSI.h" "${ORTHANC_WSI_DIR}/Framework/PrecompiledHeadersWSI.cpp" ORTHANC_WSI_SOURCES)
diff --git a/ViewerPlugin/DicomPyramidCache.cpp b/ViewerPlugin/DicomPyramidCache.cpp
new file mode 100644
index 0000000..16509f4
--- /dev/null
+++ b/ViewerPlugin/DicomPyramidCache.cpp
@@ -0,0 +1,156 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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 "../Framework/PrecompiledHeadersWSI.h"
+#include "DicomPyramidCache.h"
+
+#include <cassert>
+
+namespace OrthancWSI
+{
+  DicomPyramid* DicomPyramidCache::GetCachedPyramid(const std::string& seriesId)
+  {
+    // Mutex is assumed to be locked
+    DicomPyramid* pyramid = NULL;
+
+    // Is the series of interest already cached as a pyramid?
+    if (cache_.Contains(seriesId, pyramid))
+    {
+      if (pyramid == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      // Tag the series as the most recently used
+      cache_.MakeMostRecent(seriesId);
+    }
+
+    return pyramid;
+  }
+
+
+  DicomPyramid& DicomPyramidCache::GetPyramid(const std::string& seriesId,
+                                              boost::mutex::scoped_lock& lock)
+  {
+    // Mutex is assumed to be locked
+
+    {
+      DicomPyramid* pyramid = GetCachedPyramid(seriesId);
+      if (pyramid != NULL)
+      {
+        return *pyramid;
+      }
+    }
+
+    // Unlock the mutex to construct the pyramid (this is a
+    // time-consuming operation, we don't want it to block other clients)
+    lock.unlock();
+
+    std::auto_ptr<DicomPyramid> pyramid
+      (new DicomPyramid(orthanc_, seriesId, true /* use metadata cache */));
+
+    {
+      // The pyramid is constructed: Store it into the cache
+      lock.lock();
+
+      DicomPyramid* cached = GetCachedPyramid(seriesId);
+      if (cached != NULL)
+      {
+        // The pyramid was already constructed by another request in
+        // between, reuse the cached value (the auto_ptr destroys
+        // the just-constructed pyramid)
+        return *cached;
+      }
+
+      if (cache_.GetSize() == maxSize_)
+      {
+        // The cache has grown too large: First delete the least
+        // recently used entry
+        DicomPyramid* oldest = NULL;
+        cache_.RemoveOldest(oldest);
+
+        if (oldest == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+        else
+        {
+          delete oldest;
+        }
+      }
+
+      // Now we have at least one free entry in the cache
+      assert(cache_.GetSize() < maxSize_);
+
+      // Add a new element to the cache and make it the most
+      // recently used entry
+      DicomPyramid* payload = pyramid.release();
+      cache_.Add(seriesId, payload);
+      return *payload;
+    }
+  }
+
+
+  DicomPyramidCache::DicomPyramidCache(OrthancPlugins::IOrthancConnection& orthanc,
+                                       size_t maxSize) :
+    orthanc_(orthanc),
+    maxSize_(maxSize)
+  {
+  }
+
+
+  DicomPyramidCache::~DicomPyramidCache()
+  {
+    while (!cache_.IsEmpty())
+    {
+      DicomPyramid* pyramid = NULL;
+      std::string seriesId = cache_.RemoveOldest(pyramid);
+
+      if (pyramid != NULL)
+      {
+        delete pyramid;
+      }        
+    }
+  }
+
+
+  void DicomPyramidCache::Invalidate(const std::string& seriesId)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    if (cache_.Contains(seriesId))
+    {
+      std::auto_ptr<DicomPyramid> pyramid(cache_.Invalidate(seriesId));
+
+      if (pyramid.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  DicomPyramidCache::Locker::Locker(DicomPyramidCache& cache,
+                                    const std::string& seriesId) :
+    lock_(cache.mutex_),
+    pyramid_(cache.GetPyramid(seriesId, lock_))
+  {
+  }
+}
diff --git a/ViewerPlugin/DicomPyramidCache.h b/ViewerPlugin/DicomPyramidCache.h
new file mode 100644
index 0000000..0c0fbaa
--- /dev/null
+++ b/ViewerPlugin/DicomPyramidCache.h
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, 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 "../Framework/Inputs/DicomPyramid.h"
+#include "../Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancWSI
+{
+  class DicomPyramidCache : public boost::noncopyable
+  {
+  private:
+    typedef Orthanc::LeastRecentlyUsedIndex<std::string, DicomPyramid*>  Cache;
+
+    boost::mutex                         mutex_;
+    OrthancPlugins::IOrthancConnection&  orthanc_;
+    size_t                               maxSize_;
+    Cache                                cache_;
+
+
+    DicomPyramid* GetCachedPyramid(const std::string& seriesId);
+
+    DicomPyramid& GetPyramid(const std::string& seriesId,
+                             boost::mutex::scoped_lock& lock);
+
+  public:
+    DicomPyramidCache(OrthancPlugins::IOrthancConnection& orthanc,
+                      size_t maxSize);
+
+    ~DicomPyramidCache();
+
+    void Invalidate(const std::string& seriesId);
+
+    class Locker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      DicomPyramid&              pyramid_;
+
+    public:
+      Locker(DicomPyramidCache& cache,
+             const std::string& seriesId);
+
+      DicomPyramid& GetPyramid() const
+      {
+        return pyramid_;
+      }
+    };
+  };
+}
diff --git a/ViewerPlugin/Plugin.cpp b/ViewerPlugin/Plugin.cpp
index d68ed00..3eed2a9 100644
--- a/ViewerPlugin/Plugin.cpp
+++ b/ViewerPlugin/Plugin.cpp
@@ -19,95 +19,47 @@
 
 
 #include "../Framework/PrecompiledHeadersWSI.h"
-#include "../Framework/Inputs/DicomPyramid.h"
+
+#include "DicomPyramidCache.h"
 #include "../Framework/Jpeg2000Reader.h"
-#include "../Framework/Messaging/PluginOrthancConnection.h"
-#include "../Framework/Orthanc/Core/Images/PngWriter.h"
-#include "../Framework/Orthanc/Core/MultiThreading/Semaphore.h"
-#include "../Framework/Orthanc/Core/OrthancException.h"
-#include "../Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+
+#include "../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include "../Resources/Orthanc/Core/Images/PngWriter.h"
+#include "../Resources/Orthanc/Core/MultiThreading/Semaphore.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginConnection.h"
 
 #include <EmbeddedResources.h>
 
 #include <cassert>
 
+OrthancPluginContext* context_ = NULL;
 
+std::auto_ptr<OrthancPlugins::OrthancPluginConnection>  orthanc_;
+std::auto_ptr<OrthancWSI::DicomPyramidCache>            cache_;
+std::auto_ptr<Orthanc::Semaphore>                       transcoderSemaphore_;
 
-namespace OrthancWSI
-{
-  // TODO Add LRU recycling policy
-  class DicomPyramidCache : public boost::noncopyable
-  {
-  private:
-    boost::mutex                  mutex_;
-    IOrthancConnection&           orthanc_;
-    std::auto_ptr<DicomPyramid>   pyramid_;
-
-    DicomPyramid& GetPyramid(const std::string& seriesId)
-    {
-      // Mutex is assumed to be locked
-
-      if (pyramid_.get() == NULL ||
-          pyramid_->GetSeriesId() != seriesId)
-      {
-        pyramid_.reset(new DicomPyramid(orthanc_, seriesId));
-      }
-
-      if (pyramid_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      return *pyramid_;
-    }
-
-  public:
-    DicomPyramidCache(IOrthancConnection& orthanc) :
-      orthanc_(orthanc)
-    {
-    }
-
-    void Invalidate(const std::string& seriesId)
-    {
-      boost::mutex::scoped_lock  lock(mutex_);
-
-      if (pyramid_.get() != NULL &&
-          pyramid_->GetSeriesId() == seriesId)
-      {
-        pyramid_.reset(NULL);
-      }
-    }
-
-    class Locker : public boost::noncopyable
-    {
-    private:
-      boost::mutex::scoped_lock  lock_;
-      DicomPyramid&              pyramid_;
-
-    public:
-      Locker(DicomPyramidCache& cache,
-             const std::string& seriesId) :
-        lock_(cache.mutex_),
-        pyramid_(cache.GetPyramid(seriesId))
-      {
-      }
 
-      DicomPyramid& GetPyramid() const
-      {
-        return pyramid_;
-      }
-    };
-  };
+static void AnswerSparseTile(OrthancPluginRestOutput* output,
+                             unsigned int tileWidth,
+                             unsigned int tileHeight)
+{
+  Orthanc::Image tile(Orthanc::PixelFormat_RGB24, tileWidth, tileHeight, false);
+
+  // Black (TODO parameter)
+  uint8_t red = 0;
+  uint8_t green = 0;
+  uint8_t blue = 0;
+  Orthanc::ImageProcessing::Set(tile, red, green, blue, 255);
+
+  // TODO Cache the tile
+  OrthancPluginCompressAndAnswerPngImage(context_, output, OrthancPluginPixelFormat_RGB24, 
+                                         tile.GetWidth(), tile.GetHeight(), 
+                                         tile.GetPitch(), tile.GetBuffer());
 }
 
 
-OrthancPluginContext* context_ = NULL;
-
-std::auto_ptr<OrthancWSI::PluginOrthancConnection>  orthanc_;
-std::auto_ptr<OrthancWSI::DicomPyramidCache>        cache_;
-std::auto_ptr<Orthanc::Semaphore>                   transcoderSemaphore_;
-
-
 static bool DisplayPerformanceWarning()
 {
   (void) DisplayPerformanceWarning;   // Disable warning about unused function
@@ -130,23 +82,41 @@ void ServePyramid(OrthancPluginRestOutput* output,
 
   OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
 
+  unsigned int tileWidth = locker.GetPyramid().GetTileWidth();
+  unsigned int tileHeight = locker.GetPyramid().GetTileHeight();
   unsigned int totalWidth = locker.GetPyramid().GetLevelWidth(0);
   unsigned int totalHeight = locker.GetPyramid().GetLevelHeight(0);
 
+  Json::Value sizes = Json::arrayValue;
   Json::Value resolutions = Json::arrayValue;
+  Json::Value tilesCount = Json::arrayValue;
   for (unsigned int i = 0; i < locker.GetPyramid().GetLevelCount(); i++)
   {
-    resolutions.append(static_cast<float>(totalWidth) /
-                       static_cast<float>(locker.GetPyramid().GetLevelWidth(i)));
+    unsigned int levelWidth = locker.GetPyramid().GetLevelWidth(i);
+    unsigned int levelHeight = locker.GetPyramid().GetLevelHeight(i);
+
+    resolutions.append(static_cast<float>(totalWidth) / static_cast<float>(levelWidth));
+    
+    Json::Value s = Json::arrayValue;
+    s.append(levelWidth);
+    s.append(levelHeight);
+    sizes.append(s);
+
+    s = Json::arrayValue;
+    s.append(OrthancWSI::CeilingDivision(levelWidth, tileWidth));
+    s.append(OrthancWSI::CeilingDivision(levelHeight, tileHeight));
+    tilesCount.append(s);
   }
 
   Json::Value result;
   result["ID"] = seriesId;
-  result["TotalWidth"] = totalWidth;
-  result["TotalHeight"] = totalHeight;
-  result["TileWidth"] = locker.GetPyramid().GetTileWidth();
-  result["TileHeight"] = locker.GetPyramid().GetTileHeight();
   result["Resolutions"] = resolutions;
+  result["Sizes"] = sizes;
+  result["TileHeight"] = tileHeight;
+  result["TileWidth"] = tileWidth;
+  result["TilesCount"] = tilesCount;
+  result["TotalHeight"] = totalHeight;
+  result["TotalWidth"] = totalWidth;
 
   std::string s = result.toStyledString();
   OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "application/json");
@@ -182,16 +152,17 @@ void ServeTile(OrthancPluginRestOutput* output,
   {
     OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
 
-    compression = locker.GetPyramid().GetImageCompression();
     format = locker.GetPyramid().GetPixelFormat();
     tileWidth = locker.GetPyramid().GetTileWidth();
     tileHeight = locker.GetPyramid().GetTileHeight();
 
-    if (!locker.GetPyramid().ReadRawTile(tile, 
+    if (!locker.GetPyramid().ReadRawTile(tile, compression, 
                                          static_cast<unsigned int>(level),
                                          static_cast<unsigned int>(tileX),
                                          static_cast<unsigned int>(tileY)))
     {
+      // Handling of missing tile (for sparse tiling): TODO parameter?
+      // AnswerSparseTile(output, tileWidth, tileHeight); return;
       throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
   }
@@ -355,8 +326,8 @@ extern "C"
 
     OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc.");
 
-    orthanc_.reset(new OrthancWSI::PluginOrthancConnection(context));
-    cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_));
+    orthanc_.reset(new OrthancPlugins::OrthancPluginConnection(context));
+    cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_, 10 /* Number of pyramids to be cached - TODO parameter */));
 
     OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback);
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/orthanc-wsi.git



More information about the debian-med-commit mailing list