changeset 286:031f34284a78 refactoring

integration mainline->refactoring
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 07 Jun 2019 16:03:50 +0200
parents 99d70e054ee4 (current diff) 5bccf7600ded (diff)
children 8d0c90e8b374
files Orthanc/Core/ChunkedBuffer.cpp Orthanc/Core/ChunkedBuffer.h Orthanc/Core/Enumerations.cpp Orthanc/Core/Enumerations.h Orthanc/Core/Logging.h Orthanc/Core/OrthancException.h Orthanc/Core/PrecompiledHeaders.h Orthanc/Core/Toolbox.cpp Orthanc/Core/Toolbox.h Orthanc/Plugins/Samples/Common/ExportedSymbols.list Orthanc/Plugins/Samples/Common/VersionScript.map Orthanc/Resources/CMake/BoostConfiguration.cmake Orthanc/Resources/CMake/Compiler.cmake Orthanc/Resources/CMake/DownloadPackage.cmake Orthanc/Resources/CMake/GoogleTestConfiguration.cmake Orthanc/Resources/CMake/JsonCppConfiguration.cmake Orthanc/Resources/CMake/PugixmlConfiguration.cmake Orthanc/Resources/CMake/ZlibConfiguration.cmake Orthanc/Resources/MinGW-W64-Toolchain32.cmake Orthanc/Resources/MinGW-W64-Toolchain64.cmake Orthanc/Resources/MinGWToolchain.cmake Orthanc/Resources/ThirdParty/VisualStudio/stdint.h Orthanc/Resources/WindowsResources.py Orthanc/Resources/WindowsResources.rc Orthanc/Sdk-0.9.5/orthanc/OrthancCPlugin.h Plugin/ChunkedBuffer.cpp Plugin/ChunkedBuffer.h Plugin/Dicom.cpp Plugin/Dicom.h Plugin/DicomResults.cpp Plugin/DicomResults.h Plugin/Plugin.h Plugin/Wado.cpp Plugin/Wado.h Resources/Samples/SendStow.py Resources/Samples/WadoRetrieveStudy.py Status.txt
diffstat 79 files changed, 12161 insertions(+), 12161 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Tue Dec 08 17:24:44 2015 +0100
+++ b/AUTHORS	Fri Jun 07 16:03:50 2019 +0200
@@ -1,13 +1,20 @@
-DICOM Web plugin for Orthanc
-============================
+DICOMweb plugin for Orthanc
+===========================
 
 
 Authors
 -------
 
-* Sebastien Jodogne <s.jodogne@gmail.com>
-  Department of Medical Physics
+* Sebastien Jodogne <s.jodogne@orthanc-labs.com>
+
+  Overall design and lead developer.
+
+* Department of Medical Physics
   University Hospital of Liege
+  4000 Liege
   Belgium
 
-  Overall design and lead developer.
+* Osimis S.A. <info@osimis.io>
+  Rue des Chasseurs Ardennais 3
+  4031 Liege 
+  Belgium
--- a/CMakeLists.txt	Tue Dec 08 17:24:44 2015 +0100
+++ b/CMakeLists.txt	Fri Jun 07 16:03:50 2019 +0200
@@ -1,6 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2019 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Affero General Public License
@@ -22,44 +23,55 @@
 
 set(ORTHANC_DICOM_WEB_VERSION "mainline")
 
+if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_VERSION "1.5.5")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
 
 # Parameters of the build
 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(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
 
 # Advanced parameters to fine-tune linking against system libraries
-set(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
 set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)")
-set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
-set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
-set(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of zlib")
-set(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml")
 set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+set(ORTHANC_SDK_VERSION "1.5.4" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"1.5.4\", or \"framework\")")
 
-# Distribution-specific settings
-set(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
-mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE)
+
+
+# Download and setup the Orthanc framework
+include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/DownloadOrthancFramework.cmake)
 
-set(USE_PUGIXML ON)
-set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/Orthanc)
-include(CheckIncludeFiles)
-include(CheckIncludeFileCXX)
-include(CheckLibraryExists)
-include(FindPythonInterp)
-include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+set(ORTHANC_FRAMEWORK_PLUGIN ON)
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
 
-include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/GoogleTestConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/PugixmlConfiguration.cmake)
+set(ENABLE_LOCALE ON)         # Enable support for locales (notably in Boost)
+set(ENABLE_GOOGLE_TEST ON)
+set(ENABLE_PUGIXML ON)
+set(USE_BOOST_ICONV ON)
+
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include_directories(${ORTHANC_ROOT})
+
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake)
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
-  include_directories(${ORTHANC_ROOT}/Sdk-0.9.5)
+  if (ORTHANC_SDK_VERSION STREQUAL "1.5.4")
+    include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.4)
+  elseif (ORTHANC_SDK_VERSION STREQUAL "framework")
+    include_directories(${ORTHANC_ROOT}/Plugins/Include)
+  else()
+    message(FATAL_ERROR "Unsupported version of the Orthanc plugin SDK: ${ORTHANC_SDK_VERSION}")
+  endif()
 else ()
   CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
   if (NOT HAVE_ORTHANC_H)
@@ -98,34 +110,31 @@
 endif()
 
 
+include_directories(${ORTHANC_ROOT}/Core)  # To access "OrthancException.h"
+
 add_definitions(
-  -DORTHANC_ENABLE_MD5=0
-  -DORTHANC_ENABLE_BASE64=0
-  -DORTHANC_ENABLE_LOGGING=0
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=1
   )
 
 set(CORE_SOURCES
-  ${BOOST_SOURCES}
-  ${JSONCPP_SOURCES}
-  ${ZLIB_SOURCES}
-  ${PUGIXML_SOURCES}
+  Plugin/Configuration.cpp
+  Plugin/GdcmParsedDicomFile.cpp
 
-  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
-  ${ORTHANC_ROOT}/Core/Enumerations.cpp
-  ${ORTHANC_ROOT}/Core/Toolbox.cpp
-
-  Plugin/ChunkedBuffer.cpp
-  Plugin/Configuration.cpp
-  Plugin/Dicom.cpp
-  Plugin/DicomResults.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  ${ORTHANC_CORE_SOURCES}
   )
 
 add_library(OrthancDicomWeb SHARED ${CORE_SOURCES}
+  ${CMAKE_SOURCE_DIR}/Plugin/DicomWebClient.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/DicomWebFormatter.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/DicomWebServers.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/Plugin.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/QidoRs.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/StowRs.cpp
-  ${CMAKE_SOURCE_DIR}/Plugin/Wado.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/WadoRs.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/WadoRsRetrieveFrames.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/WadoUri.cpp
   ${AUTOGENERATED_SOURCES}
   )
 
@@ -148,11 +157,15 @@
 
 add_executable(UnitTests
   ${CORE_SOURCES}
-  ${GTEST_SOURCES}
+  ${GOOGLE_TEST_SOURCES}
+  ${CMAKE_SOURCE_DIR}/Plugin/DicomWebServers.cpp
   UnitTestsSources/UnitTestsMain.cpp
   )
 
-target_link_libraries(UnitTests ${GDCM_LIBRARIES})
+target_link_libraries(UnitTests
+  ${GDCM_LIBRARIES}
+  ${GOOGLE_TEST_LIBRARIES}
+  )
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM)
   add_dependencies(OrthancDicomWeb GDCM)
--- a/NEWS	Tue Dec 08 17:24:44 2015 +0100
+++ b/NEWS	Fri Jun 07 16:03:50 2019 +0200
@@ -1,15 +1,76 @@
 Pending changes in the mainline
 ===============================
 
+=> Minimum SDK version: 1.5.7 <=
+
+* Handling of the HTTP header "Forwarded" for WADO-RS
+
+
+Version 0.6 (2019-02-27)
+========================
+
+=> Minimum SDK version: 1.5.4 <=
+
+* Sending "HttpHeaders" of the "DicomWeb.Servers" configuration to remote DICOMweb servers
+* Improved WADO-RS compatibility if Orthanc is acting as a DICOMweb client
+* More detailed information about errors is provided in the HTTP answers
+* Fix issue #96 / #5 (WADO-RS: RetrieveFrames rejects valid accept headers)
+* Fix issue #111 / #3 (QIDO-RS: wrong serialization of empty values)
+* Fix issue #112 / #4 (QIDO-RS: wrong serialization of number values)
+* Fix issue #113 / #2 (QIDO-RS: wrong serialization of PN VR)
+* Upgrade to GDCM 2.8.8 in static builds
+
+
+Version 0.5 (2018-04-19)
+========================
+
+* New option: "QidoCaseSensitive" to make queries to QIDO-RS server case insensitive
+* Defaults to JSON answers instead of XML
+* Use of "application/dicom+json" MIME type instead of "application/json"
+* Added "?expand" argument to "/servers" route
+* Fix generation of numeric tags part of sequences for ".../metadata" routes
+* Support for OpenBSD
+* Support for Linux Standard Base
+* Upgrade to GDCM 2.8.4 in static builds
+* Resort to Orthanc framework
+
+
+Version 0.4 (2017-07-19)
+========================
+
+* Improved robustness in the STOW-RS server (occurrences of "\r\n\r\n" in DICOM are supported)
+* Performance warning if runtime debug assertions are turned on
+* WADO-RS client supports quoted Content-Type header in HTTP answers
+* Added "Arguments" to WADO-RS and STOW-RS client to handle query arguments in uri
+* Using MIME types of DICOM version 2017c in WADO RetrieveFrames
+* Fix issue #53 (DICOMWeb plugin support for "limit" and "offset" parameters in QIDO-RS)
+* Fix issue #28 (Non-compliant enumerations for "accept" header for WADO RetrieveFrames)
+
+
+Version 0.3 (2016-06-28)
+========================
+
+=> Minimum SDK version: 1.1.0 <=
+
+* STOW-RS client with URI "/{dicom-web}/servers/{id}/stow"
+* QIDO-RS and WADO-RS client with URI "/{dicom-web}/servers/{id}/get"
+* Retrieval of DICOM instances with WADO-RS through URI "/{dicom-web}/servers/{id}/retrieve"
+* Improved robustness in the STOW-RS server
+* Fix issue #13 (QIDO-RS study-level query is slow)
+* Fix issue #14 (Aggregate fields empty for QIDO-RS study/series-level queries)
+
+
+Version 0.2 (2015-12-10)
+========================
 
 => Minimum SDK version: 0.9.5 <=
 
+* Support of WADO-RS - RetrieveFrames
 * QIDO-RS now takes advantage of "/tools/find"
 * Upgrade to GDCM 2.6.0 for static and Windows builds
 
 
-
-Version 0.1 (2015/08/03)
+Version 0.1 (2015-08-03)
 ========================
 
 => Minimum SDK version: 0.9.1 <=
@@ -29,7 +90,7 @@
 * Upgrade to Boost 1.58.0 for static and Windows builds
 
 
-2015/03/13
+2015-03-13
 ==========
 
 * Initial commit
--- a/Orthanc/Core/ChunkedBuffer.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "ChunkedBuffer.h"
-
-#include <cassert>
-#include <string.h>
-
-
-namespace Orthanc
-{
-  void ChunkedBuffer::Clear()
-  {
-    numBytes_ = 0;
-
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  void ChunkedBuffer::AddChunk(const char* chunkData,
-                               size_t chunkSize)
-  {
-    if (chunkSize == 0)
-    {
-      return;
-    }
-
-    assert(chunkData != NULL);
-    chunks_.push_back(new std::string(chunkData, chunkSize));
-    numBytes_ += chunkSize;
-  }
-
-
-  void ChunkedBuffer::AddChunk(const std::string& chunk)
-  {
-    if (chunk.size() > 0)
-    {
-      AddChunk(&chunk[0], chunk.size());
-    }
-  }
-
-
-  void ChunkedBuffer::Flatten(std::string& result)
-  {
-    result.resize(numBytes_);
-
-    size_t pos = 0;
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      assert(*it != NULL);
-
-      size_t s = (*it)->size();
-      if (s != 0)
-      {
-        memcpy(&result[pos], (*it)->c_str(), s);
-        pos += s;
-      }
-
-      delete *it;
-    }
-
-    chunks_.clear();
-  }
-}
--- a/Orthanc/Core/ChunkedBuffer.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 <string>
-
-namespace Orthanc
-{
-  class ChunkedBuffer
-  {
-  private:
-    typedef std::list<std::string*>  Chunks;
-    size_t numBytes_;
-    Chunks chunks_;
-  
-    void Clear();
-
-  public:
-    ChunkedBuffer() : numBytes_(0)
-    {
-    }
-
-    ~ChunkedBuffer()
-    {
-      Clear();
-    }
-
-    size_t GetNumBytes() const
-    {
-      return numBytes_;
-    }
-
-    void AddChunk(const char* chunkData,
-                  size_t chunkSize);
-
-    void AddChunk(const std::string& chunk);
-
-    void Flatten(std::string& result);
-  };
-}
--- a/Orthanc/Core/Enumerations.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1154 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Enumerations.h"
-
-#include "OrthancException.h"
-#include "Toolbox.h"
-
-#include <string.h>
-
-namespace Orthanc
-{
-  // This function is autogenerated by the script
-  // "Resources/GenerateErrorCodes.py"
-  const char* EnumerationToString(ErrorCode error)
-  {
-    switch (error)
-    {
-      case ErrorCode_InternalError:
-        return "Internal error";
-
-      case ErrorCode_Success:
-        return "Success";
-
-      case ErrorCode_Plugin:
-        return "Error encountered within the plugin engine";
-
-      case ErrorCode_NotImplemented:
-        return "Not implemented yet";
-
-      case ErrorCode_ParameterOutOfRange:
-        return "Parameter out of range";
-
-      case ErrorCode_NotEnoughMemory:
-        return "Not enough memory";
-
-      case ErrorCode_BadParameterType:
-        return "Bad type for a parameter";
-
-      case ErrorCode_BadSequenceOfCalls:
-        return "Bad sequence of calls";
-
-      case ErrorCode_InexistentItem:
-        return "Accessing an inexistent item";
-
-      case ErrorCode_BadRequest:
-        return "Bad request";
-
-      case ErrorCode_NetworkProtocol:
-        return "Error in the network protocol";
-
-      case ErrorCode_SystemCommand:
-        return "Error while calling a system command";
-
-      case ErrorCode_Database:
-        return "Error with the database engine";
-
-      case ErrorCode_UriSyntax:
-        return "Badly formatted URI";
-
-      case ErrorCode_InexistentFile:
-        return "Inexistent file";
-
-      case ErrorCode_CannotWriteFile:
-        return "Cannot write to file";
-
-      case ErrorCode_BadFileFormat:
-        return "Bad file format";
-
-      case ErrorCode_Timeout:
-        return "Timeout";
-
-      case ErrorCode_UnknownResource:
-        return "Unknown resource";
-
-      case ErrorCode_IncompatibleDatabaseVersion:
-        return "Incompatible version of the database";
-
-      case ErrorCode_FullStorage:
-        return "The file storage is full";
-
-      case ErrorCode_CorruptedFile:
-        return "Corrupted file (e.g. inconsistent MD5 hash)";
-
-      case ErrorCode_InexistentTag:
-        return "Inexistent tag";
-
-      case ErrorCode_ReadOnly:
-        return "Cannot modify a read-only data structure";
-
-      case ErrorCode_IncompatibleImageFormat:
-        return "Incompatible format of the images";
-
-      case ErrorCode_IncompatibleImageSize:
-        return "Incompatible size of the images";
-
-      case ErrorCode_SharedLibrary:
-        return "Error while using a shared library (plugin)";
-
-      case ErrorCode_UnknownPluginService:
-        return "Plugin invoking an unknown service";
-
-      case ErrorCode_UnknownDicomTag:
-        return "Unknown DICOM tag";
-
-      case ErrorCode_BadJson:
-        return "Cannot parse a JSON document";
-
-      case ErrorCode_Unauthorized:
-        return "Bad credentials were provided to an HTTP request";
-
-      case ErrorCode_BadFont:
-        return "Badly formatted font file";
-
-      case ErrorCode_DatabasePlugin:
-        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
-
-      case ErrorCode_StorageAreaPlugin:
-        return "Error in the plugin implementing a custom storage area";
-
-      case ErrorCode_EmptyRequest:
-        return "The request is empty";
-
-      case ErrorCode_NotAcceptable:
-        return "Cannot send a response which is acceptable according to the Accept HTTP header";
-
-      case ErrorCode_SQLiteNotOpened:
-        return "SQLite: The database is not opened";
-
-      case ErrorCode_SQLiteAlreadyOpened:
-        return "SQLite: Connection is already open";
-
-      case ErrorCode_SQLiteCannotOpen:
-        return "SQLite: Unable to open the database";
-
-      case ErrorCode_SQLiteStatementAlreadyUsed:
-        return "SQLite: This cached statement is already being referred to";
-
-      case ErrorCode_SQLiteExecute:
-        return "SQLite: Cannot execute a command";
-
-      case ErrorCode_SQLiteRollbackWithoutTransaction:
-        return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)";
-
-      case ErrorCode_SQLiteCommitWithoutTransaction:
-        return "SQLite: Committing a nonexistent transaction";
-
-      case ErrorCode_SQLiteRegisterFunction:
-        return "SQLite: Unable to register a function";
-
-      case ErrorCode_SQLiteFlush:
-        return "SQLite: Unable to flush the database";
-
-      case ErrorCode_SQLiteCannotRun:
-        return "SQLite: Cannot run a cached statement";
-
-      case ErrorCode_SQLiteCannotStep:
-        return "SQLite: Cannot step over a cached statement";
-
-      case ErrorCode_SQLiteBindOutOfRange:
-        return "SQLite: Bing a value while out of range (serious error)";
-
-      case ErrorCode_SQLitePrepareStatement:
-        return "SQLite: Cannot prepare a cached statement";
-
-      case ErrorCode_SQLiteTransactionAlreadyStarted:
-        return "SQLite: Beginning the same transaction twice";
-
-      case ErrorCode_SQLiteTransactionCommit:
-        return "SQLite: Failure when committing the transaction";
-
-      case ErrorCode_SQLiteTransactionBegin:
-        return "SQLite: Cannot start a transaction";
-
-      case ErrorCode_DirectoryOverFile:
-        return "The directory to be created is already occupied by a regular file";
-
-      case ErrorCode_FileStorageCannotWrite:
-        return "Unable to create a subdirectory or a file in the file storage";
-
-      case ErrorCode_DirectoryExpected:
-        return "The specified path does not point to a directory";
-
-      case ErrorCode_HttpPortInUse:
-        return "The TCP port of the HTTP server is already in use";
-
-      case ErrorCode_DicomPortInUse:
-        return "The TCP port of the DICOM server is already in use";
-
-      case ErrorCode_BadHttpStatusInRest:
-        return "This HTTP status is not allowed in a REST API";
-
-      case ErrorCode_RegularFileExpected:
-        return "The specified path does not point to a regular file";
-
-      case ErrorCode_PathToExecutable:
-        return "Unable to get the path to the executable";
-
-      case ErrorCode_MakeDirectory:
-        return "Cannot create a directory";
-
-      case ErrorCode_BadApplicationEntityTitle:
-        return "An application entity title (AET) cannot be empty or be longer than 16 characters";
-
-      case ErrorCode_NoCFindHandler:
-        return "No request handler factory for DICOM C-FIND SCP";
-
-      case ErrorCode_NoCMoveHandler:
-        return "No request handler factory for DICOM C-MOVE SCP";
-
-      case ErrorCode_NoCStoreHandler:
-        return "No request handler factory for DICOM C-STORE SCP";
-
-      case ErrorCode_NoApplicationEntityFilter:
-        return "No application entity filter";
-
-      case ErrorCode_NoSopClassOrInstance:
-        return "DicomUserConnection: Unable to find the SOP class and instance";
-
-      case ErrorCode_NoPresentationContext:
-        return "DicomUserConnection: No acceptable presentation context for modality";
-
-      case ErrorCode_DicomFindUnavailable:
-        return "DicomUserConnection: The C-FIND command is not supported by the remote SCP";
-
-      case ErrorCode_DicomMoveUnavailable:
-        return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP";
-
-      case ErrorCode_CannotStoreInstance:
-        return "Cannot store an instance";
-
-      case ErrorCode_CreateDicomNotString:
-        return "Only string values are supported when creating DICOM instances";
-
-      case ErrorCode_CreateDicomOverrideTag:
-        return "Trying to override a value inherited from a parent module";
-
-      case ErrorCode_CreateDicomUseContent:
-        return "Use \"Content\" to inject an image into a new DICOM instance";
-
-      case ErrorCode_CreateDicomNoPayload:
-        return "No payload is present for one instance in the series";
-
-      case ErrorCode_CreateDicomUseDataUriScheme:
-        return "The payload of the DICOM instance must be specified according to Data URI scheme";
-
-      case ErrorCode_CreateDicomBadParent:
-        return "Trying to attach a new DICOM instance to an inexistent resource";
-
-      case ErrorCode_CreateDicomParentIsInstance:
-        return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)";
-
-      case ErrorCode_CreateDicomParentEncoding:
-        return "Unable to get the encoding of the parent resource";
-
-      case ErrorCode_UnknownModality:
-        return "Unknown modality";
-
-      case ErrorCode_BadJobOrdering:
-        return "Bad ordering of filters in a job";
-
-      case ErrorCode_JsonToLuaTable:
-        return "Cannot convert the given JSON object to a Lua table";
-
-      case ErrorCode_CannotCreateLua:
-        return "Cannot create the Lua context";
-
-      case ErrorCode_CannotExecuteLua:
-        return "Cannot execute a Lua command";
-
-      case ErrorCode_LuaAlreadyExecuted:
-        return "Arguments cannot be pushed after the Lua function is executed";
-
-      case ErrorCode_LuaBadOutput:
-        return "The Lua function does not give the expected number of outputs";
-
-      case ErrorCode_NotLuaPredicate:
-        return "The Lua function is not a predicate (only true/false outputs allowed)";
-
-      case ErrorCode_LuaReturnsNoString:
-        return "The Lua function does not return a string";
-
-      case ErrorCode_StorageAreaAlreadyRegistered:
-        return "Another plugin has already registered a custom storage area";
-
-      case ErrorCode_DatabaseBackendAlreadyRegistered:
-        return "Another plugin has already registered a custom database back-end";
-
-      case ErrorCode_DatabaseNotInitialized:
-        return "Plugin trying to call the database during its initialization";
-
-      case ErrorCode_SslDisabled:
-        return "Orthanc has been built without SSL support";
-
-      case ErrorCode_CannotOrderSlices:
-        return "Unable to order the slices of the series";
-
-      case ErrorCode_NoWorklistHandler:
-        return "No request handler factory for DICOM C-Find Modality SCP";
-
-      default:
-        if (error >= ErrorCode_START_PLUGINS)
-        {
-          return "Error encountered within some plugin";
-        }
-        else
-        {
-          return "Unknown error code";
-        }
-    }
-  }
-
-
-  const char* EnumerationToString(HttpMethod method)
-  {
-    switch (method)
-    {
-      case HttpMethod_Get:
-        return "GET";
-
-      case HttpMethod_Post:
-        return "POST";
-
-      case HttpMethod_Delete:
-        return "DELETE";
-
-      case HttpMethod_Put:
-        return "PUT";
-
-      default:
-        return "?";
-    }
-  }
-
-
-  const char* EnumerationToString(HttpStatus status)
-  {
-    switch (status)
-    {
-    case HttpStatus_100_Continue:
-      return "Continue";
-
-    case HttpStatus_101_SwitchingProtocols:
-      return "Switching Protocols";
-
-    case HttpStatus_102_Processing:
-      return "Processing";
-
-    case HttpStatus_200_Ok:
-      return "OK";
-
-    case HttpStatus_201_Created:
-      return "Created";
-
-    case HttpStatus_202_Accepted:
-      return "Accepted";
-
-    case HttpStatus_203_NonAuthoritativeInformation:
-      return "Non-Authoritative Information";
-
-    case HttpStatus_204_NoContent:
-      return "No Content";
-
-    case HttpStatus_205_ResetContent:
-      return "Reset Content";
-
-    case HttpStatus_206_PartialContent:
-      return "Partial Content";
-
-    case HttpStatus_207_MultiStatus:
-      return "Multi-Status";
-
-    case HttpStatus_208_AlreadyReported:
-      return "Already Reported";
-
-    case HttpStatus_226_IMUsed:
-      return "IM Used";
-
-    case HttpStatus_300_MultipleChoices:
-      return "Multiple Choices";
-
-    case HttpStatus_301_MovedPermanently:
-      return "Moved Permanently";
-
-    case HttpStatus_302_Found:
-      return "Found";
-
-    case HttpStatus_303_SeeOther:
-      return "See Other";
-
-    case HttpStatus_304_NotModified:
-      return "Not Modified";
-
-    case HttpStatus_305_UseProxy:
-      return "Use Proxy";
-
-    case HttpStatus_307_TemporaryRedirect:
-      return "Temporary Redirect";
-
-    case HttpStatus_400_BadRequest:
-      return "Bad Request";
-
-    case HttpStatus_401_Unauthorized:
-      return "Unauthorized";
-
-    case HttpStatus_402_PaymentRequired:
-      return "Payment Required";
-
-    case HttpStatus_403_Forbidden:
-      return "Forbidden";
-
-    case HttpStatus_404_NotFound:
-      return "Not Found";
-
-    case HttpStatus_405_MethodNotAllowed:
-      return "Method Not Allowed";
-
-    case HttpStatus_406_NotAcceptable:
-      return "Not Acceptable";
-
-    case HttpStatus_407_ProxyAuthenticationRequired:
-      return "Proxy Authentication Required";
-
-    case HttpStatus_408_RequestTimeout:
-      return "Request Timeout";
-
-    case HttpStatus_409_Conflict:
-      return "Conflict";
-
-    case HttpStatus_410_Gone:
-      return "Gone";
-
-    case HttpStatus_411_LengthRequired:
-      return "Length Required";
-
-    case HttpStatus_412_PreconditionFailed:
-      return "Precondition Failed";
-
-    case HttpStatus_413_RequestEntityTooLarge:
-      return "Request Entity Too Large";
-
-    case HttpStatus_414_RequestUriTooLong:
-      return "Request-URI Too Long";
-
-    case HttpStatus_415_UnsupportedMediaType:
-      return "Unsupported Media Type";
-
-    case HttpStatus_416_RequestedRangeNotSatisfiable:
-      return "Requested Range Not Satisfiable";
-
-    case HttpStatus_417_ExpectationFailed:
-      return "Expectation Failed";
-
-    case HttpStatus_422_UnprocessableEntity:
-      return "Unprocessable Entity";
-
-    case HttpStatus_423_Locked:
-      return "Locked";
-
-    case HttpStatus_424_FailedDependency:
-      return "Failed Dependency";
-
-    case HttpStatus_426_UpgradeRequired:
-      return "Upgrade Required";
-
-    case HttpStatus_500_InternalServerError:
-      return "Internal Server Error";
-
-    case HttpStatus_501_NotImplemented:
-      return "Not Implemented";
-
-    case HttpStatus_502_BadGateway:
-      return "Bad Gateway";
-
-    case HttpStatus_503_ServiceUnavailable:
-      return "Service Unavailable";
-
-    case HttpStatus_504_GatewayTimeout:
-      return "Gateway Timeout";
-
-    case HttpStatus_505_HttpVersionNotSupported:
-      return "HTTP Version Not Supported";
-
-    case HttpStatus_506_VariantAlsoNegotiates:
-      return "Variant Also Negotiates";
-
-    case HttpStatus_507_InsufficientStorage:
-      return "Insufficient Storage";
-
-    case HttpStatus_509_BandwidthLimitExceeded:
-      return "Bandwidth Limit Exceeded";
-
-    case HttpStatus_510_NotExtended:
-      return "Not Extended";
-
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return "Patient";
-
-      case ResourceType_Study:
-        return "Study";
-
-      case ResourceType_Series:
-        return "Series";
-
-      case ResourceType_Instance:
-        return "Instance";
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(ImageFormat format)
-  {
-    switch (format)
-    {
-      case ImageFormat_Png:
-        return "Png";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(Encoding encoding)
-  {
-    switch (encoding)
-    {
-      case Encoding_Ascii:
-        return "Ascii";
-
-      case Encoding_Utf8:
-        return "Utf8";
-
-      case Encoding_Latin1:
-        return "Latin1";
-
-      case Encoding_Latin2:
-        return "Latin2";
-
-      case Encoding_Latin3:
-        return "Latin3";
-
-      case Encoding_Latin4:
-        return "Latin4";
-
-      case Encoding_Latin5:
-        return "Latin5";
-
-      case Encoding_Cyrillic:
-        return "Cyrillic";
-
-      case Encoding_Windows1251:
-        return "Windows1251";
-
-      case Encoding_Arabic:
-        return "Arabic";
-
-      case Encoding_Greek:
-        return "Greek";
-
-      case Encoding_Hebrew:
-        return "Hebrew";
-
-      case Encoding_Thai:
-        return "Thai";
-
-      case Encoding_Japanese:
-        return "Japanese";
-
-      case Encoding_Chinese:
-        return "Chinese";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(PhotometricInterpretation photometric)
-  {
-    switch (photometric)
-    {
-      case PhotometricInterpretation_RGB:
-        return "RGB";
-
-      case PhotometricInterpretation_Monochrome1:
-        return "Monochrome1";
-
-      case PhotometricInterpretation_Monochrome2:
-        return "Monochrome2";
-
-      case PhotometricInterpretation_ARGB:
-        return "ARGB";
-
-      case PhotometricInterpretation_CMYK:
-        return "CMYK";
-
-      case PhotometricInterpretation_HSV:
-        return "HSV";
-
-      case PhotometricInterpretation_Palette:
-        return "Palette color";
-
-      case PhotometricInterpretation_YBRFull:
-        return "YBR full";
-
-      case PhotometricInterpretation_YBRFull422:
-        return "YBR full 422";
-
-      case PhotometricInterpretation_YBRPartial420:
-        return "YBR partial 420"; 
-
-      case PhotometricInterpretation_YBRPartial422:
-        return "YBR partial 422"; 
-
-      case PhotometricInterpretation_YBR_ICT:
-        return "YBR ICT"; 
-
-      case PhotometricInterpretation_YBR_RCT:
-        return "YBR RCT"; 
-
-      case PhotometricInterpretation_Unknown:
-        return "Unknown";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(RequestOrigin origin)
-  {
-    switch (origin)
-    {
-      case RequestOrigin_Unknown:
-        return "Unknown";
-
-      case RequestOrigin_DicomProtocol:
-        return "DicomProtocol";
-
-      case RequestOrigin_RestApi:
-        return "RestApi";
-
-      case RequestOrigin_Plugins:
-        return "Plugins";
-
-      case RequestOrigin_Lua:
-        return "Lua";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(LogLevel level)
-  {
-    switch (level)
-    {
-      case LogLevel_Error:
-        return "ERROR";
-
-      case LogLevel_Warning:
-        return "WARNING";
-
-      case LogLevel_Info:
-        return "INFO";
-
-      case LogLevel_Trace:
-        return "TRACE";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  Encoding StringToEncoding(const char* encoding)
-  {
-    std::string s(encoding);
-    Toolbox::ToUpperCase(s);
-
-    if (s == "UTF8")
-    {
-      return Encoding_Utf8;
-    }
-
-    if (s == "ASCII")
-    {
-      return Encoding_Ascii;
-    }
-
-    if (s == "LATIN1")
-    {
-      return Encoding_Latin1;
-    }
-
-    if (s == "LATIN2")
-    {
-      return Encoding_Latin2;
-    }
-
-    if (s == "LATIN3")
-    {
-      return Encoding_Latin3;
-    }
-
-    if (s == "LATIN4")
-    {
-      return Encoding_Latin4;
-    }
-
-    if (s == "LATIN5")
-    {
-      return Encoding_Latin5;
-    }
-
-    if (s == "CYRILLIC")
-    {
-      return Encoding_Cyrillic;
-    }
-
-    if (s == "WINDOWS1251")
-    {
-      return Encoding_Windows1251;
-    }
-
-    if (s == "ARABIC")
-    {
-      return Encoding_Arabic;
-    }
-
-    if (s == "GREEK")
-    {
-      return Encoding_Greek;
-    }
-
-    if (s == "HEBREW")
-    {
-      return Encoding_Hebrew;
-    }
-
-    if (s == "THAI")
-    {
-      return Encoding_Thai;
-    }
-
-    if (s == "JAPANESE")
-    {
-      return Encoding_Japanese;
-    }
-
-    if (s == "CHINESE")
-    {
-      return Encoding_Chinese;
-    }
-
-    throw OrthancException(ErrorCode_ParameterOutOfRange);
-  }
-
-
-  ResourceType StringToResourceType(const char* type)
-  {
-    std::string s(type);
-    Toolbox::ToUpperCase(s);
-
-    if (s == "PATIENT" || s == "PATIENTS")
-    {
-      return ResourceType_Patient;
-    }
-    else if (s == "STUDY" || s == "STUDIES")
-    {
-      return ResourceType_Study;
-    }
-    else if (s == "SERIES")
-    {
-      return ResourceType_Series;
-    }
-    else if (s == "INSTANCE"  || s == "IMAGE" || 
-             s == "INSTANCES" || s == "IMAGES")
-    {
-      return ResourceType_Instance;
-    }
-
-    throw OrthancException(ErrorCode_ParameterOutOfRange);
-  }
-
-
-  ImageFormat StringToImageFormat(const char* format)
-  {
-    std::string s(format);
-    Toolbox::ToUpperCase(s);
-
-    if (s == "PNG")
-    {
-      return ImageFormat_Png;
-    }
-
-    throw OrthancException(ErrorCode_ParameterOutOfRange);
-  }
-
-
-  LogLevel StringToLogLevel(const char *level)
-  {
-    if (strcmp(level, "ERROR") == 0)
-    {
-      return LogLevel_Error;
-    }
-    else if (strcmp(level, "WARNING") == 0)
-    {
-      return LogLevel_Warning;
-    }
-    else if (strcmp(level, "INFO") == 0)
-    {
-      return LogLevel_Info;
-    }
-    else if (strcmp(level, "TRACE") == 0)
-    {
-      return LogLevel_Trace;
-    }
-    else 
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
-  unsigned int GetBytesPerPixel(PixelFormat format)
-  {
-    switch (format)
-    {
-      case PixelFormat_Grayscale8:
-        return 1;
-
-      case PixelFormat_Grayscale16:
-      case PixelFormat_SignedGrayscale16:
-        return 2;
-
-      case PixelFormat_RGB24:
-        return 3;
-
-      case PixelFormat_RGBA32:
-        return 4;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  bool GetDicomEncoding(Encoding& encoding,
-                        const char* specificCharacterSet)
-  {
-    std::string s = specificCharacterSet;
-    Toolbox::ToUpperCase(s);
-
-    // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
-    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
-    if (s == "ISO_IR 6" ||
-        s == "ISO_IR 192" ||
-        s == "ISO 2022 IR 6")
-    {
-      encoding = Encoding_Utf8;
-    }
-    else if (s == "ISO_IR 100" ||
-             s == "ISO 2022 IR 100")
-    {
-      encoding = Encoding_Latin1;
-    }
-    else if (s == "ISO_IR 101" ||
-             s == "ISO 2022 IR 101")
-    {
-      encoding = Encoding_Latin2;
-    }
-    else if (s == "ISO_IR 109" ||
-             s == "ISO 2022 IR 109")
-    {
-      encoding = Encoding_Latin3;
-    }
-    else if (s == "ISO_IR 110" ||
-             s == "ISO 2022 IR 110")
-    {
-      encoding = Encoding_Latin4;
-    }
-    else if (s == "ISO_IR 148" ||
-             s == "ISO 2022 IR 148")
-    {
-      encoding = Encoding_Latin5;
-    }
-    else if (s == "ISO_IR 144" ||
-             s == "ISO 2022 IR 144")
-    {
-      encoding = Encoding_Cyrillic;
-    }
-    else if (s == "ISO_IR 127" ||
-             s == "ISO 2022 IR 127")
-    {
-      encoding = Encoding_Arabic;
-    }
-    else if (s == "ISO_IR 126" ||
-             s == "ISO 2022 IR 126")
-    {
-      encoding = Encoding_Greek;
-    }
-    else if (s == "ISO_IR 138" ||
-             s == "ISO 2022 IR 138")
-    {
-      encoding = Encoding_Hebrew;
-    }
-    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
-    {
-      encoding = Encoding_Thai;
-    }
-    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
-    {
-      encoding = Encoding_Japanese;
-    }
-    else if (s == "GB18030")
-    {
-      encoding = Encoding_Chinese;
-    }
-    /*
-      else if (s == "ISO 2022 IR 149")
-      {
-      TODO
-      }
-      else if (s == "ISO 2022 IR 159")
-      {
-      TODO
-      }
-      else if (s == "ISO 2022 IR 87")
-      {
-      TODO
-      }
-    */
-    else
-    {
-      return false;
-    }
-
-    // The encoding was properly detected
-    return true;
-  }
-
-
-  ResourceType GetChildResourceType(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return ResourceType_Study;
-
-      case ResourceType_Study:
-        return ResourceType_Series;
-        
-      case ResourceType_Series:
-        return ResourceType_Instance;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  ResourceType GetParentResourceType(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Study:
-        return ResourceType_Patient;
-        
-      case ResourceType_Series:
-        return ResourceType_Study;
-
-      case ResourceType_Instance:
-        return ResourceType_Series;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  DicomModule GetModule(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return DicomModule_Patient;
-
-      case ResourceType_Study:
-        return DicomModule_Study;
-        
-      case ResourceType_Series:
-        return DicomModule_Series;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-
-  const char* GetDicomSpecificCharacterSet(Encoding encoding)
-  {
-    // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
-    switch (encoding)
-    {
-      case Encoding_Utf8:
-      case Encoding_Ascii:
-        return "ISO_IR 192";
-
-      case Encoding_Latin1:
-        return "ISO_IR 100";
-
-      case Encoding_Latin2:
-        return "ISO_IR 101";
-
-      case Encoding_Latin3:
-        return "ISO_IR 109";
-
-      case Encoding_Latin4:
-        return "ISO_IR 110";
-
-      case Encoding_Latin5:
-        return "ISO_IR 148";
-
-      case Encoding_Cyrillic:
-        return "ISO_IR 144";
-
-      case Encoding_Arabic:
-        return "ISO_IR 127";
-
-      case Encoding_Greek:
-        return "ISO_IR 126";
-
-      case Encoding_Hebrew:
-        return "ISO_IR 138";
-
-      case Encoding_Japanese:
-        return "ISO_IR 13";
-
-      case Encoding_Chinese:
-        return "GB18030";
-
-      case Encoding_Thai:
-        return "ISO_IR 166";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  // This function is autogenerated by the script
-  // "Resources/GenerateErrorCodes.py"
-  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error)
-  {
-    switch (error)
-    {
-      case ErrorCode_Success:
-        return HttpStatus_200_Ok;
-
-      case ErrorCode_ParameterOutOfRange:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_BadParameterType:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_InexistentItem:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_BadRequest:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_UriSyntax:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_InexistentFile:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_BadFileFormat:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_UnknownResource:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_InexistentTag:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_BadJson:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_Unauthorized:
-        return HttpStatus_401_Unauthorized;
-
-      case ErrorCode_NotAcceptable:
-        return HttpStatus_406_NotAcceptable;
-
-      default:
-        return HttpStatus_500_InternalServerError;
-    }
-  }
-
-
-  bool IsUserContentType(FileContentType type)
-  {
-    return (type >= FileContentType_StartUser &&
-            type <= FileContentType_EndUser);
-  }
-}
--- a/Orthanc/Core/Enumerations.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,470 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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
-
-namespace Orthanc
-{
-  enum Endianness
-  {
-    Endianness_Unknown,
-    Endianness_Big,
-    Endianness_Little
-  };
-
-  // This enumeration is autogenerated by the script
-  // "Resources/GenerateErrorCodes.py"
-  enum ErrorCode
-  {
-    ErrorCode_InternalError = -1    /*!< Internal error */,
-    ErrorCode_Success = 0    /*!< Success */,
-    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_BadParameterType = 5    /*!< Bad type for a parameter */,
-    ErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
-    ErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
-    ErrorCode_BadRequest = 8    /*!< Bad request */,
-    ErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
-    ErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
-    ErrorCode_Database = 11    /*!< Error with the database engine */,
-    ErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
-    ErrorCode_InexistentFile = 13    /*!< Inexistent file */,
-    ErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
-    ErrorCode_BadFileFormat = 15    /*!< Bad file format */,
-    ErrorCode_Timeout = 16    /*!< Timeout */,
-    ErrorCode_UnknownResource = 17    /*!< Unknown resource */,
-    ErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
-    ErrorCode_FullStorage = 19    /*!< The file storage is full */,
-    ErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
-    ErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
-    ErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
-    ErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
-    ErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
-    ErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
-    ErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
-    ErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
-    ErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
-    ErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
-    ErrorCode_BadFont = 30    /*!< Badly formatted font file */,
-    ErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
-    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_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 */,
-    ErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
-    ErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
-    ErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
-    ErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
-    ErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
-    ErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
-    ErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
-    ErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
-    ErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
-    ErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
-    ErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
-    ErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
-    ErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
-    ErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
-    ErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
-    ErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
-    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is already in use */,
-    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
-    ErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
-    ErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
-    ErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
-    ErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
-    ErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
-    ErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
-    ErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
-    ErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
-    ErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
-    ErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
-    ErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
-    ErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
-    ErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
-    ErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
-    ErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
-    ErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
-    ErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
-    ErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
-    ErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
-    ErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
-    ErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
-    ErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
-    ErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
-    ErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
-    ErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
-    ErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
-    ErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
-    ErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
-    ErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
-    ErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
-    ErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
-    ErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
-    ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
-    ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
-    ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
-    ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
-    ErrorCode_START_PLUGINS = 1000000
-  };
-
-  enum LogLevel
-  {
-    LogLevel_Error,
-    LogLevel_Warning,
-    LogLevel_Info,
-    LogLevel_Trace
-  };
-
-
-  /**
-   * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
-   **/
-  enum PixelFormat
-  {
-    /**
-     * {summary}{Color image in RGB24 format.}
-     * {description}{This format describes a color image. The pixels are stored in 3
-     * consecutive bytes. The memory layout is RGB.}
-     **/
-    PixelFormat_RGB24 = 1,
-
-    /**
-     * {summary}{Color image in RGBA32 format.}
-     * {description}{This format describes a color image. The pixels are stored in 4
-     * consecutive bytes. The memory layout is RGBA.}
-     **/
-    PixelFormat_RGBA32 = 2,
-
-    /**
-     * {summary}{Graylevel 8bpp image.}
-     * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
-     **/
-    PixelFormat_Grayscale8 = 3,
-      
-    /**
-     * {summary}{Graylevel, unsigned 16bpp image.}
-     * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.}
-     **/
-    PixelFormat_Grayscale16 = 4,
-      
-    /**
-     * {summary}{Graylevel, signed 16bpp image.}
-     * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.}
-     **/
-    PixelFormat_SignedGrayscale16 = 5
-  };
-
-
-  /**
-   * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.}
-   **/
-  enum ImageExtractionMode
-  {
-    /**
-     * {summary}{Rescaled to 8bpp.}
-     * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.}
-     **/
-    ImageExtractionMode_Preview = 1,
-
-    /**
-     * {summary}{Truncation to the [0, 255] range.}
-     **/
-    ImageExtractionMode_UInt8 = 2,
-
-    /**
-     * {summary}{Truncation to the [0, 65535] range.}
-     **/
-    ImageExtractionMode_UInt16 = 3,
-
-    /**
-     * {summary}{Truncation to the [-32768, 32767] range.}
-     **/
-    ImageExtractionMode_Int16 = 4
-  };
-
-
-  /**
-   * Most common, non-joke and non-experimental HTTP status codes
-   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-   **/
-  enum HttpStatus
-  {
-    HttpStatus_None = -1,
-
-    // 1xx Informational
-    HttpStatus_100_Continue = 100,
-    HttpStatus_101_SwitchingProtocols = 101,
-    HttpStatus_102_Processing = 102,
-
-    // 2xx Success
-    HttpStatus_200_Ok = 200,
-    HttpStatus_201_Created = 201,
-    HttpStatus_202_Accepted = 202,
-    HttpStatus_203_NonAuthoritativeInformation = 203,
-    HttpStatus_204_NoContent = 204,
-    HttpStatus_205_ResetContent = 205,
-    HttpStatus_206_PartialContent = 206,
-    HttpStatus_207_MultiStatus = 207,
-    HttpStatus_208_AlreadyReported = 208,
-    HttpStatus_226_IMUsed = 226,
-
-    // 3xx Redirection
-    HttpStatus_300_MultipleChoices = 300,
-    HttpStatus_301_MovedPermanently = 301,
-    HttpStatus_302_Found = 302,
-    HttpStatus_303_SeeOther = 303,
-    HttpStatus_304_NotModified = 304,
-    HttpStatus_305_UseProxy = 305,
-    HttpStatus_307_TemporaryRedirect = 307,
-
-    // 4xx Client Error
-    HttpStatus_400_BadRequest = 400,
-    HttpStatus_401_Unauthorized = 401,
-    HttpStatus_402_PaymentRequired = 402,
-    HttpStatus_403_Forbidden = 403,
-    HttpStatus_404_NotFound = 404,
-    HttpStatus_405_MethodNotAllowed = 405,
-    HttpStatus_406_NotAcceptable = 406,
-    HttpStatus_407_ProxyAuthenticationRequired = 407,
-    HttpStatus_408_RequestTimeout = 408,
-    HttpStatus_409_Conflict = 409,
-    HttpStatus_410_Gone = 410,
-    HttpStatus_411_LengthRequired = 411,
-    HttpStatus_412_PreconditionFailed = 412,
-    HttpStatus_413_RequestEntityTooLarge = 413,
-    HttpStatus_414_RequestUriTooLong = 414,
-    HttpStatus_415_UnsupportedMediaType = 415,
-    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
-    HttpStatus_417_ExpectationFailed = 417,
-    HttpStatus_422_UnprocessableEntity = 422,
-    HttpStatus_423_Locked = 423,
-    HttpStatus_424_FailedDependency = 424,
-    HttpStatus_426_UpgradeRequired = 426,
-
-    // 5xx Server Error
-    HttpStatus_500_InternalServerError = 500,
-    HttpStatus_501_NotImplemented = 501,
-    HttpStatus_502_BadGateway = 502,
-    HttpStatus_503_ServiceUnavailable = 503,
-    HttpStatus_504_GatewayTimeout = 504,
-    HttpStatus_505_HttpVersionNotSupported = 505,
-    HttpStatus_506_VariantAlsoNegotiates = 506,
-    HttpStatus_507_InsufficientStorage = 507,
-    HttpStatus_509_BandwidthLimitExceeded = 509,
-    HttpStatus_510_NotExtended = 510
-  };
-
-
-  enum HttpMethod
-  {
-    HttpMethod_Get = 0,
-    HttpMethod_Post = 1,
-    HttpMethod_Delete = 2,
-    HttpMethod_Put = 3
-  };
-
-
-  enum ImageFormat
-  {
-    ImageFormat_Png = 1
-  };
-
-
-  // https://en.wikipedia.org/wiki/HTTP_compression
-  enum HttpCompression
-  {
-    HttpCompression_None,
-    HttpCompression_Deflate,
-    HttpCompression_Gzip
-  };
-
-
-  // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
-  enum Encoding
-  {
-    Encoding_Ascii,
-    Encoding_Utf8,
-    Encoding_Latin1,
-    Encoding_Latin2,
-    Encoding_Latin3,
-    Encoding_Latin4,
-    Encoding_Latin5,                        // Turkish
-    Encoding_Cyrillic,
-    Encoding_Windows1251,                   // Windows-1251 (commonly used for Cyrillic)
-    Encoding_Arabic,
-    Encoding_Greek,
-    Encoding_Hebrew,
-    Encoding_Thai,                          // TIS 620-2533
-    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
-    Encoding_Chinese                        // GB18030 - Chinese simplified
-    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
-    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
-    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
-  };
-
-
-  // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.2/
-  enum PhotometricInterpretation
-  {
-    PhotometricInterpretation_ARGB,  // Retired
-    PhotometricInterpretation_CMYK,  // Retired
-    PhotometricInterpretation_HSV,   // Retired
-    PhotometricInterpretation_Monochrome1,
-    PhotometricInterpretation_Monochrome2,
-    PhotometricInterpretation_Palette,
-    PhotometricInterpretation_RGB,
-    PhotometricInterpretation_YBRFull,
-    PhotometricInterpretation_YBRFull422,
-    PhotometricInterpretation_YBRPartial420,
-    PhotometricInterpretation_YBRPartial422,
-    PhotometricInterpretation_YBR_ICT,
-    PhotometricInterpretation_YBR_RCT,
-    PhotometricInterpretation_Unknown
-  };
-
-  enum DicomModule
-  {
-    DicomModule_Patient,
-    DicomModule_Study,
-    DicomModule_Series,
-    DicomModule_Instance,
-    DicomModule_Image
-  };
-
-  enum RequestOrigin
-  {
-    RequestOrigin_Unknown,
-    RequestOrigin_DicomProtocol,
-    RequestOrigin_RestApi,
-    RequestOrigin_Plugins,
-    RequestOrigin_Lua
-  };
-
-
-  /**
-   * WARNING: Do not change the explicit values in the enumerations
-   * below this point. This would result in incompatible databases
-   * between versions of Orthanc!
-   **/
-
-  enum CompressionType
-  {
-    /**
-     * Buffer/file that is stored as-is, in a raw fashion, without
-     * compression.
-     **/
-    CompressionType_None = 1,
-
-    /**
-     * Buffer that is compressed using the "deflate" algorithm (RFC
-     * 1951), wrapped inside the zlib data format (RFC 1950), prefixed
-     * with a "uint64_t" (8 bytes) that encodes the size of the
-     * uncompressed buffer. If the compressed buffer is empty, its
-     * represents an empty uncompressed buffer. This format is
-     * internal to Orthanc. If the 8 first bytes are skipped AND the
-     * buffer is non-empty, the buffer is compatible with the
-     * "deflate" HTTP compression.
-     **/
-    CompressionType_ZlibWithSize = 2
-  };
-
-  enum FileContentType
-  {
-    // If you add a value below, insert it in "PluginStorageArea" in
-    // the file "Plugins/Engine/OrthancPlugins.cpp"
-    FileContentType_Unknown = 0,
-    FileContentType_Dicom = 1,
-    FileContentType_DicomAsJson = 2,
-
-    // Make sure that the value "65535" can be stored into this enumeration
-    FileContentType_StartUser = 1024,
-    FileContentType_EndUser = 65535
-  };
-
-  enum ResourceType
-  {
-    ResourceType_Patient = 1,
-    ResourceType_Study = 2,
-    ResourceType_Series = 3,
-    ResourceType_Instance = 4
-  };
-
-
-  const char* EnumerationToString(ErrorCode code);
-
-  const char* EnumerationToString(HttpMethod method);
-
-  const char* EnumerationToString(HttpStatus status);
-
-  const char* EnumerationToString(ResourceType type);
-
-  const char* EnumerationToString(ImageFormat format);
-
-  const char* EnumerationToString(Encoding encoding);
-
-  const char* EnumerationToString(PhotometricInterpretation photometric);
-
-  const char* EnumerationToString(LogLevel level);
-
-  const char* EnumerationToString(RequestOrigin origin);
-
-  Encoding StringToEncoding(const char* encoding);
-
-  ResourceType StringToResourceType(const char* type);
-
-  ImageFormat StringToImageFormat(const char* format);
-
-  LogLevel StringToLogLevel(const char* format);
-
-  unsigned int GetBytesPerPixel(PixelFormat format);
-
-  bool GetDicomEncoding(Encoding& encoding,
-                        const char* specificCharacterSet);
-
-  ResourceType GetChildResourceType(ResourceType type);
-
-  ResourceType GetParentResourceType(ResourceType type);
-
-  DicomModule GetModule(ResourceType type);
-
-  const char* GetDicomSpecificCharacterSet(Encoding encoding);
-
-  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
-
-  bool IsUserContentType(FileContentType type);
-}
--- a/Orthanc/Core/Logging.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 <iostream>
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    void Initialize();
-
-    void Finalize();
-
-    void EnableInfoLevel(bool enabled);
-
-    void EnableTraceLevel(bool enabled);
-
-    void SetTargetFolder(const std::string& path);
-
-    struct NullStream : public std::ostream 
-    {
-      NullStream() : 
-        std::ios(0), 
-        std::ostream(0)
-      {
-      }
-      
-      std::ostream& operator<< (const std::string& message)
-      {
-        return *this;
-      }
-    };
-  }
-}
-
-
-#if ORTHANC_ENABLE_LOGGING != 1
-
-#  define LOG(level)   ::Orthanc::Logging::NullStream()
-#  define VLOG(level)  ::Orthanc::Logging::NullStream()
-
-#else  /* ORTHANC_ENABLE_LOGGING == 1 */
-
-#if ORTHANC_ENABLE_GOOGLE_LOG == 1
-#  include <stdlib.h>  // Including this fixes a problem in glog for recent releases of MinGW
-#  include <glog/logging.h>
-#else
-#  include <boost/thread/mutex.hpp>
-#  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
-#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
-#endif
-
-#if ORTHANC_ENABLE_GOOGLE_LOG != 1
-namespace Orthanc
-{
-  namespace Logging
-  {
-    class InternalLogger
-    {
-    private:
-      boost::mutex::scoped_lock lock_;
-      NullStream                null_;
-      std::ostream*             stream_;
-
-    public:
-      InternalLogger(const char* level,
-                     const char* file,
-                     int line);
-
-      ~InternalLogger();
-      
-      std::ostream& operator<< (const std::string& message)
-      {
-        return (*stream_) << message;
-      }
-    };
-  }
-}
-#endif
-
-#endif  // ORTHANC_ENABLE_LOGGING
--- a/Orthanc/Core/OrthancException.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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>
-#include <string>
-#include "Enumerations.h"
-
-namespace Orthanc
-{
-  class OrthancException
-  {
-  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_);
-    }
-  };
-}
--- a/Orthanc/Core/PrecompiledHeaders.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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
-
-#if defined(_WIN32) && !defined(NOMINMAX)
-#define NOMINMAX
-#endif
-
-#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
-
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/locale.hpp>
-#include <boost/regex.hpp>
-#include <boost/thread.hpp>
-#include <boost/thread/shared_mutex.hpp>
-
-#include <json/value.h>
-
-#if ORTHANC_PUGIXML_ENABLED == 1
-#include <pugixml.hpp>
-#endif
-
-#include "Enumerations.h"
-#include "Logging.h"
-#include "OrthancException.h"
-#include "Toolbox.h"
-#include "Uuid.h"
-
-#endif
--- a/Orthanc/Core/Toolbox.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1386 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Toolbox.h"
-
-#include "OrthancException.h"
-#include "Logging.h"
-
-#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>
-#endif
-
-#if BOOST_HAS_LOCALE != 1
-#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"
-#endif
-
-
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-#include "../Resources/ThirdParty/base64/base64.h"
-#endif
-
-
-#if defined(_MSC_VER) && (_MSC_VER < 1800)
-// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013
-extern "C"
-{
-  int64_t _strtoi64(const char *nptr, char **endptr, int base);
-  int64_t strtoll(const char *nptr, char **endptr, int base)
-  {
-    return _strtoi64(nptr, endptr, base);
-  } 
-}
-#endif
-
-
-#if ORTHANC_PUGIXML_ENABLED == 1
-#include "ChunkedBuffer.h"
-#include <pugixml.hpp>
-#endif
-
-
-namespace Orthanc
-{
-  static bool finish;
-
-#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)
-  {
-    finish = true;
-  }
-#endif
-
-  void Toolbox::USleep(uint64_t microSeconds)
-  {
-#if defined(_WIN32)
-    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
-#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
-    usleep(microSeconds);
-#else
-#error Support your platform here
-#endif
-  }
-
-
-  static void ServerBarrierInternal(const bool* stopFlag)
-  {
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, true);
-#else
-    signal(SIGINT, SignalHandler);
-    signal(SIGQUIT, SignalHandler);
-    signal(SIGTERM, SignalHandler);
-#endif
-  
-    // Active loop that awakens every 100ms
-    finish = false;
-    while (!(*stopFlag || finish))
-    {
-      Toolbox::USleep(100 * 1000);
-    }
-
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, false);
-#else
-    signal(SIGINT, NULL);
-    signal(SIGQUIT, NULL);
-    signal(SIGTERM, NULL);
-#endif
-  }
-
-
-  void Toolbox::ServerBarrier(const bool& stopFlag)
-  {
-    ServerBarrierInternal(&stopFlag);
-  }
-
-  void Toolbox::ServerBarrier()
-  {
-    const bool stopFlag = false;
-    ServerBarrierInternal(&stopFlag);
-  }
-
-
-  void Toolbox::ToUpperCase(std::string& s)
-  {
-    std::transform(s.begin(), s.end(), s.begin(), toupper);
-  }
-
-
-  void Toolbox::ToLowerCase(std::string& s)
-  {
-    std::transform(s.begin(), s.end(), s.begin(), tolower);
-  }
-
-
-  void Toolbox::ToUpperCase(std::string& result,
-                            const std::string& source)
-  {
-    result = source;
-    ToUpperCase(result);
-  }
-
-  void Toolbox::ToLowerCase(std::string& result,
-                            const std::string& source)
-  {
-    result = source;
-    ToLowerCase(result);
-  }
-
-
-  void Toolbox::ReadFile(std::string& content,
-                         const std::string& path) 
-  {
-    if (!boost::filesystem::is_regular_file(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);
-    }
-
-    // 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);
-
-    content.resize(size);
-    if (size != 0)
-    {
-      f.read(reinterpret_cast<char*>(&content[0]), size);
-    }
-
-    f.close();
-  }
-
-
-  void Toolbox::WriteFile(const void* content,
-                          size_t size,
-                          const std::string& path)
-  {
-    boost::filesystem::ofstream f;
-    f.open(path, std::ofstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_CannotWriteFile);
-    }
-
-    if (size != 0)
-    {
-      f.write(reinterpret_cast<const char*>(content), size);
-    }
-
-    f.close();
-  }
-
-
-  void Toolbox::WriteFile(const std::string& content,
-                          const std::string& path)
-  {
-    WriteFile(content.size() > 0 ? content.c_str() : NULL,
-              content.size(), path);
-  }
-
-
-  void Toolbox::RemoveFile(const std::string& path)
-  {
-    if (boost::filesystem::exists(path))
-    {
-      if (boost::filesystem::is_regular_file(path))
-      {
-        boost::filesystem::remove(path);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_RegularFileExpected);
-      }
-    }
-  }
-
-
-
-  void Toolbox::SplitUriComponents(UriComponents& components,
-                                   const std::string& uri)
-  {
-    static const char URI_SEPARATOR = '/';
-
-    components.clear();
-
-    if (uri.size() == 0 ||
-        uri[0] != URI_SEPARATOR)
-    {
-      throw OrthancException(ErrorCode_UriSyntax);
-    }
-
-    // Count the number of slashes in the URI to make an assumption
-    // about the number of components in the URI
-    unsigned int estimatedSize = 0;
-    for (unsigned int i = 0; i < uri.size(); i++)
-    {
-      if (uri[i] == URI_SEPARATOR)
-        estimatedSize++;
-    }
-
-    components.reserve(estimatedSize - 1);
-
-    unsigned int start = 1;
-    unsigned int end = 1;
-    while (end < uri.size())
-    {
-      // This is the loop invariant
-      assert(uri[start - 1] == '/' && (end >= start));
-
-      if (uri[end] == '/')
-      {
-        components.push_back(std::string(&uri[start], end - start));
-        end++;
-        start = end;
-      }
-      else
-      {
-        end++;
-      }
-    }
-
-    if (start < uri.size())
-    {
-      components.push_back(std::string(&uri[start], end - start));
-    }
-
-    for (size_t i = 0; i < components.size(); i++)
-    {
-      if (components[i].size() == 0)
-      {
-        // Empty component, as in: "/coucou//e"
-        throw OrthancException(ErrorCode_UriSyntax);
-      }
-    }
-  }
-
-
-  void Toolbox::TruncateUri(UriComponents& target,
-                            const UriComponents& source,
-                            size_t fromLevel)
-  {
-    target.clear();
-
-    if (source.size() > fromLevel)
-    {
-      target.resize(source.size() - fromLevel);
-
-      size_t j = 0;
-      for (size_t i = fromLevel; i < source.size(); i++, j++)
-      {
-        target[j] = source[i];
-      }
-
-      assert(j == target.size());
-    }
-  }
-  
-
-
-  bool Toolbox::IsChildUri(const UriComponents& baseUri,
-                           const UriComponents& testedUri)
-  {
-    if (testedUri.size() < baseUri.size())
-    {
-      return false;
-    }
-
-    for (size_t i = 0; i < baseUri.size(); i++)
-    {
-      if (baseUri[i] != testedUri[i])
-        return false;
-    }
-
-    return true;
-  }
-
-
-  std::string Toolbox::AutodetectMimeType(const std::string& path)
-  {
-    std::string contentType;
-    size_t lastDot = path.rfind('.');
-    size_t lastSlash = path.rfind('/');
-
-    if (lastDot == std::string::npos ||
-        (lastSlash != std::string::npos && lastDot < lastSlash))
-    {
-      // No trailing dot, unable to detect the content type
-    }
-    else
-    {
-      const char* extension = &path[lastDot + 1];
-    
-      // http://en.wikipedia.org/wiki/Mime_types
-      // Text types
-      if (!strcmp(extension, "txt"))
-        contentType = "text/plain";
-      else if (!strcmp(extension, "html"))
-        contentType = "text/html";
-      else if (!strcmp(extension, "xml"))
-        contentType = "text/xml";
-      else if (!strcmp(extension, "css"))
-        contentType = "text/css";
-
-      // Application types
-      else if (!strcmp(extension, "js"))
-        contentType = "application/javascript";
-      else if (!strcmp(extension, "json"))
-        contentType = "application/json";
-      else if (!strcmp(extension, "pdf"))
-        contentType = "application/pdf";
-
-      // Images types
-      else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg"))
-        contentType = "image/jpeg";
-      else if (!strcmp(extension, "gif"))
-        contentType = "image/gif";
-      else if (!strcmp(extension, "png"))
-        contentType = "image/png";
-    }
-
-    return contentType;
-  }
-
-
-  std::string Toolbox::FlattenUri(const UriComponents& components,
-                                  size_t fromLevel)
-  {
-    if (components.size() <= fromLevel)
-    {
-      return "/";
-    }
-    else
-    {
-      std::string r;
-
-      for (size_t i = fromLevel; i < components.size(); i++)
-      {
-        r += "/" + components[i];
-      }
-
-      return r;
-    }
-  }
-
-
-
-  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);
-    }
-  }
-
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
-  static char GetHexadecimalCharacter(uint8_t value)
-  {
-    assert(value < 16);
-
-    if (value < 10)
-    {
-      return value + '0';
-    }
-    else
-    {
-      return (value - 10) + 'a';
-    }
-  }
-
-
-  void Toolbox::ComputeMD5(std::string& result,
-                           const std::string& data)
-  {
-    if (data.size() > 0)
-    {
-      ComputeMD5(result, &data[0], data.size());
-    }
-    else
-    {
-      ComputeMD5(result, NULL, 0);
-    }
-  }
-
-
-  void Toolbox::ComputeMD5(std::string& result,
-                           const void* data,
-                           size_t size)
-  {
-    md5_state_s state;
-    md5_init(&state);
-
-    if (size > 0)
-    {
-      md5_append(&state, 
-                 reinterpret_cast<const md5_byte_t*>(data), 
-                 static_cast<int>(size));
-    }
-
-    md5_byte_t actualHash[16];
-    md5_finish(&state, actualHash);
-
-    result.resize(32);
-    for (unsigned int i = 0; i < 16; i++)
-    {
-      result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
-      result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
-    }
-  }
-#endif
-
-
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-  void Toolbox::EncodeBase64(std::string& result, 
-                             const std::string& data)
-  {
-    result = base64_encode(data);
-  }
-
-  void Toolbox::DecodeBase64(std::string& result, 
-                             const std::string& data)
-  {
-    result = base64_decode(data);
-  }
-
-
-#  if BOOST_HAS_REGEX == 1
-  void Toolbox::DecodeDataUriScheme(std::string& mime,
-                                    std::string& content,
-                                    const std::string& source)
-  {
-    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
-                         boost::regex::icase /* case insensitive search */);
-
-    boost::cmatch what;
-    if (regex_match(source.c_str(), what, pattern))
-    {
-      mime = what[1];
-      DecodeBase64(content, what[2]);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-#  endif
-
-
-  void Toolbox::EncodeDataUriScheme(std::string& result,
-                                    const std::string& mime,
-                                    const std::string& content)
-  {
-    result = "data:" + mime + ";base64," + base64_encode(content);
-  }
-
-#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);
-  }
-
-#else
-#error Support your platform here
-#endif
-
-
-  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();
-  }
-
-
-  static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
-  {
-    switch (sourceEncoding)
-    {
-      case Encoding_Utf8:
-        return "UTF-8";
-
-      case Encoding_Ascii:
-        return "ASCII";
-
-      case Encoding_Latin1:
-        return "ISO-8859-1";
-        break;
-
-      case Encoding_Latin2:
-        return "ISO-8859-2";
-        break;
-
-      case Encoding_Latin3:
-        return "ISO-8859-3";
-        break;
-
-      case Encoding_Latin4:
-        return "ISO-8859-4";
-        break;
-
-      case Encoding_Latin5:
-        return "ISO-8859-9";
-        break;
-
-      case Encoding_Cyrillic:
-        return "ISO-8859-5";
-        break;
-
-      case Encoding_Windows1251:
-        return "WINDOWS-1251";
-        break;
-
-      case Encoding_Arabic:
-        return "ISO-8859-6";
-        break;
-
-      case Encoding_Greek:
-        return "ISO-8859-7";
-        break;
-
-      case Encoding_Hebrew:
-        return "ISO-8859-8";
-        break;
-        
-      case Encoding_Japanese:
-        return "SHIFT-JIS";
-        break;
-
-      case Encoding_Chinese:
-        return "GB18030";
-        break;
-
-      case Encoding_Thai:
-        return "TIS620.2533-0";
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  std::string Toolbox::ConvertToUtf8(const std::string& source,
-                                     Encoding sourceEncoding)
-  {
-    if (sourceEncoding == Encoding_Utf8)
-    {
-      // Already in UTF-8: No conversion is required
-      return source;
-    }
-
-    if (sourceEncoding == Encoding_Ascii)
-    {
-      return ConvertToAscii(source);
-    }
-
-    const char* encoding = GetBoostLocaleEncoding(sourceEncoding);
-
-    try
-    {
-      return boost::locale::conv::to_utf<char>(source, encoding);
-    }
-    catch (std::runtime_error&)
-    {
-      // Bad input string or bad encoding
-      return ConvertToAscii(source);
-    }
-  }
-
-
-  std::string Toolbox::ConvertFromUtf8(const std::string& source,
-                                       Encoding targetEncoding)
-  {
-    if (targetEncoding == Encoding_Utf8)
-    {
-      // Already in UTF-8: No conversion is required
-      return source;
-    }
-
-    if (targetEncoding == Encoding_Ascii)
-    {
-      return ConvertToAscii(source);
-    }
-
-    const char* encoding = GetBoostLocaleEncoding(targetEncoding);
-
-    try
-    {
-      return boost::locale::conv::from_utf<char>(source, encoding);
-    }
-    catch (std::runtime_error&)
-    {
-      // Bad input string or bad encoding
-      return ConvertToAscii(source);
-    }
-  }
-
-
-  std::string Toolbox::ConvertToAscii(const std::string& source)
-  {
-    std::string result;
-
-    result.reserve(source.size() + 1);
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
-      {
-        result.push_back(source[i]);
-      }
-    }
-
-    return result;
-  }
-
-
-  void Toolbox::ComputeSHA1(std::string& result,
-                            const void* data,
-                            size_t size)
-  {
-    boost::uuids::detail::sha1 sha1;
-
-    if (size > 0)
-    {
-      sha1.process_bytes(data, size);
-    }
-
-    unsigned int digest[5];
-
-    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
-    assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 
-    
-    sha1.get_digest(digest);
-
-    result.resize(8 * 5 + 4);
-    sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
-            digest[0],
-            digest[1],
-            digest[2],
-            digest[3],
-            digest[4]);
-  }
-
-  void Toolbox::ComputeSHA1(std::string& result,
-                            const std::string& data)
-  {
-    if (data.size() > 0)
-    {
-      ComputeSHA1(result, data.c_str(), data.size());
-    }
-    else
-    {
-      ComputeSHA1(result, NULL, 0);
-    }
-  }
-
-
-  bool Toolbox::IsSHA1(const char* str,
-                       size_t size)
-  {
-    if (size == 0)
-    {
-      return false;
-    }
-
-    const char* start = str;
-    const char* end = str + size;
-
-    // Trim the beginning of the string
-    while (start < end)
-    {
-      if (*start == '\0' ||
-          isspace(*start))
-      {
-        start++;
-      }
-      else
-      {
-        break;
-      }
-    }
-
-    // Trim the trailing of the string
-    while (start < end)
-    {
-      if (*(end - 1) == '\0' ||
-          isspace(*(end - 1)))
-      {
-        end--;
-      }
-      else
-      {
-        break;
-      }
-    }
-
-    if (end - start != 44)
-    {
-      return false;
-    }
-
-    for (unsigned int i = 0; i < 44; i++)
-    {
-      if (i == 8 ||
-          i == 17 ||
-          i == 26 ||
-          i == 35)
-      {
-        if (start[i] != '-')
-          return false;
-      }
-      else
-      {
-        if (!isalnum(start[i]))
-          return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  bool Toolbox::IsSHA1(const std::string& s)
-  {
-    if (s.size() == 0)
-    {
-      return false;
-    }
-    else
-    {
-      return IsSHA1(s.c_str(), s.size());
-    }
-  }
-
-
-#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;
-
-    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);
-  }
-
-
-  static char Hex2Dec(char c)
-  {
-    return ((c >= '0' && c <= '9') ? c - '0' :
-            ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10));
-  }
-
-  void Toolbox::UrlDecode(std::string& s)
-  {
-    // http://en.wikipedia.org/wiki/Percent-encoding
-    // http://www.w3schools.com/tags/ref_urlencode.asp
-    // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c
-
-    if (s.size() == 0)
-    {
-      return;
-    }
-
-    size_t source = 0;
-    size_t target = 0;
-
-    while (source < s.size())
-    {
-      if (s[source] == '%' &&
-          source + 2 < s.size() &&
-          isalnum(s[source + 1]) &&
-          isalnum(s[source + 2]))
-      {
-        s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]);
-        source += 3;
-        target += 1;
-      }
-      else
-      {
-        if (s[source] == '+')
-          s[target] = ' ';
-        else
-          s[target] = s[source];
-
-        source++;
-        target++;
-      }
-    }
-
-    s.resize(target);
-  }
-
-
-  Endianness Toolbox::DetectEndianness()
-  {
-    // http://sourceforge.net/p/predef/wiki/Endianness/
-
-    uint8_t buffer[4];
-
-    buffer[0] = 0x00;
-    buffer[1] = 0x01;
-    buffer[2] = 0x02;
-    buffer[3] = 0x03;
-
-    switch (*((uint32_t *)buffer)) 
-    {
-      case 0x00010203: 
-        return Endianness_Big;
-
-      case 0x03020100: 
-        return Endianness_Little;
-        
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-#if BOOST_HAS_REGEX == 1
-  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
-  {
-    // TODO - Speed up this with a regular expression
-
-    std::string result = source;
-
-    // Escape all special characters
-    boost::replace_all(result, "\\", "\\\\");
-    boost::replace_all(result, "^", "\\^");
-    boost::replace_all(result, ".", "\\.");
-    boost::replace_all(result, "$", "\\$");
-    boost::replace_all(result, "|", "\\|");
-    boost::replace_all(result, "(", "\\(");
-    boost::replace_all(result, ")", "\\)");
-    boost::replace_all(result, "[", "\\[");
-    boost::replace_all(result, "]", "\\]");
-    boost::replace_all(result, "+", "\\+");
-    boost::replace_all(result, "/", "\\/");
-    boost::replace_all(result, "{", "\\{");
-    boost::replace_all(result, "}", "\\}");
-
-    // Convert wildcards '*' and '?' to their regex equivalents
-    boost::replace_all(result, "?", ".");
-    boost::replace_all(result, "*", ".*");
-
-    return result;
-  }
-#endif
-
-
-
-  void Toolbox::TokenizeString(std::vector<std::string>& result,
-                               const std::string& value,
-                               char separator)
-  {
-    result.clear();
-
-    std::string currentItem;
-
-    for (size_t i = 0; i < value.size(); i++)
-    {
-      if (value[i] == separator)
-      {
-        result.push_back(currentItem);
-        currentItem.clear();
-      }
-      else
-      {
-        currentItem.push_back(value[i]);
-      }
-    }
-
-    result.push_back(currentItem);
-  }
-
-
-  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);
-      }
-    }
-  }
-
-
-  bool Toolbox::IsExistingFile(const std::string& path)
-  {
-    return boost::filesystem::exists(path);
-  }
-
-
-#if ORTHANC_PUGIXML_ENABLED == 1
-  class ChunkedBufferWriter : public pugi::xml_writer
-  {
-  private:
-    ChunkedBuffer buffer_;
-
-  public:
-    virtual void write(const void *data, size_t size)
-    {
-      if (size > 0)
-      {
-        buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
-      }
-    }
-
-    void Flatten(std::string& s)
-    {
-      buffer_.Flatten(s);
-    }
-  };
-
-
-  static void JsonToXmlInternal(pugi::xml_node& target,
-                                const Json::Value& source,
-                                const std::string& arrayElement)
-  {
-    // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030
-
-    switch (source.type())
-    {
-      case Json::nullValue:
-      {
-        target.append_child(pugi::node_pcdata).set_value("null");
-        break;
-      }
-
-      case Json::intValue:
-      {
-        std::string s = boost::lexical_cast<std::string>(source.asInt());
-        target.append_child(pugi::node_pcdata).set_value(s.c_str());
-        break;
-      }
-
-      case Json::uintValue:
-      {
-        std::string s = boost::lexical_cast<std::string>(source.asUInt());
-        target.append_child(pugi::node_pcdata).set_value(s.c_str());
-        break;
-      }
-
-      case Json::realValue:
-      {
-        std::string s = boost::lexical_cast<std::string>(source.asFloat());
-        target.append_child(pugi::node_pcdata).set_value(s.c_str());
-        break;
-      }
-
-      case Json::stringValue:
-      {
-        target.append_child(pugi::node_pcdata).set_value(source.asString().c_str());
-        break;
-      }
-
-      case Json::booleanValue:
-      {
-        target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false");
-        break;
-      }
-
-      case Json::arrayValue:
-      {
-        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
-        {
-          pugi::xml_node node = target.append_child();
-          node.set_name(arrayElement.c_str());
-          JsonToXmlInternal(node, source[i], arrayElement);
-        }
-        break;
-      }
-        
-      case Json::objectValue:
-      {
-        Json::Value::Members members = source.getMemberNames();
-
-        for (size_t i = 0; i < members.size(); i++)
-        {
-          pugi::xml_node node = target.append_child();
-          node.set_name(members[i].c_str());
-          JsonToXmlInternal(node, source[members[i]], arrayElement);          
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void Toolbox::JsonToXml(std::string& target,
-                          const Json::Value& source,
-                          const std::string& rootElement,
-                          const std::string& arrayElement)
-  {
-    pugi::xml_document doc;
-
-    pugi::xml_node n = doc.append_child(rootElement.c_str());
-    JsonToXmlInternal(n, source, arrayElement);
-
-    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
-    decl.append_attribute("version").set_value("1.0");
-    decl.append_attribute("encoding").set_value("utf-8");
-
-    ChunkedBufferWriter writer;
-    doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
-    writer.Flatten(target);
-  }
-
-#endif
-
-
-  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);
-    }
-  }
-
-  
-  bool Toolbox::IsInteger(const std::string& str)
-  {
-    std::string s = StripSpaces(str);
-
-    if (s.size() == 0)
-    {
-      return false;
-    }
-
-    size_t pos = 0;
-    if (s[0] == '-')
-    {
-      if (s.size() == 1)
-      {
-        return false;
-      }
-
-      pos = 1;
-    }
-
-    while (pos < s.size())
-    {
-      if (!isdigit(s[pos]))
-      {
-        return false;
-      }
-
-      pos++;
-    }
-
-    return true;
-  }
-
-
-  void Toolbox::CopyJsonWithoutComments(Json::Value& target,
-                                        const Json::Value& source)
-  {
-    switch (source.type())
-    {
-      case Json::nullValue:
-        target = Json::nullValue;
-        break;
-
-      case Json::intValue:
-        target = source.asInt64();
-        break;
-
-      case Json::uintValue:
-        target = source.asUInt64();
-        break;
-
-      case Json::realValue:
-        target = source.asDouble();
-        break;
-
-      case Json::stringValue:
-        target = source.asString();
-        break;
-
-      case Json::booleanValue:
-        target = source.asBool();
-        break;
-
-      case Json::arrayValue:
-      {
-        target = Json::arrayValue;
-        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
-        {
-          Json::Value& item = target.append(Json::nullValue);
-          CopyJsonWithoutComments(item, source[i]);
-        }
-
-        break;
-      }
-
-      case Json::objectValue:
-      {
-        target = Json::objectValue;
-        Json::Value::Members members = source.getMemberNames();
-        for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
-        {
-          const std::string item = members[i];
-          CopyJsonWithoutComments(target[item], source[item]);
-        }
-
-        break;
-      }
-
-      default:
-        break;
-    }
-  }
-
-
-  bool Toolbox::StartsWith(const std::string& str,
-                           const std::string& prefix)
-  {
-    if (str.size() < prefix.size())
-    {
-      return false;
-    }
-    else
-    {
-      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
-  }
-}
-
--- a/Orthanc/Core/Toolbox.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Enumerations.h"
-
-#include <stdint.h>
-#include <vector>
-#include <string>
-#include <json/json.h>
-
-namespace Orthanc
-{
-  typedef std::vector<std::string> UriComponents;
-
-  class NullType
-  {
-  };
-
-  namespace Toolbox
-  {
-    void ServerBarrier(const bool& stopFlag);
-
-    void ServerBarrier();
-
-    void ToUpperCase(std::string& s);  // Inplace version
-
-    void ToLowerCase(std::string& s);  // Inplace version
-
-    void ToUpperCase(std::string& result,
-                     const std::string& source);
-
-    void ToLowerCase(std::string& result,
-                     const std::string& source);
-
-    void ReadFile(std::string& content,
-                  const std::string& path);
-
-    void WriteFile(const std::string& content,
-                   const std::string& path);
-
-    void WriteFile(const void* content,
-                   size_t size,
-                   const std::string& path);
-
-    void USleep(uint64_t microSeconds);
-
-    void RemoveFile(const std::string& path);
-
-    void SplitUriComponents(UriComponents& components,
-                            const std::string& uri);
-  
-    void TruncateUri(UriComponents& target,
-                     const UriComponents& source,
-                     size_t fromLevel);
-  
-    bool IsChildUri(const UriComponents& baseUri,
-                    const UriComponents& testedUri);
-
-    std::string AutodetectMimeType(const std::string& path);
-
-    std::string FlattenUri(const UriComponents& components,
-                           size_t fromLevel = 0);
-
-    uint64_t GetFileSize(const std::string& path);
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
-    void ComputeMD5(std::string& result,
-                    const std::string& data);
-
-    void ComputeMD5(std::string& result,
-                    const void* data,
-                    size_t size);
-#endif
-
-    void ComputeSHA1(std::string& result,
-                     const std::string& data);
-
-    void ComputeSHA1(std::string& result,
-                     const void* data,
-                     size_t size);
-
-    bool IsSHA1(const char* str,
-                size_t size);
-
-    bool IsSHA1(const std::string& s);
-
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-    void DecodeBase64(std::string& result, 
-                      const std::string& data);
-
-    void EncodeBase64(std::string& result, 
-                      const std::string& data);
-
-#  if BOOST_HAS_REGEX == 1
-    void DecodeDataUriScheme(std::string& mime,
-                             std::string& content,
-                             const std::string& source);
-#  endif
-
-    void EncodeDataUriScheme(std::string& result,
-                             const std::string& mime,
-                             const std::string& content);
-#endif
-
-    std::string GetPathToExecutable();
-
-    std::string GetDirectoryOfExecutable();
-
-    std::string ConvertToUtf8(const std::string& source,
-                              Encoding sourceEncoding);
-
-    std::string ConvertFromUtf8(const std::string& source,
-                                Encoding targetEncoding);
-
-    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);
-
-    Endianness DetectEndianness();
-
-#if BOOST_HAS_REGEX == 1
-    std::string WildcardToRegularExpression(const std::string& s);
-#endif
-
-    void TokenizeString(std::vector<std::string>& result,
-                        const std::string& source,
-                        char separator);
-
-    void MakeDirectory(const std::string& path);
-
-    bool IsExistingFile(const std::string& path);
-
-#if ORTHANC_PUGIXML_ENABLED == 1
-    void JsonToXml(std::string& target,
-                   const Json::Value& source,
-                   const std::string& rootElement = "root",
-                   const std::string& arrayElement = "item");
-#endif
-
-    void ExecuteSystemCommand(const std::string& command,
-                              const std::vector<std::string>& arguments);
-
-    bool IsInteger(const std::string& str);
-
-    void CopyJsonWithoutComments(Json::Value& target,
-                                 const Json::Value& source);
-
-    bool StartsWith(const std::string& str,
-                    const std::string& prefix);
-
-    int GetProcessId();
-  }
-}
--- a/Orthanc/Plugins/Samples/Common/ExportedSymbols.list	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-# This is the list of the symbols that must be exported by Orthanc
-# plugins, if targeting OS X
-
-_OrthancPluginInitialize
-_OrthancPluginFinalize
-_OrthancPluginGetName
-_OrthancPluginGetVersion
--- a/Orthanc/Plugins/Samples/Common/VersionScript.map	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-# This is a version-script for Orthanc plugins
-
-{
-global:
-  OrthancPluginInitialize;
-  OrthancPluginFinalize;
-  OrthancPluginGetName;
-  OrthancPluginGetVersion;
-
-local:
-  *;
-};
--- a/Orthanc/Resources/CMake/BoostConfiguration.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
-  set(BOOST_STATIC 1)
-else()
-  include(FindBoost)
-
-  set(BOOST_STATIC 0)
-  #set(Boost_DEBUG 1)
-  #set(Boost_USE_STATIC_LIBS ON)
-
-  find_package(Boost
-    COMPONENTS filesystem thread system date_time regex locale)
-
-  if (NOT Boost_FOUND)
-    message(FATAL_ERROR "Unable to locate Boost on this system")
-  endif()
-
-  # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem
-  # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm
-  if (${Boost_VERSION} LESS 104400)
-    add_definitions(
-      -DBOOST_HAS_FILESYSTEM_V3=0
-      )
-  else()
-    add_definitions(
-      -DBOOST_HAS_FILESYSTEM_V3=1
-      -DBOOST_FILESYSTEM_VERSION=3
-      )
-  endif()
-
-  #if (${Boost_VERSION} LESS 104800)
-  # boost::locale is only available from 1.48.00
-  #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version")
-  #  set(BOOST_STATIC 1)
-  #endif()
-
-  include_directories(${Boost_INCLUDE_DIRS})
-  link_libraries(${Boost_LIBRARIES})
-endif()
-
-
-if (BOOST_STATIC)
-  # Parameters for Boost 1.59.0
-  set(BOOST_NAME boost_1_59_0)
-  set(BOOST_BCP_SUFFIX bcpdigest-0.9.5)
-  set(BOOST_MD5 "08abb7cdbea0b380f9ab0d5cce476f12")
-  set(BOOST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
-  set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") 
-  set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
-
-  DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}")
-
-  set(BOOST_SOURCES)
-
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp
-      )
-    add_definitions(
-      -DBOOST_LOCALE_WITH_ICONV=1
-      )
-
-    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
-      add_definitions(-DBOOST_HAS_SCHED_YIELD=1)
-    endif()
-
-  elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp
-      )
-
-    # Starting with release 0.8.2, Orthanc statically links against
-    # libiconv, even on Windows. Indeed, the "WCONV" library of
-    # Windows XP seems not to support properly several codepages
-    # (notably "Latin3", "Hebrew", and "Arabic").
-
-    if (USE_BOOST_ICONV)
-      include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake)
-    else()
-      add_definitions(-DBOOST_LOCALE_WITH_WCONV=1)
-    endif()
-
-  else()
-    message(FATAL_ERROR "Support your platform here")
-  endif()
-
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
-      )
-  endif()
-
-  aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES)
-
-  list(APPEND BOOST_SOURCES
-    ${BOOST_REGEX_SOURCES}
-    ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
-    ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
-    ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
-    )
-
-  add_definitions(
-    # Static build of Boost
-    -DBOOST_ALL_NO_LIB 
-    -DBOOST_ALL_NOLIB 
-    -DBOOST_DATE_TIME_NO_LIB 
-    -DBOOST_THREAD_BUILD_LIB
-    -DBOOST_PROGRAM_OPTIONS_NO_LIB
-    -DBOOST_REGEX_NO_LIB
-    -DBOOST_SYSTEM_NO_LIB
-    -DBOOST_LOCALE_NO_LIB
-    -DBOOST_HAS_LOCALE=1
-    -DBOOST_HAS_FILESYSTEM_V3=1
-    )
-
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    add_definitions(-isystem ${BOOST_SOURCES_DIR})
-  endif()
-
-  include_directories(
-    ${BOOST_SOURCES_DIR}
-    )
-
-  source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
-else()
-  add_definitions(
-    -DBOOST_HAS_LOCALE=1
-    )
-endif()
-
-
-add_definitions(
-  -DBOOST_HAS_DATE_TIME=1
-  -DBOOST_HAS_REGEX=1
-  )
--- a/Orthanc/Resources/CMake/Compiler.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-# This file sets all the compiler-related flags
-
-if (CMAKE_CROSSCOMPILING)
-  # Cross-compilation necessarily implies standalone and static build
-  SET(STATIC_BUILD ON)
-  SET(STANDALONE_BUILD ON)
-endif()
-
-if (CMAKE_COMPILER_IS_GNUCXX)
-  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration")  
-  # --std=c99 makes libcurl not to compile
-  # -pedantic gives a lot of warnings on OpenSSL 
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wno-long-long -Wno-variadic-macros")
-
-  if (CMAKE_CROSSCOMPILING)
-    # http://stackoverflow.com/a/3543845/881731
-    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>")
-  endif()
-
-elseif (MSVC)
-  # Use static runtime under Visual Studio
-  # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
-  # http://stackoverflow.com/a/6510446
-  foreach(flag_var
-    CMAKE_C_FLAGS_DEBUG
-    CMAKE_CXX_FLAGS_DEBUG
-    CMAKE_C_FLAGS_RELEASE 
-    CMAKE_CXX_FLAGS_RELEASE
-    CMAKE_C_FLAGS_MINSIZEREL 
-    CMAKE_CXX_FLAGS_MINSIZEREL 
-    CMAKE_C_FLAGS_RELWITHDEBINFO 
-    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
-    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
-  endforeach(flag_var)
-
-  # Add /Zm256 compiler option to Visual Studio to fix PCH errors
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256")
-
-  add_definitions(
-    -D_CRT_SECURE_NO_WARNINGS=1
-    -D_CRT_SECURE_NO_DEPRECATE=1
-    )
-  include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
-  link_libraries(netapi32)
-endif()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
-
-  # Remove the "-rdynamic" option
-  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
-  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
-  link_libraries(uuid pthread rt)
-
-  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
-    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed")
-    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed")
-    add_definitions(
-      -D_LARGEFILE64_SOURCE=1 
-      -D_FILE_OFFSET_BITS=64
-      )
-    link_libraries(dl)
-  endif()
-
-elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  if (MSVC)
-    message("MSVC compiler version = " ${MSVC_VERSION} "\n")
-    # Starting Visual Studio 2013 (version 1800), it is not possible
-    # to target Windows XP anymore
-    if (MSVC_VERSION LESS 1800)
-      add_definitions(
-        -DWINVER=0x0501
-        -D_WIN32_WINNT=0x0501
-        )
-    endif()
-  else()
-    add_definitions(
-      -DWINVER=0x0501
-      -D_WIN32_WINNT=0x0501
-      )
-  endif()
-
-  add_definitions(
-    -D_CRT_SECURE_NO_WARNINGS=1
-    )
-  link_libraries(rpcrt4 ws2_32)
-
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    # This is a patch for MinGW64
-    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
-    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
-
-    CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
-    if (HAVE_WIN_PTHREAD)
-      # This line is necessary to compile with recent versions of MinGW,
-      # otherwise "libwinpthread-1.dll" is not statically linked.
-      SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
-      add_definitions(-DHAVE_WIN_PTHREAD=1)
-    else()
-      add_definitions(-DHAVE_WIN_PTHREAD=0)
-    endif()
-  endif()
-
-elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list")
-
-  add_definitions(
-    -D_XOPEN_SOURCE=1
-    )
-  link_libraries(iconv)
-
-endif()
-
-
-if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}")
-endif()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-  # In FreeBSD, the "/usr/local/" folder contains the ports and need to be imported
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
-  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib")
-endif()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  CHECK_INCLUDE_FILES(rpc.h HAVE_UUID_H)
-else()
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-endif()
-
-if (NOT HAVE_UUID_H)
-  message(FATAL_ERROR "Please install the uuid-dev package")
-endif()
-
-
-if (STATIC_BUILD)
-  add_definitions(-DORTHANC_STATIC=1)
-else()
-  add_definitions(-DORTHANC_STATIC=0)
-endif()
--- a/Orthanc/Resources/CMake/DownloadPackage.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-macro(GetUrlFilename TargetVariable Url)
-  string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}")
-endmacro()
-
-
-macro(GetUrlExtension TargetVariable Url)
-  #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}")
-  string(REGEX REPLACE "^.*\\." "" TMP "${Url}")
-  string(TOLOWER "${TMP}" "${TargetVariable}")
-endmacro()
-
-
-
-##
-## Setup the patch command-line tool
-##
-
-if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-  set(PATCH_EXECUTABLE ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/patch/patch.exe)
-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()
-
-
-
-##
-## Check the existence of the required decompression tools
-##
-
-if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-  find_program(ZIP_EXECUTABLE 7z 
-    PATHS 
-    "$ENV{ProgramFiles}/7-Zip"
-    "$ENV{ProgramW6432}/7-Zip"
-    )
-
-  if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
-  endif()
-
-else()
-  find_program(UNZIP_EXECUTABLE unzip)
-  if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the 'unzip' package")
-  endif()
-
-  find_program(TAR_EXECUTABLE tar)
-  if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the 'tar' package")
-  endif()
-endif()
-
-
-macro(DownloadPackage MD5 Url TargetDirectory)
-  if (NOT IS_DIRECTORY "${TargetDirectory}")
-    GetUrlFilename(TMP_FILENAME "${Url}")
-
-    set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
-    if (NOT EXISTS "${TMP_PATH}")
-      message("Downloading ${Url}")
-
-      # This fixes issue 6: "I think cmake shouldn't download the
-      # packages which are not in the system, it should stop and let
-      # user know."
-      # https://code.google.com/p/orthanc/issues/detail?id=6
-      if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
-	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}")
-    else()
-      message("Using local copy of ${Url}")
-    endif()
-
-    GetUrlExtension(TMP_EXTENSION "${Url}")
-    #message(${TMP_EXTENSION})
-    message("Uncompressing ${TMP_FILENAME}")
-
-    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-      # How to silently extract files using 7-zip
-      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
-
-      if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
-        execute_process(
-          COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          OUTPUT_QUIET
-          )
-
-        if (Failure)
-          message(FATAL_ERROR "Error while running the uncompression tool")
-        endif()
-
-        if ("${TMP_EXTENSION}" STREQUAL "tgz")
-          string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
-        else()
-          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
-        endif()
-
-        execute_process(
-          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          OUTPUT_QUIET
-          )
-      elseif ("${TMP_EXTENSION}" STREQUAL "zip")
-        execute_process(
-          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          OUTPUT_QUIET
-          )
-      else()
-        message(FATAL_ERROR "Support your platform here")
-      endif()
-
-    else()
-      if ("${TMP_EXTENSION}" STREQUAL "zip")
-        execute_process(
-          COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-        )
-      elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
-        #message("tar xvfz ${TMP_PATH}")
-        execute_process(
-          COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          )
-      elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
-        execute_process(
-          COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          )
-      else()
-        message(FATAL_ERROR "Unknown package format.")
-      endif()
-    endif()
-   
-    if (Failure)
-      message(FATAL_ERROR "Error while running the uncompression tool")
-    endif()
-
-    if (NOT IS_DIRECTORY "${TargetDirectory}")
-      message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.")
-    endif()
-  endif()
-endmacro()
--- a/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-if (USE_GTEST_DEBIAN_SOURCE_PACKAGE)
-  set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc)
-  include_directories(/usr/src/gtest)
-
-  if (NOT EXISTS /usr/include/gtest/gtest.h OR
-      NOT EXISTS ${GTEST_SOURCES})
-    message(FATAL_ERROR "Please install the libgtest-dev package")
-  endif()
-
-elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
-  set(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0)
-  set(GTEST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip")
-  set(GTEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7")
-
-  DownloadPackage(${GTEST_MD5} ${GTEST_URL} "${GTEST_SOURCES_DIR}")
-
-  include_directories(
-    ${GTEST_SOURCES_DIR}/include
-    ${GTEST_SOURCES_DIR}
-    )
-
-  set(GTEST_SOURCES
-    ${GTEST_SOURCES_DIR}/src/gtest-all.cc
-    )
-
-  # https://code.google.com/p/googletest/issues/detail?id=412
-  if (MSVC) # VS2012 does not support tuples correctly yet
-    add_definitions(/D _VARIADIC_MAX=10)
-  endif()
-
-else()
-  include(FindGTest)
-  if (NOT GTEST_FOUND)
-    message(FATAL_ERROR "Unable to find GoogleTest")
-  endif()
-
-  include_directories(${GTEST_INCLUDE_DIRS})
-  link_libraries(${GTEST_LIBRARIES})
-endif()
--- a/Orthanc/Resources/CMake/JsonCppConfiguration.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
-  set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5)
-  set(JSONCPP_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-0.10.5.tar.gz")
-  set(JSONCPP_MD5 "db146bac5a126ded9bd728ab7b61ed6b")
-
-  DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}")
-
-  set(JSONCPP_SOURCES
-    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
-    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
-    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
-    )
-
-  include_directories(
-    ${JSONCPP_SOURCES_DIR}/include
-    )
-
-  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
-
-else()
-  find_path(JSONCPP_INCLUDE_DIR json/reader.h
-    /usr/include/jsoncpp
-    /usr/local/include/jsoncpp
-    )
-
-  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
-  include_directories(${JSONCPP_INCLUDE_DIR})
-  link_libraries(jsoncpp)
-
-  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
-  if (NOT HAVE_JSONCPP_H)
-    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
-  endif()
-
-endif()
--- a/Orthanc/Resources/CMake/PugixmlConfiguration.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-if (USE_PUGIXML)
-  add_definitions(-DORTHANC_PUGIXML_ENABLED=1)
-
-  if (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML)
-    set(PUGIXML_SOURCES_DIR ${CMAKE_BINARY_DIR}/pugixml-1.4)
-    set(PUGIXML_MD5 "7c56c91cfe3ecdee248a8e4892ef5781")
-    set(PUGIXML_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/pugixml-1.4.tar.gz")
-
-    DownloadPackage(${PUGIXML_MD5} ${PUGIXML_URL} "${PUGIXML_SOURCES_DIR}")
-
-    include_directories(
-      ${PUGIXML_SOURCES_DIR}/src
-      )
-
-    set(PUGIXML_SOURCES
-      #${PUGIXML_SOURCES_DIR}/src/vlog_is_on.cc
-      ${PUGIXML_SOURCES_DIR}/src/pugixml.cpp
-      )
-
-  else()
-    CHECK_INCLUDE_FILE_CXX(pugixml.hpp HAVE_PUGIXML_H)
-    if (NOT HAVE_PUGIXML_H)
-      message(FATAL_ERROR "Please install the libpugixml-dev package")
-    endif()
-
-    link_libraries(pugixml)
-  endif()
-
-else()
-  add_definitions(-DORTHANC_PUGIXML_ENABLED=0)
-endif()
--- a/Orthanc/Resources/CMake/ZlibConfiguration.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
-  SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
-  SET(ZLIB_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz")
-  SET(ZLIB_MD5 "60df6a37c56e7c1366cca812414f7b85")
-
-  DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}")
-
-  include_directories(
-    ${ZLIB_SOURCES_DIR}
-    )
-
-  list(APPEND ZLIB_SOURCES 
-    ${ZLIB_SOURCES_DIR}/adler32.c
-    ${ZLIB_SOURCES_DIR}/compress.c
-    ${ZLIB_SOURCES_DIR}/crc32.c 
-    ${ZLIB_SOURCES_DIR}/deflate.c 
-    ${ZLIB_SOURCES_DIR}/gzclose.c 
-    ${ZLIB_SOURCES_DIR}/gzlib.c 
-    ${ZLIB_SOURCES_DIR}/gzread.c 
-    ${ZLIB_SOURCES_DIR}/gzwrite.c 
-    ${ZLIB_SOURCES_DIR}/infback.c 
-    ${ZLIB_SOURCES_DIR}/inffast.c 
-    ${ZLIB_SOURCES_DIR}/inflate.c 
-    ${ZLIB_SOURCES_DIR}/inftrees.c 
-    ${ZLIB_SOURCES_DIR}/trees.c 
-    ${ZLIB_SOURCES_DIR}/uncompr.c 
-    ${ZLIB_SOURCES_DIR}/zutil.c
-    )
-
-else()
-  include(FindZLIB)
-  include_directories(${ZLIB_INCLUDE_DIRS})
-  link_libraries(${ZLIB_LIBRARIES})
-endif()
-
-source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- a/Orthanc/Resources/MinGW-W64-Toolchain32.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# the name of the target operating system
-set(CMAKE_SYSTEM_NAME Windows)
-
-# which compilers to use for C and C++
-set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
-set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
-set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
-
-# here is the target environment located
-set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
-
-# adjust the default behaviour of the FIND_XXX() commands:
-# search headers and libraries in the target environment, search 
-# programs in the host environment
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Orthanc/Resources/MinGW-W64-Toolchain64.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# the name of the target operating system
-set(CMAKE_SYSTEM_NAME Windows)
-
-# which compilers to use for C and C++
-set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
-set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
-set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
-
-# here is the target environment located
-set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
-
-# adjust the default behaviour of the FIND_XXX() commands:
-# search headers and libraries in the target environment, search 
-# programs in the host environment
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Orthanc/Resources/MinGWToolchain.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# the name of the target operating system
-set(CMAKE_SYSTEM_NAME Windows)
-
-# which compilers to use for C and C++
-set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
-set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
-set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
-
-# here is the target environment located
-set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
-
-# adjust the default behaviour of the FIND_XXX() commands:
-# search headers and libraries in the target environment, search 
-# programs in the host environment
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-// ISO C9x  compliant stdint.h for Microsoft Visual Studio
-// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
-// 
-//  Copyright (c) 2006-2008 Alexander Chemeris
-// 
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-// 
-//   1. Redistributions of source code must retain the above copyright notice,
-//      this list of conditions and the following disclaimer.
-// 
-//   2. Redistributions in binary form must reproduce the above copyright
-//      notice, this list of conditions and the following disclaimer in the
-//      documentation and/or other materials provided with the distribution.
-// 
-//   3. The name of the author may be used to endorse or promote products
-//      derived from this software without specific prior written permission.
-// 
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// 
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef _MSC_VER // [
-#error "Use this header only with Microsoft Visual C++ compilers!"
-#endif // _MSC_VER ]
-
-#ifndef _MSC_STDINT_H_ // [
-#define _MSC_STDINT_H_
-
-#if _MSC_VER > 1000
-#pragma once
-#endif
-
-#include <limits.h>
-
-// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
-// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
-// or compiler give many errors like this:
-//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
-#ifdef __cplusplus
-extern "C" {
-#endif
-#  include <wchar.h>
-#ifdef __cplusplus
-}
-#endif
-
-// Define _W64 macros to mark types changing their size, like intptr_t.
-#ifndef _W64
-#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
-#     define _W64 __w64
-#  else
-#     define _W64
-#  endif
-#endif
-
-
-// 7.18.1 Integer types
-
-// 7.18.1.1 Exact-width integer types
-
-// Visual Studio 6 and Embedded Visual C++ 4 doesn't
-// realize that, e.g. char has the same size as __int8
-// so we give up on __intX for them.
-#if (_MSC_VER < 1300)
-   typedef signed char       int8_t;
-   typedef signed short      int16_t;
-   typedef signed int        int32_t;
-   typedef unsigned char     uint8_t;
-   typedef unsigned short    uint16_t;
-   typedef unsigned int      uint32_t;
-#else
-   typedef signed __int8     int8_t;
-   typedef signed __int16    int16_t;
-   typedef signed __int32    int32_t;
-   typedef unsigned __int8   uint8_t;
-   typedef unsigned __int16  uint16_t;
-   typedef unsigned __int32  uint32_t;
-#endif
-typedef signed __int64       int64_t;
-typedef unsigned __int64     uint64_t;
-
-
-// 7.18.1.2 Minimum-width integer types
-typedef int8_t    int_least8_t;
-typedef int16_t   int_least16_t;
-typedef int32_t   int_least32_t;
-typedef int64_t   int_least64_t;
-typedef uint8_t   uint_least8_t;
-typedef uint16_t  uint_least16_t;
-typedef uint32_t  uint_least32_t;
-typedef uint64_t  uint_least64_t;
-
-// 7.18.1.3 Fastest minimum-width integer types
-typedef int8_t    int_fast8_t;
-typedef int16_t   int_fast16_t;
-typedef int32_t   int_fast32_t;
-typedef int64_t   int_fast64_t;
-typedef uint8_t   uint_fast8_t;
-typedef uint16_t  uint_fast16_t;
-typedef uint32_t  uint_fast32_t;
-typedef uint64_t  uint_fast64_t;
-
-// 7.18.1.4 Integer types capable of holding object pointers
-#ifdef _WIN64 // [
-   typedef signed __int64    intptr_t;
-   typedef unsigned __int64  uintptr_t;
-#else // _WIN64 ][
-   typedef _W64 signed int   intptr_t;
-   typedef _W64 unsigned int uintptr_t;
-#endif // _WIN64 ]
-
-// 7.18.1.5 Greatest-width integer types
-typedef int64_t   intmax_t;
-typedef uint64_t  uintmax_t;
-
-
-// 7.18.2 Limits of specified-width integer types
-
-#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
-
-// 7.18.2.1 Limits of exact-width integer types
-#define INT8_MIN     ((int8_t)_I8_MIN)
-#define INT8_MAX     _I8_MAX
-#define INT16_MIN    ((int16_t)_I16_MIN)
-#define INT16_MAX    _I16_MAX
-#define INT32_MIN    ((int32_t)_I32_MIN)
-#define INT32_MAX    _I32_MAX
-#define INT64_MIN    ((int64_t)_I64_MIN)
-#define INT64_MAX    _I64_MAX
-#define UINT8_MAX    _UI8_MAX
-#define UINT16_MAX   _UI16_MAX
-#define UINT32_MAX   _UI32_MAX
-#define UINT64_MAX   _UI64_MAX
-
-// 7.18.2.2 Limits of minimum-width integer types
-#define INT_LEAST8_MIN    INT8_MIN
-#define INT_LEAST8_MAX    INT8_MAX
-#define INT_LEAST16_MIN   INT16_MIN
-#define INT_LEAST16_MAX   INT16_MAX
-#define INT_LEAST32_MIN   INT32_MIN
-#define INT_LEAST32_MAX   INT32_MAX
-#define INT_LEAST64_MIN   INT64_MIN
-#define INT_LEAST64_MAX   INT64_MAX
-#define UINT_LEAST8_MAX   UINT8_MAX
-#define UINT_LEAST16_MAX  UINT16_MAX
-#define UINT_LEAST32_MAX  UINT32_MAX
-#define UINT_LEAST64_MAX  UINT64_MAX
-
-// 7.18.2.3 Limits of fastest minimum-width integer types
-#define INT_FAST8_MIN    INT8_MIN
-#define INT_FAST8_MAX    INT8_MAX
-#define INT_FAST16_MIN   INT16_MIN
-#define INT_FAST16_MAX   INT16_MAX
-#define INT_FAST32_MIN   INT32_MIN
-#define INT_FAST32_MAX   INT32_MAX
-#define INT_FAST64_MIN   INT64_MIN
-#define INT_FAST64_MAX   INT64_MAX
-#define UINT_FAST8_MAX   UINT8_MAX
-#define UINT_FAST16_MAX  UINT16_MAX
-#define UINT_FAST32_MAX  UINT32_MAX
-#define UINT_FAST64_MAX  UINT64_MAX
-
-// 7.18.2.4 Limits of integer types capable of holding object pointers
-#ifdef _WIN64 // [
-#  define INTPTR_MIN   INT64_MIN
-#  define INTPTR_MAX   INT64_MAX
-#  define UINTPTR_MAX  UINT64_MAX
-#else // _WIN64 ][
-#  define INTPTR_MIN   INT32_MIN
-#  define INTPTR_MAX   INT32_MAX
-#  define UINTPTR_MAX  UINT32_MAX
-#endif // _WIN64 ]
-
-// 7.18.2.5 Limits of greatest-width integer types
-#define INTMAX_MIN   INT64_MIN
-#define INTMAX_MAX   INT64_MAX
-#define UINTMAX_MAX  UINT64_MAX
-
-// 7.18.3 Limits of other integer types
-
-#ifdef _WIN64 // [
-#  define PTRDIFF_MIN  _I64_MIN
-#  define PTRDIFF_MAX  _I64_MAX
-#else  // _WIN64 ][
-#  define PTRDIFF_MIN  _I32_MIN
-#  define PTRDIFF_MAX  _I32_MAX
-#endif  // _WIN64 ]
-
-#define SIG_ATOMIC_MIN  INT_MIN
-#define SIG_ATOMIC_MAX  INT_MAX
-
-#ifndef SIZE_MAX // [
-#  ifdef _WIN64 // [
-#     define SIZE_MAX  _UI64_MAX
-#  else // _WIN64 ][
-#     define SIZE_MAX  _UI32_MAX
-#  endif // _WIN64 ]
-#endif // SIZE_MAX ]
-
-// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
-#ifndef WCHAR_MIN // [
-#  define WCHAR_MIN  0
-#endif  // WCHAR_MIN ]
-#ifndef WCHAR_MAX // [
-#  define WCHAR_MAX  _UI16_MAX
-#endif  // WCHAR_MAX ]
-
-#define WINT_MIN  0
-#define WINT_MAX  _UI16_MAX
-
-#endif // __STDC_LIMIT_MACROS ]
-
-
-// 7.18.4 Limits of other integer types
-
-#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
-
-// 7.18.4.1 Macros for minimum-width integer constants
-
-#define INT8_C(val)  val##i8
-#define INT16_C(val) val##i16
-#define INT32_C(val) val##i32
-#define INT64_C(val) val##i64
-
-#define UINT8_C(val)  val##ui8
-#define UINT16_C(val) val##ui16
-#define UINT32_C(val) val##ui32
-#define UINT64_C(val) val##ui64
-
-// 7.18.4.2 Macros for greatest-width integer constants
-#define INTMAX_C   INT64_C
-#define UINTMAX_C  UINT64_C
-
-#endif // __STDC_CONSTANT_MACROS ]
-
-
-#endif // _MSC_STDINT_H_ ]
--- a/Orthanc/Resources/WindowsResources.py	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-#!/usr/bin/python
-
-# Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2015 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/>.
-
-
-import os
-import sys
-import datetime
-
-if len(sys.argv) != 5:
-    sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0])
-    sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0])
-    sys.exit(-1)
-
-SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc')
-
-VERSION = sys.argv[1]
-PRODUCT = sys.argv[2]
-FILENAME = sys.argv[3]
-DESCRIPTION = sys.argv[4]
-
-if VERSION == 'mainline':
-    VERSION = '999.999.999'
-    RELEASE = 'This is a mainline build, not an official release'
-else:
-    RELEASE = 'Release %s' % VERSION
-
-v = VERSION.split('.')
-if len(v) != 2 and len(v) != 3:
-    sys.stderr.write('Bad version number: %s\n' % VERSION)
-    sys.exit(-1)
-
-if len(v) == 2:
-    v.append('0')
-
-extension = os.path.splitext(FILENAME)[1]
-if extension.lower() == '.dll':
-    BLOCK = '040904E4'
-    TYPE = 'VFT_DLL'
-elif extension.lower() == '.exe':
-    #BLOCK = '040904B0'   # LANG_ENGLISH/SUBLANG_ENGLISH_US,
-    BLOCK = '040904E4'   # Lang=US English, CharSet=Windows Multilingual
-    TYPE = 'VFT_APP'
-else:
-    sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension)
-    sys.exit(-1)
-
-
-with open(SOURCE, 'r') as source:
-    content = source.read()
-    content = content.replace('${VERSION_MAJOR}', v[0])
-    content = content.replace('${VERSION_MINOR}', v[1])
-    content = content.replace('${VERSION_PATCH}', v[2])
-    content = content.replace('${RELEASE}', RELEASE)
-    content = content.replace('${DESCRIPTION}', DESCRIPTION)
-    content = content.replace('${PRODUCT}', PRODUCT)   
-    content = content.replace('${FILENAME}', FILENAME)   
-    content = content.replace('${YEAR}', str(datetime.datetime.now().year))
-    content = content.replace('${BLOCK}', BLOCK)
-    content = content.replace('${TYPE}', TYPE)
-
-    sys.stdout.write(content)
--- a/Orthanc/Resources/WindowsResources.rc	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#include <winver.h>
-
-VS_VERSION_INFO VERSIONINFO
-   FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH}
-   PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0
-   FILEOS VOS_NT_WINDOWS32
-   FILETYPE ${TYPE}
-   BEGIN
-      BLOCK "StringFileInfo"
-      BEGIN
-         BLOCK "${BLOCK}"
-         BEGIN
-            VALUE "Comments", "${RELEASE}"
-            VALUE "CompanyName", "University Hospital of Liege, Belgium"
-            VALUE "FileDescription", "${DESCRIPTION}"
-            VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
-            VALUE "InternalName", "${PRODUCT}"
-            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium"
-            VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
-            VALUE "OriginalFilename", "${FILENAME}"
-            VALUE "ProductName", "${PRODUCT}"
-            VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}"
-         END
-      END
-
-      BLOCK "VarFileInfo"
-      BEGIN
-        VALUE "Translation", 0x409, 1252  // U.S. English
-      END
-   END
--- a/Orthanc/Sdk-0.9.5/orthanc/OrthancCPlugin.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4686 +0,0 @@
-/**
- * \mainpage
- *
- * This C/C++ SDK allows external developers to create plugins that
- * can be loaded into Orthanc to extend its functionality. Each
- * Orthanc plugin must expose 4 public functions with the following
- * signatures:
- * 
- * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
- *    This function is invoked by Orthanc when it loads the plugin on startup.
- *    The plugin must:
- *    - Check its compatibility with the Orthanc version using
- *      ::OrthancPluginCheckVersion().
- *    - Store the context pointer so that it can use the plugin 
- *      services of Orthanc.
- *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
- *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
- *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
- *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
- *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
- *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
- *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
- * -# <tt>void OrthancPluginFinalize()</tt>:
- *    This function is invoked by Orthanc during its shutdown. The plugin
- *    must free all its memory.
- * -# <tt>const char* OrthancPluginGetName()</tt>:
- *    The plugin must return a short string to identify itself.
- * -# <tt>const char* OrthancPluginGetVersion()</tt>:
- *    The plugin must return a string containing its version number.
- *
- * The name and the version of a plugin is only used to prevent it
- * from being loaded twice. Note that, in C++, it is mandatory to
- * declare these functions within an <tt>extern "C"</tt> section.
- * 
- * To ensure multi-threading safety, the various REST callbacks are
- * guaranteed to be executed in mutual exclusion since Orthanc
- * 0.8.5. If this feature is undesired (notably when developing
- * high-performance plugins handling simultaneous requests), use
- * ::OrthancPluginRegisterRestCallbackNoLock().
- **/
-
-
-
-/**
- * @defgroup Images Images and compression
- * @brief Functions to deal with images and compressed buffers.
- *
- * @defgroup REST REST
- * @brief Functions to answer REST requests in a callback.
- *
- * @defgroup Callbacks Callbacks
- * @brief Functions to register and manage callbacks by the plugins.
- *
- * @defgroup Worklists Worklists
- * @brief Functions to register and manage worklists.
- *
- * @defgroup Orthanc Orthanc
- * @brief Functions to access the content of the Orthanc server.
- **/
-
-
-
-/**
- * @defgroup Toolbox Toolbox
- * @brief Generic functions to help with the creation of plugins.
- **/
-
-
-
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 <stdio.h>
-#include <string.h>
-
-#ifdef WIN32
-#define ORTHANC_PLUGINS_API __declspec(dllexport)
-#else
-#define ORTHANC_PLUGINS_API
-#endif
-
-#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     0
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     9
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  5
-
-
-
-/********************************************************************
- ** Check that function inlining is properly supported. The use of
- ** inlining is required, to avoid the duplication of object code
- ** between two compilation modules that would use the Orthanc Plugin
- ** API.
- ********************************************************************/
-
-/* If the auto-detection of the "inline" keyword below does not work
-   automatically and that your compiler is known to properly support
-   inlining, uncomment the following #define and adapt the definition
-   of "static inline". */
-
-/* #define ORTHANC_PLUGIN_INLINE static inline */
-
-#ifndef ORTHANC_PLUGIN_INLINE
-#  if __STDC_VERSION__ >= 199901L
-/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
-#    define ORTHANC_PLUGIN_INLINE static inline
-#  elif defined(__cplusplus)
-/*   This is C++ */
-#    define ORTHANC_PLUGIN_INLINE static inline
-#  elif defined(__GNUC__)
-/*   This is GCC running in C89 mode */
-#    define ORTHANC_PLUGIN_INLINE static __inline
-#  elif defined(_MSC_VER)
-/*   This is Visual Studio running in C89 mode */
-#    define ORTHANC_PLUGIN_INLINE static __inline
-#  else
-#    error Your compiler is not known to support the "inline" keyword
-#  endif
-#endif
-
-
-
-/********************************************************************
- ** Inclusion of standard libraries.
- ********************************************************************/
-
-/**
- * For Microsoft Visual Studio, a compatibility "stdint.h" can be
- * downloaded at the following URL:
- * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h
- **/
-#include <stdint.h>
-
-#include <stdlib.h>
-
-
-
-/********************************************************************
- ** Definition of the Orthanc Plugin API.
- ********************************************************************/
-
-/** @{ */
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-  /**
-   * The various error codes that can be returned by the Orthanc core.
-   **/
-  typedef enum
-  {
-    OrthancPluginErrorCode_InternalError = -1    /*!< Internal error */,
-    OrthancPluginErrorCode_Success = 0    /*!< Success */,
-    OrthancPluginErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
-    OrthancPluginErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
-    OrthancPluginErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
-    OrthancPluginErrorCode_NotEnoughMemory = 4    /*!< Not enough memory */,
-    OrthancPluginErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
-    OrthancPluginErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
-    OrthancPluginErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
-    OrthancPluginErrorCode_BadRequest = 8    /*!< Bad request */,
-    OrthancPluginErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
-    OrthancPluginErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
-    OrthancPluginErrorCode_Database = 11    /*!< Error with the database engine */,
-    OrthancPluginErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
-    OrthancPluginErrorCode_InexistentFile = 13    /*!< Inexistent file */,
-    OrthancPluginErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
-    OrthancPluginErrorCode_BadFileFormat = 15    /*!< Bad file format */,
-    OrthancPluginErrorCode_Timeout = 16    /*!< Timeout */,
-    OrthancPluginErrorCode_UnknownResource = 17    /*!< Unknown resource */,
-    OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
-    OrthancPluginErrorCode_FullStorage = 19    /*!< The file storage is full */,
-    OrthancPluginErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
-    OrthancPluginErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
-    OrthancPluginErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
-    OrthancPluginErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
-    OrthancPluginErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
-    OrthancPluginErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
-    OrthancPluginErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
-    OrthancPluginErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
-    OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
-    OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
-    OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
-    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
-    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
-    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
-    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
-    OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
-    OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
-    OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
-    OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
-    OrthancPluginErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
-    OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
-    OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
-    OrthancPluginErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
-    OrthancPluginErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
-    OrthancPluginErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
-    OrthancPluginErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
-    OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
-    OrthancPluginErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
-    OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
-    OrthancPluginErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
-    OrthancPluginErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
-    OrthancPluginErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
-    OrthancPluginErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
-    OrthancPluginErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
-    OrthancPluginErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is already in use */,
-    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
-    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
-    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
-    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
-    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
-    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
-    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
-    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
-    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
-    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
-    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
-    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
-    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
-    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
-    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
-    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
-    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
-    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
-    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
-    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
-    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
-    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
-    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
-    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
-    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
-    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
-    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
-    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
-    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
-    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
-    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
-    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
-    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
-    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
-    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
-    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
-    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
-
-    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
-  } OrthancPluginErrorCode;
-
-
-  /**
-   * Forward declaration of one of the mandatory functions for Orthanc
-   * plugins.
-   **/
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
-
-
-  /**
-   * The various HTTP methods for a REST call.
-   **/
-  typedef enum
-  {
-    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
-    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
-    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
-    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
-
-    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
-  } OrthancPluginHttpMethod;
-
-
-  /**
-   * @brief The parameters of a REST request.
-   * @ingroup Callbacks
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The HTTP method.
-     **/
-    OrthancPluginHttpMethod method;    
-
-    /**
-     * @brief The number of groups of the regular expression.
-     **/
-    uint32_t                groupsCount;
-
-    /**
-     * @brief The matched values for the groups of the regular expression.
-     **/
-    const char* const*      groups;
-
-    /**
-     * @brief For a GET request, the number of GET parameters.
-     **/
-    uint32_t                getCount;
-
-    /**
-     * @brief For a GET request, the keys of the GET parameters.
-     **/
-    const char* const*      getKeys;
-
-    /**
-     * @brief For a GET request, the values of the GET parameters.
-     **/
-    const char* const*      getValues;
-
-    /**
-     * @brief For a PUT or POST request, the content of the body.
-     **/
-    const char*             body;
-
-    /**
-     * @brief For a PUT or POST request, the number of bytes of the body.
-     **/
-    uint32_t                bodySize;
-
-
-    /* --------------------------------------------------
-       New in version 0.8.1
-       -------------------------------------------------- */
-
-    /**
-     * @brief The number of HTTP headers.
-     **/
-    uint32_t                headersCount;
-
-    /**
-     * @brief The keys of the HTTP headers (always converted to low-case).
-     **/
-    const char* const*      headersKeys;
-
-    /**
-     * @brief The values of the HTTP headers.
-     **/
-    const char* const*      headersValues;
-
-  } OrthancPluginHttpRequest;
-
-
-  typedef enum 
-  {
-    /* Generic services */
-    _OrthancPluginService_LogInfo = 1,
-    _OrthancPluginService_LogWarning = 2,
-    _OrthancPluginService_LogError = 3,
-    _OrthancPluginService_GetOrthancPath = 4,
-    _OrthancPluginService_GetOrthancDirectory = 5,
-    _OrthancPluginService_GetConfigurationPath = 6,
-    _OrthancPluginService_SetPluginProperty = 7,
-    _OrthancPluginService_GetGlobalProperty = 8,
-    _OrthancPluginService_SetGlobalProperty = 9,
-    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
-    _OrthancPluginService_GetCommandLineArgument = 11,
-    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
-    _OrthancPluginService_GetConfiguration = 13,
-    _OrthancPluginService_BufferCompression = 14,
-    _OrthancPluginService_ReadFile = 15,
-    _OrthancPluginService_WriteFile = 16,
-    _OrthancPluginService_GetErrorDescription = 17,
-    _OrthancPluginService_CallHttpClient = 18,
-    _OrthancPluginService_RegisterErrorCode = 19,
-    _OrthancPluginService_RegisterDictionaryTag = 20,
-    _OrthancPluginService_DicomBufferToJson = 21,
-    _OrthancPluginService_DicomInstanceToJson = 22,
-    _OrthancPluginService_CreateDicom = 23,
-    _OrthancPluginService_ComputeMd5 = 24,
-    _OrthancPluginService_ComputeSha1 = 25,
-    _OrthancPluginService_LookupDictionary = 26,
-
-    /* Registration of callbacks */
-    _OrthancPluginService_RegisterRestCallback = 1000,
-    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
-    _OrthancPluginService_RegisterStorageArea = 1002,
-    _OrthancPluginService_RegisterOnChangeCallback = 1003,
-    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
-    _OrthancPluginService_RegisterWorklistCallback = 1005,
-    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
-
-    /* Sending answers to REST calls */
-    _OrthancPluginService_AnswerBuffer = 2000,
-    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
-    _OrthancPluginService_Redirect = 2002,
-    _OrthancPluginService_SendHttpStatusCode = 2003,
-    _OrthancPluginService_SendUnauthorized = 2004,
-    _OrthancPluginService_SendMethodNotAllowed = 2005,
-    _OrthancPluginService_SetCookie = 2006,
-    _OrthancPluginService_SetHttpHeader = 2007,
-    _OrthancPluginService_StartMultipartAnswer = 2008,
-    _OrthancPluginService_SendMultipartItem = 2009,
-    _OrthancPluginService_SendHttpStatus = 2010,
-    _OrthancPluginService_CompressAndAnswerImage = 2011,
-
-    /* Access to the Orthanc database and API */
-    _OrthancPluginService_GetDicomForInstance = 3000,
-    _OrthancPluginService_RestApiGet = 3001,
-    _OrthancPluginService_RestApiPost = 3002,
-    _OrthancPluginService_RestApiDelete = 3003,
-    _OrthancPluginService_RestApiPut = 3004,
-    _OrthancPluginService_LookupPatient = 3005,
-    _OrthancPluginService_LookupStudy = 3006,
-    _OrthancPluginService_LookupSeries = 3007,
-    _OrthancPluginService_LookupInstance = 3008,
-    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
-    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
-    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
-    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
-    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
-    _OrthancPluginService_ReconstructMainDicomTags = 3014,
-    _OrthancPluginService_RestApiGet2 = 3015,
-
-    /* Access to DICOM instances */
-    _OrthancPluginService_GetInstanceRemoteAet = 4000,
-    _OrthancPluginService_GetInstanceSize = 4001,
-    _OrthancPluginService_GetInstanceData = 4002,
-    _OrthancPluginService_GetInstanceJson = 4003,
-    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
-    _OrthancPluginService_HasInstanceMetadata = 4005,
-    _OrthancPluginService_GetInstanceMetadata = 4006,
-    _OrthancPluginService_GetInstanceOrigin = 4007,
-
-    /* Services for plugins implementing a database back-end */
-    _OrthancPluginService_RegisterDatabaseBackend = 5000,
-    _OrthancPluginService_DatabaseAnswer = 5001,
-    _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
-    _OrthancPluginService_StorageAreaCreate = 5003,
-    _OrthancPluginService_StorageAreaRead = 5004,
-    _OrthancPluginService_StorageAreaRemove = 5005,
-
-    /* Primitives for handling images */
-    _OrthancPluginService_GetImagePixelFormat = 6000,
-    _OrthancPluginService_GetImageWidth = 6001,
-    _OrthancPluginService_GetImageHeight = 6002,
-    _OrthancPluginService_GetImagePitch = 6003,
-    _OrthancPluginService_GetImageBuffer = 6004,
-    _OrthancPluginService_UncompressImage = 6005,
-    _OrthancPluginService_FreeImage = 6006,
-    _OrthancPluginService_CompressImage = 6007,
-    _OrthancPluginService_ConvertPixelFormat = 6008,
-    _OrthancPluginService_GetFontsCount = 6009,
-    _OrthancPluginService_GetFontInfo = 6010,
-    _OrthancPluginService_DrawText = 6011,
-    _OrthancPluginService_CreateImage = 6012,
-    _OrthancPluginService_CreateImageAccessor = 6013,
-    _OrthancPluginService_DecodeDicomImage = 6014,
-
-    /* Primitives for handling worklists */
-    _OrthancPluginService_WorklistAddAnswer = 7000,
-    _OrthancPluginService_WorklistMarkIncomplete = 7001,
-    _OrthancPluginService_WorklistIsMatch = 7002,
-    _OrthancPluginService_WorklistGetDicomQuery = 7003,
-
-    _OrthancPluginService_INTERNAL = 0x7fffffff
-  } _OrthancPluginService;
-
-
-  typedef enum
-  {
-    _OrthancPluginProperty_Description = 1,
-    _OrthancPluginProperty_RootUri = 2,
-    _OrthancPluginProperty_OrthancExplorer = 3,
-
-    _OrthancPluginProperty_INTERNAL = 0x7fffffff
-  } _OrthancPluginProperty;
-
-
-
-  /**
-   * The memory layout of the pixels of an image.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    /**
-     * @brief Graylevel 8bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * one byte.
-     **/
-    OrthancPluginPixelFormat_Grayscale8 = 1,
-
-    /**
-     * @brief Graylevel, unsigned 16bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * two bytes.
-     **/
-    OrthancPluginPixelFormat_Grayscale16 = 2,
-
-    /**
-     * @brief Graylevel, signed 16bpp image.
-     *
-     * The image is graylevel. Each pixel is signed and stored in two
-     * bytes.
-     **/
-    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
-
-    /**
-     * @brief Color image in RGB24 format.
-     *
-     * This format describes a color image. The pixels are stored in 3
-     * consecutive bytes. The memory layout is RGB.
-     **/
-    OrthancPluginPixelFormat_RGB24 = 4,
-
-    /**
-     * @brief Color image in RGBA32 format.
-     *
-     * This format describes a color image. The pixels are stored in 4
-     * consecutive bytes. The memory layout is RGBA.
-     **/
-    OrthancPluginPixelFormat_RGBA32 = 5,
-
-    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
-
-    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginPixelFormat;
-
-
-
-  /**
-   * The content types that are supported by Orthanc plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
-    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
-    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
-
-    _OrthancPluginContentType_INTERNAL = 0x7fffffff
-  } OrthancPluginContentType;
-
-
-
-  /**
-   * The supported types of DICOM resources.
-   **/
-  typedef enum
-  {
-    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
-    OrthancPluginResourceType_Study = 1,       /*!< Study */
-    OrthancPluginResourceType_Series = 2,      /*!< Series */
-    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
-    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
-
-    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
-  } OrthancPluginResourceType;
-
-
-
-  /**
-   * The supported types of changes that can happen to DICOM resources.
-   * @ingroup Callbacks
-   **/
-  typedef enum
-  {
-    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
-    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
-    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
-    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
-    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
-    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
-    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
-    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
-    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
-    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
-    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
-    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
-    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
-    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
-
-    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
-  } OrthancPluginChangeType;
-
-
-  /**
-   * The compression algorithms that are supported by the Orthanc core.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
-    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
-    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
-    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
-
-    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
-  } OrthancPluginCompressionType;
-
-
-  /**
-   * The image formats that are supported by the Orthanc core.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
-    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
-    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
-
-    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginImageFormat;
-
-
-  /**
-   * The value representations present in the DICOM standard (version 2013).
-   * @ingroup Toolbox
-   **/
-  typedef enum
-  {
-    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
-    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
-    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
-    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
-    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
-    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
-    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
-    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
-    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
-    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
-    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
-    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
-    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
-    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
-    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
-    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
-    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
-    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
-    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
-    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
-    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
-    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
-    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
-    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
-    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
-    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
-    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
-
-    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
-  } OrthancPluginValueRepresentation;
-
-
-  /**
-   * The possible output formats for a DICOM-to-JSON conversion.
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomToJson()
-   **/
-  typedef enum
-  {
-    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
-    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
-    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
-
-    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginDicomToJsonFormat;
-
-
-  /**
-   * Flags to customize a DICOM-to-JSON conversion. By default, binary
-   * tags are formatted using Data URI scheme.
-   * @ingroup Toolbox
-   **/
-  typedef enum
-  {
-    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
-    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
-    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
-    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
-    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
-    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
-
-    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
-  } OrthancPluginDicomToJsonFlags;
-
-
-  /**
-   * Flags to the creation of a DICOM file.
-   * @ingroup Toolbox
-   * @see OrthancPluginCreateDicom()
-   **/
-  typedef enum
-  {
-    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
-    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
-
-    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
-  } OrthancPluginCreateDicomFlags;
-
-
-  /**
-   * The constraints on the DICOM identifiers that must be supported
-   * by the database plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
-    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
-    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
-    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
-
-    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
-  } OrthancPluginIdentifierConstraint;
-
-
-  /**
-   * The origin of a DICOM instance that has been received by Orthanc.
-   **/
-  typedef enum
-  {
-    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
-    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
-    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
-    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
-    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
-
-    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
-  } OrthancPluginInstanceOrigin;
-
-
-  /**
-   * @brief A memory buffer allocated by the core system of Orthanc.
-   *
-   * A memory buffer allocated by the core system of Orthanc. When the
-   * content of the buffer is not useful anymore, it must be free by a
-   * call to ::OrthancPluginFreeMemoryBuffer().
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The content of the buffer.
-     **/
-    void*      data;
-
-    /**
-     * @brief The number of bytes in the buffer.
-     **/
-    uint32_t   size;
-  } OrthancPluginMemoryBuffer;
-
-
-
-
-  /**
-   * @brief Opaque structure that represents the HTTP connection to the client application.
-   * @ingroup Callback
-   **/
-  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
-
-
-
-  /**
-   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
-   **/
-  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
-
-
-
-  /**
-   * @brief Opaque structure that represents an image that is uncompressed in memory.
-   * @ingroup Images
-   **/
-  typedef struct _OrthancPluginImage_t OrthancPluginImage;
-
-
-
-  /**
-   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
-   * @ingroup Images
-   **/
-  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
-
-
-
-  /**
-   * @brief Opaque structure to an object that represents a C-Find query.
-   * @ingroup Worklists
-   **/
-  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
-
-
-
-  /**
-   * @brief Opaque structure to an object that represents the answers to a C-Find query.
-   * @ingroup Worklists
-   **/
-  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
-
-
-
-  /**
-   * @brief Signature of a callback function that answers to a REST request.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
-    OrthancPluginRestOutput* output,
-    const char* url,
-    const OrthancPluginHttpRequest* request);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
-    OrthancPluginDicomInstance* instance,
-    const char* instanceId);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
-    OrthancPluginChangeType changeType,
-    OrthancPluginResourceType resourceType,
-    const char* resourceId);
-
-
-
-  /**
-   * @brief Signature of a callback function to decode a DICOM instance as an image.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
-    OrthancPluginImage** target,
-    const void* dicom,
-    const uint32_t size,
-    uint32_t frameIndex);
-
-
-
-  /**
-   * @brief Signature of a function to free dynamic memory.
-   **/
-  typedef void (*OrthancPluginFree) (void* buffer);
-
-
-
-  /**
-   * @brief Callback for writing to the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
-   *
-   * @param uuid The UUID of the file.
-   * @param content The content of the file.
-   * @param size The size of the file.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
-    const char* uuid,
-    const void* content,
-    int64_t size,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for reading from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
-   *
-   * @param content The content of the file (output).
-   * @param size The size of the file (output).
-   * @param uuid The UUID of the file of interest.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
-    void** content,
-    int64_t* size,
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for removing a file from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
-   *
-   * @param uuid The UUID of the file to be removed.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
-   *
-   * Signature of a callback function that is triggered when Orthanc
-   * receives a C-Find SCP request against modality worklists.
-   *
-   * @param answers The target structure where answers must be stored.
-   * @param query The worklist query.
-   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
-   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
-    OrthancPluginWorklistAnswers*     answers,
-    const OrthancPluginWorklistQuery* query,
-    const char*                       remoteAet,
-    const char*                       calledAet);
-
-
-
-  /**
-   * @brief Data structure that contains information about the Orthanc core.
-   **/
-  typedef struct _OrthancPluginContext_t
-  {
-    void*                     pluginsManager;
-    const char*               orthancVersion;
-    OrthancPluginFree         Free;
-    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
-                                              _OrthancPluginService service,
-                                              const void* params);
-  } OrthancPluginContext;
-
-
-  
-  /**
-   * @brief An entry in the dictionary of DICOM tags.
-   **/
-  typedef struct
-  {
-    uint16_t                          group;            /*!< The group of the tag */
-    uint16_t                          element;          /*!< The element of the tag */
-    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
-    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
-    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
-  } OrthancPluginDictionaryEntry;
-
-
-
-  /**
-   * @brief Free a string.
-   * 
-   * Free a string that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param str The string to be freed.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
-    OrthancPluginContext* context, 
-    char* str)
-  {
-    if (str != NULL)
-    {
-      context->Free(str);
-    }
-  }
-
-
-  /**
-   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
-   * 
-   * This function checks whether the version of this C header is
-   * compatible with the current version of Orthanc. The result of
-   * this function should always be checked in the
-   * OrthancPluginInitialize() entry point of the plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return 1 if and only if the versions are compatible. If the
-   * result is 0, the initialization of the plugin should fail.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
-    OrthancPluginContext* context)
-  {
-    int major, minor, revision;
-
-    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
-        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
-        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
-        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
-    {
-      /* Mismatch in the size of the enumerations */
-      return 0;
-    }
-
-    /* Assume compatibility with the mainline */
-    if (!strcmp(context->orthancVersion, "mainline"))
-    {
-      return 1;
-    }
-
-    /* Parse the version of the Orthanc core */
-    if ( 
-#ifdef _MSC_VER
-      sscanf_s
-#else
-      sscanf
-#endif
-      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
-    {
-      return 0;
-    }
-
-    /* Check the major number of the version */
-
-    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the minor number of the version */
-
-    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the revision number of the version */
-
-    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
-    {
-      return 1;
-    }
-    else
-    {
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Free a memory buffer.
-   * 
-   * Free a memory buffer that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer to release.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
-    OrthancPluginContext* context, 
-    OrthancPluginMemoryBuffer* buffer)
-  {
-    context->Free(buffer->data);
-  }
-
-
-  /**
-   * @brief Log an error.
-   *
-   * Log an error message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogError, message);
-  }
-
-
-  /**
-   * @brief Log a warning.
-   *
-   * Log a warning message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
-  }
-
-
-  /**
-   * @brief Log an information.
-   *
-   * Log an information message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
-  }
-
-
-
-  typedef struct
-  {
-    const char* pathRegularExpression;
-    OrthancPluginRestCallback callback;
-  } _OrthancPluginRestCallback;
-
-  /**
-   * @brief Register a REST callback.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   *
-   * Each REST callback is guaranteed to run in mutual exclusion.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   * @see OrthancPluginRegisterRestCallbackNoLock()
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
-  }
-
-
-
-  /**
-   * @brief Register a REST callback, without locking.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   *
-   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
-   * will NOT be invoked in mutual exclusion. This can be useful for
-   * high-performance plugins that must handle concurrent requests
-   * (Orthanc uses a pool of threads, one thread being assigned to
-   * each incoming HTTP request). Of course, it is up to the plugin to
-   * implement the required locking mechanisms.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   * @see OrthancPluginRegisterRestCallback()
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnStoredInstanceCallback callback;
-  } _OrthancPluginOnStoredInstanceCallback;
-
-  /**
-   * @brief Register a callback for received instances.
-   *
-   * This function registers a callback function that is called
-   * whenever a new DICOM instance is stored into the Orthanc core.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
-    OrthancPluginContext*                  context,
-    OrthancPluginOnStoredInstanceCallback  callback)
-  {
-    _OrthancPluginOnStoredInstanceCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              answer;
-    uint32_t                 answerSize;
-    const char*              mimeType;
-  } _OrthancPluginAnswerBuffer;
-
-  /**
-   * @brief Answer to a REST request.
-   *
-   * This function answers to a REST request with the content of a memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param answer Pointer to the memory buffer containing the answer.
-   * @param answerSize Number of bytes of the answer.
-   * @param mimeType The MIME type of the answer.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              answer,
-    uint32_t                 answerSize,
-    const char*              mimeType)
-  {
-    _OrthancPluginAnswerBuffer params;
-    params.output = output;
-    params.answer = answer;
-    params.answerSize = answerSize;
-    params.mimeType = mimeType;
-    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginPixelFormat  format;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-  } _OrthancPluginCompressAndAnswerPngImage;
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginImageFormat  imageFormat;
-    OrthancPluginPixelFormat  pixelFormat;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-    uint8_t                   quality;
-  } _OrthancPluginCompressAndAnswerImage;
-
-
-  /**
-   * @brief Answer to a REST request with a PNG image.
-   *
-   * This function answers to a REST request with a PNG image. The
-   * parameters of this function describe a memory buffer that
-   * contains an uncompressed image. The image will be automatically compressed
-   * as a PNG image by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer containing the uncompressed image.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
-    OrthancPluginContext*     context,
-    OrthancPluginRestOutput*  output,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    const void*               buffer)
-  {
-    _OrthancPluginCompressAndAnswerImage params;
-    params.output = output;
-    params.imageFormat = OrthancPluginImageFormat_Png;
-    params.pixelFormat = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    params.quality = 0;  /* No quality for PNG */
-    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 instanceId;
-  } _OrthancPluginGetDicomForInstance;
-
-  /**
-   * @brief Retrieve a DICOM instance using its Orthanc identifier.
-   * 
-   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
-   * file is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 instanceId)
-  {
-    _OrthancPluginGetDicomForInstance params;
-    params.target = target;
-    params.instanceId = instanceId;
-    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-  } _OrthancPluginRestApiGet;
-
-  /**
-   * @brief Make a GET call to the built-in Orthanc REST API.
-   * 
-   * Make a GET call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGetAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
-  }
-
-
-
-  /**
-   * @brief Make a GET call to the REST API, as tainted by the plugins.
-   * 
-   * Make a GET call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    const char*                 body;
-    uint32_t                    bodySize;
-  } _OrthancPluginRestApiPostPut;
-
-  /**
-   * @brief Make a POST call to the built-in Orthanc REST API.
-   * 
-   * Make a POST call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPostAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
-  }
-
-
-  /**
-   * @brief Make a POST call to the REST API, as tainted by the plugins.
-   * 
-   * Make a POST call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPost
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
-  }
-
-
-
-  /**
-   * @brief Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiDeleteAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
-  }
-
-
-  /**
-   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
-   * 
-   * Make a DELETE call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. 
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiDelete
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the built-in Orthanc REST API.
-   * 
-   * Make a PUT call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPutAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the REST API, as tainted by the plugins.
-   * 
-   * Make a PUT call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPut
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              argument;
-  } _OrthancPluginOutputPlusArgument;
-
-  /**
-   * @brief Redirect a REST request.
-   *
-   * This function answers to a REST request by redirecting the user
-   * to another URI using HTTP status 301.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param redirection Where to redirect.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              redirection)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = redirection;
-    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const char*  argument;
-  } _OrthancPluginRetrieveDynamicString;
-
-  /**
-   * @brief Look for a patient.
-   *
-   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored patients).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param patientID The Patient ID of interest.
-   * @return The NULL value if the patient is non-existent, or a string containing the 
-   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
-    OrthancPluginContext*  context,
-    const char*            patientID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = patientID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study.
-   *
-   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param studyUID The Study Instance UID of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
-    OrthancPluginContext*  context,
-    const char*            studyUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = studyUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study, using the accession number.
-   *
-   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param accessionNumber The Accession Number of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
-    OrthancPluginContext*  context,
-    const char*            accessionNumber)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = accessionNumber;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a series.
-   *
-   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored series).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param seriesUID The Series Instance UID of interest.
-   * @return The NULL value if the series is non-existent, or a string containing the 
-   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
-    OrthancPluginContext*  context,
-    const char*            seriesUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = seriesUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for an instance.
-   *
-   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored instances).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param sopInstanceUID The SOP Instance UID of interest.
-   * @return The NULL value if the instance is non-existent, or a string containing the 
-   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
-    OrthancPluginContext*  context,
-    const char*            sopInstanceUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = sopInstanceUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    uint16_t                 status;
-  } _OrthancPluginSendHttpStatusCode;
-
-  /**
-   * @brief Send a HTTP status code.
-   *
-   * This function answers to a REST request by sending a HTTP status
-   * code (such as "400 - Bad Request"). Note that:
-   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
-   * - Redirections (status 301) must use ::OrthancPluginRedirect().
-   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
-   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param status The HTTP status code to be sent.
-   * @ingroup REST
-   * @see OrthancPluginSendHttpStatus()
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    uint16_t                 status)
-  {
-    _OrthancPluginSendHttpStatusCode params;
-    params.output = output;
-    params.status = status;
-    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
-  }
-
-
-  /**
-   * @brief Signal that a REST request is not authorized.
-   *
-   * This function answers to a REST request by signaling that it is
-   * not authorized.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param realm The realm for the authorization process.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              realm)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = realm;
-    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
-  }
-
-
-  /**
-   * @brief Signal that this URI does not support this HTTP method.
-   *
-   * This function answers to a REST request by signaling that the
-   * queried URI does not support this method.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              allowedMethods)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = allowedMethods;
-    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              key;
-    const char*              value;
-  } _OrthancPluginSetHttpHeader;
-
-  /**
-   * @brief Set a cookie.
-   *
-   * This function sets a cookie in the HTTP client.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param cookie The cookie to be set.
-   * @param value The value of the cookie.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              cookie,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = cookie;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
-  }
-
-
-  /**
-   * @brief Set some HTTP header.
-   *
-   * This function sets a HTTP header in the HTTP answer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param key The HTTP header to be set.
-   * @param value The value of the HTTP header.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              key,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = key;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
-  }
-
-
-  typedef struct
-  {
-    char**                       resultStringToFree;
-    const char**                 resultString;
-    int64_t*                     resultInt64;
-    const char*                  key;
-    OrthancPluginDicomInstance*  instance;
-    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
-  } _OrthancPluginAccessDicomInstance;
-
-
-  /**
-   * @brief Get the AET of a DICOM instance.
-   *
-   * This function returns the Application Entity Title (AET) of the
-   * DICOM modality from which a DICOM instance originates.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The AET if success, NULL if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the size of a DICOM file.
-   *
-   * This function returns the number of bytes of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The size of the file, -1 in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    int64_t size;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &size;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return size;
-    }
-  }
-
-
-  /**
-   * @brief Get the data of a DICOM file.
-   *
-   * This function returns a pointer to the content of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The pointer to the DICOM data, NULL in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file.
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance. In contrast with
-   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
-   * its simplified version.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Check whether a DICOM instance is associated with some metadata.
-   *
-   * This function checks whether the DICOM instance of interest is
-   * associated with some metadata. As of Orthanc 0.8.1, in the
-   * callbacks registered by
-   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
-   * possibly available metadata are "ReceptionDate", "RemoteAET" and
-   * "IndexInSeries".
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    int64_t result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return (result != 0);
-    }
-  }
-
-
-  /**
-   * @brief Get the value of some metadata associated with a given DICOM instance.
-   *
-   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
-   * Before calling this function, the existence of the metadata must have been checked with
-   * ::OrthancPluginHasInstanceMetadata().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return The metadata value if success, NULL if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginStorageCreate  create;
-    OrthancPluginStorageRead    read;
-    OrthancPluginStorageRemove  remove;
-    OrthancPluginFree           free;
-  } _OrthancPluginRegisterStorageArea;
-
-  /**
-   * @brief Register a custom storage area.
-   *
-   * This function registers a custom storage area, to replace the
-   * built-in way Orthanc stores its files on the filesystem. This
-   * function must be called during the initialization of the plugin,
-   * i.e. inside the OrthancPluginInitialize() public function.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param create The callback function to store a file on the custom storage area.
-   * @param read The callback function to read a file from the custom storage area.
-   * @param remove The callback function to remove a file from the custom storage area.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
-    OrthancPluginContext*       context,
-    OrthancPluginStorageCreate  create,
-    OrthancPluginStorageRead    read,
-    OrthancPluginStorageRemove  remove)
-  {
-    _OrthancPluginRegisterStorageArea params;
-    params.create = create;
-    params.read = read;
-    params.remove = remove;
-
-#ifdef  __cplusplus
-    params.free = ::free;
-#else
-    params.free = free;
-#endif
-
-    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
-  }
-
-
-
-  /**
-   * @brief Return the path to the Orthanc executable.
-   *
-   * This function returns the path to the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the directory containing the Orthanc.
-   *
-   * This function returns the path to the directory containing the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the path to the configuration file(s).
-   *
-   * This function returns the path to the configuration file(s) that
-   * was specified when starting Orthanc. Since version 0.9.1, this
-   * path can refer to a folder that stores a set of configuration
-   * files. This function is deprecated in favor of
-   * OrthancPluginGetConfiguration().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   * @see OrthancPluginGetConfiguration()
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnChangeCallback callback;
-  } _OrthancPluginOnChangeCallback;
-
-  /**
-   * @brief Register a callback to monitor changes.
-   *
-   * This function registers a callback function that is called
-   * whenever a change happens to some DICOM resource.
-   *
-   * @warning If your change callback has to call the REST API of
-   * Orthanc, you should make these calls in a separate thread (with
-   * the events passing through a message queue). Otherwise, this
-   * could result in deadlocks in the presence of other plugins or Lua
-   * script.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginOnChangeCallback  callback)
-  {
-    _OrthancPluginOnChangeCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    const char* plugin;
-    _OrthancPluginProperty property;
-    const char* value;
-  } _OrthancPluginSetPluginProperty;
-
-
-  /**
-   * @brief Set the URI where the plugin provides its Web interface.
-   *
-   * For plugins that come with a Web interface, this function
-   * declares the entry path where to find this interface. This
-   * information is notably used in the "Plugins" page of Orthanc
-   * Explorer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The root URI for this plugin.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
-    OrthancPluginContext*  context,
-    const char*            uri)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_RootUri;
-    params.value = uri;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  /**
-   * @brief Set a description for this plugin.
-   *
-   * Set a description for this plugin. It is displayed in the
-   * "Plugins" page of Orthanc Explorer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param description The description.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
-    OrthancPluginContext*  context,
-    const char*            description)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_Description;
-    params.value = description;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  /**
-   * @brief Extend the JavaScript code of Orthanc Explorer.
-   *
-   * Add JavaScript code to customize the default behavior of Orthanc
-   * Explorer. This can for instance be used to add new buttons.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param javascript The custom JavaScript code.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
-    OrthancPluginContext*  context,
-    const char*            javascript)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_OrthancExplorer;
-    params.value = javascript;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  typedef struct
-  {
-    char**       result;
-    int32_t      property;
-    const char*  value;
-  } _OrthancPluginGlobalProperty;
-
-
-  /**
-   * @brief Get the value of a global property.
-   *
-   * Get the value of a global property that is stored in the Orthanc database. Global
-   * properties whose index is below 1024 are reserved by Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param property The global property of interest.
-   * @param defaultValue The value to return, if the global property is unset.
-   * @return The value of the global property, or NULL in the case of an error. This
-   * string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
-    OrthancPluginContext*  context,
-    int32_t                property,
-    const char*            defaultValue)
-  {
-    char* result;
-
-    _OrthancPluginGlobalProperty params;
-    params.result = &result;
-    params.property = property;
-    params.value = defaultValue;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Set the value of a global property.
-   *
-   * Set the value of a global property into the Orthanc
-   * database. Setting a global property can be used by plugins to
-   * save their internal parameters. Plugins are only allowed to set
-   * properties whose index are above or equal to 1024 (properties
-   * below 1024 are read-only and reserved by Orthanc).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param property The global property of interest.
-   * @param value The value to be set in the global property.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
-    OrthancPluginContext*  context,
-    int32_t                property,
-    const char*            value)
-  {
-    _OrthancPluginGlobalProperty params;
-    params.result = NULL;
-    params.property = property;
-    params.value = value;
-
-    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
-  }
-
-
-
-  typedef struct
-  {
-    int32_t   *resultInt32;
-    uint32_t  *resultUint32;
-    int64_t   *resultInt64;
-    uint64_t  *resultUint64;
-  } _OrthancPluginReturnSingleValue;
-
-  /**
-   * @brief Get the number of command-line arguments.
-   *
-   * Retrieve the number of command-line arguments that were used to launch Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The number of arguments.
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-  /**
-   * @brief Get the value of a command-line argument.
-   *
-   * Get the value of one of the command-line arguments that were used
-   * to launch Orthanc. The number of available arguments can be
-   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param argument The index of the argument.
-   * @return The value of the argument, or NULL in the case of an error. This
-   * string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
-    OrthancPluginContext*  context,
-    uint32_t               argument)
-  {
-    char* result;
-
-    _OrthancPluginGlobalProperty params;
-    params.result = &result;
-    params.property = (int32_t) argument;
-    params.value = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the expected version of the database schema.
-   *
-   * Retrieve the expected version of the database schema.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The version.
-   * @ingroup Callbacks
-   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the content of the configuration file(s).
-   *
-   * This function returns the content of the configuration that is
-   * used by Orthanc, formatted as a JSON string.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the configuration. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              subType;
-    const char*              contentType;
-  } _OrthancPluginStartMultipartAnswer;
-
-  /**
-   * @brief Start an HTTP multipart answer.
-   *
-   * Initiates a HTTP multipart answer, as the result of a REST request.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param subType The sub-type of the multipart answer ("mixed" or "related").
-   * @param contentType The MIME type of the items in the multipart answer.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginSendMultipartItem()
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              subType,
-    const char*              contentType)
-  {
-    _OrthancPluginStartMultipartAnswer params;
-    params.output = output;
-    params.subType = subType;
-    params.contentType = contentType;
-    return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
-  }
-
-
-  /**
-   * @brief Send an item as a part of some HTTP multipart answer.
-   *
-   * This function sends an item as a part of some HTTP multipart
-   * answer that was initiated by OrthancPluginStartMultipartAnswer().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param answer Pointer to the memory buffer containing the item.
-   * @param answerSize Number of bytes of the item.
-   * @return 0 if success, or the error code if failure (this notably happens
-   * if the connection is closed by the client).
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              answer,
-    uint32_t                 answerSize)
-  {
-    _OrthancPluginAnswerBuffer params;
-    params.output = output;
-    params.answer = answer;
-    params.answerSize = answerSize;
-    params.mimeType = NULL;
-    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*    target;
-    const void*                   source;
-    uint32_t                      size;
-    OrthancPluginCompressionType  compression;
-    uint8_t                       uncompress;
-  } _OrthancPluginBufferCompression;
-
-
-  /**
-   * @brief Compress or decompress a buffer.
-   *
-   * This function compresses or decompresses a buffer, using the
-   * version of the zlib library that is used by the Orthanc core.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param source The source buffer.
-   * @param size The size in bytes of the source buffer.
-   * @param compression The compression algorithm.
-   * @param uncompress If set to "0", the buffer must be compressed. 
-   * If set to "1", the buffer must be uncompressed.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
-    OrthancPluginContext*         context,
-    OrthancPluginMemoryBuffer*    target,
-    const void*                   source,
-    uint32_t                      size,
-    OrthancPluginCompressionType  compression,
-    uint8_t                       uncompress)
-  {
-    _OrthancPluginBufferCompression params;
-    params.target = target;
-    params.source = source;
-    params.size = size;
-    params.compression = compression;
-    params.uncompress = uncompress;
-
-    return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 path;
-  } _OrthancPluginReadFile;
-
-  /**
-   * @brief Read a file.
-   * 
-   * Read the content of a file on the filesystem, and returns it into
-   * a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param path The path of the file to be read.
-   * @return 0 if success, or the error code if failure.
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReadFile(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 path)
-  {
-    _OrthancPluginReadFile params;
-    params.target = target;
-    params.path = path;
-    return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
-  }
-
-
-
-  typedef struct
-  {
-    const char*  path;
-    const void*  data;
-    uint32_t     size;
-  } _OrthancPluginWriteFile;
-
-  /**
-   * @brief Write a file.
-   * 
-   * Write the content of a memory buffer to the filesystem.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param path The path of the file to be written.
-   * @param data The content of the memory buffer.
-   * @param size The size of the memory buffer.
-   * @return 0 if success, or the error code if failure.
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWriteFile(
-    OrthancPluginContext*  context,
-    const char*            path,
-    const void*            data,
-    uint32_t               size)
-  {
-    _OrthancPluginWriteFile params;
-    params.path = path;
-    params.data = data;
-    params.size = size;
-    return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
-  }
-
-
-
-  typedef struct
-  {
-    const char**            target;
-    OrthancPluginErrorCode  error;
-  } _OrthancPluginGetErrorDescription;
-
-  /**
-   * @brief Get the description of a given error code.
-   *
-   * This function returns the description of a given error code.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param error The error code of interest.
-   * @return The error description. This is a statically-allocated
-   * string, do not free it.
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
-    OrthancPluginContext*    context,
-    OrthancPluginErrorCode   error)
-  {
-    const char* result = NULL;
-
-    _OrthancPluginGetErrorDescription params;
-    params.target = &result;
-    params.error = error;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
-        result == NULL)
-    {
-      return "Unknown error code";
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    uint16_t                 status;
-    const char*              body;
-    uint32_t                 bodySize;
-  } _OrthancPluginSendHttpStatus;
-
-  /**
-   * @brief Send a HTTP status, with a custom body.
-   *
-   * This function answers to a HTTP request by sending a HTTP status
-   * code (such as "400 - Bad Request"), together with a body
-   * describing the error. The body will only be returned if the
-   * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
-   * 
-   * Note that:
-   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
-   * - Redirections (status 301) must use ::OrthancPluginRedirect().
-   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
-   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param status The HTTP status code to be sent.
-   * @param body The body of the answer.
-   * @param bodySize The size of the body.
-   * @see OrthancPluginSendHttpStatusCode()
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    uint16_t                 status,
-    const char*              body,
-    uint32_t                 bodySize)
-  {
-    _OrthancPluginSendHttpStatus params;
-    params.output = output;
-    params.status = status;
-    params.body = body;
-    params.bodySize = bodySize;
-    context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
-  }
-
-
-
-  typedef struct
-  {
-    const OrthancPluginImage*  image;
-    uint32_t*                  resultUint32;
-    OrthancPluginPixelFormat*  resultPixelFormat;
-    void**                     resultBuffer;
-  } _OrthancPluginGetImageInfo;
-
-
-  /**
-   * @brief Return the pixel format of an image.
-   *
-   * This function returns the type of memory layout for the pixels of the given image.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image of interest.
-   * @return The pixel format.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat  OrthancPluginGetImagePixelFormat(
-    OrthancPluginContext*      context,
-    const OrthancPluginImage*  image)
-  {
-    OrthancPluginPixelFormat target;
-    
-    _OrthancPluginGetImageInfo params;
-    memset(&params, 0, sizeof(params));
-    params.image = image;
-    params.resultPixelFormat = &target;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
-    {
-      return OrthancPluginPixelFormat_Unknown;
-    }
-    else
-    {
-      return (OrthancPluginPixelFormat) target;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the width of an image.
-   *
-   * This function returns the width of the given image.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image of interest.
-   * @return The width.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageWidth(
-    OrthancPluginContext*      context,
-    const OrthancPluginImage*  image)
-  {
-    uint32_t width;
-    
-    _OrthancPluginGetImageInfo params;
-    memset(&params, 0, sizeof(params));
-    params.image = image;
-    params.resultUint32 = &width;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
-    {
-      return 0;
-    }
-    else
-    {
-      return width;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the height of an image.
-   *
-   * This function returns the height of the given image.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image of interest.
-   * @return The height.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageHeight(
-    OrthancPluginContext*      context,
-    const OrthancPluginImage*  image)
-  {
-    uint32_t height;
-    
-    _OrthancPluginGetImageInfo params;
-    memset(&params, 0, sizeof(params));
-    params.image = image;
-    params.resultUint32 = &height;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
-    {
-      return 0;
-    }
-    else
-    {
-      return height;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the pitch of an image.
-   *
-   * This function returns the pitch of the given image. The pitch is
-   * defined as the number of bytes between 2 successive lines of the
-   * image in the memory buffer.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image of interest.
-   * @return The pitch.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImagePitch(
-    OrthancPluginContext*      context,
-    const OrthancPluginImage*  image)
-  {
-    uint32_t pitch;
-    
-    _OrthancPluginGetImageInfo params;
-    memset(&params, 0, sizeof(params));
-    params.image = image;
-    params.resultUint32 = &pitch;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
-    {
-      return 0;
-    }
-    else
-    {
-      return pitch;
-    }
-  }
-
-
-
-  /**
-   * @brief Return a pointer to the content of an image.
-   *
-   * This function returns a pointer to the memory buffer that
-   * contains the pixels of the image.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image of interest.
-   * @return The pointer.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
-    OrthancPluginContext*      context,
-    const OrthancPluginImage*  image)
-  {
-    void* target = NULL;
-
-    _OrthancPluginGetImageInfo params;
-    memset(&params, 0, sizeof(params));
-    params.resultBuffer = &target;
-    params.image = image;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginImage**       target;
-    const void*                data;
-    uint32_t                   size;
-    OrthancPluginImageFormat   format;
-  } _OrthancPluginUncompressImage;
-
-
-  /**
-   * @brief Decode a compressed image.
-   *
-   * This function decodes a compressed image from a memory buffer.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param data Pointer to a memory buffer containing the compressed image.
-   * @param size Size of the memory buffer containing the compressed image.
-   * @param format The file format of the compressed image.
-   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
-    OrthancPluginContext*      context,
-    const void*                data,
-    uint32_t                   size,
-    OrthancPluginImageFormat   format)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginUncompressImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.data = data;
-    params.size = size;
-    params.format = format;
-
-    if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-
-  typedef struct
-  {
-    OrthancPluginImage*   image;
-  } _OrthancPluginFreeImage;
-
-  /**
-   * @brief Free an image.
-   *
-   * This function frees an image that was decoded with OrthancPluginUncompressImage().
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeImage(
-    OrthancPluginContext* context, 
-    OrthancPluginImage*   image)
-  {
-    _OrthancPluginFreeImage params;
-    params.image = image;
-
-    context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
-  }
-
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer* target;
-    OrthancPluginImageFormat   imageFormat;
-    OrthancPluginPixelFormat   pixelFormat;
-    uint32_t                   width;
-    uint32_t                   height;
-    uint32_t                   pitch;
-    const void*                buffer;
-    uint8_t                    quality;
-  } _OrthancPluginCompressImage;
-
-
-  /**
-   * @brief Encode a PNG image.
-   *
-   * This function compresses the given memory buffer containing an
-   * image using the PNG specification, and stores the result of the
-   * compression into a newly allocated memory buffer.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer containing the uncompressed image.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginCompressAndAnswerPngImage()
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
-    OrthancPluginContext*         context,
-    OrthancPluginMemoryBuffer*    target,
-    OrthancPluginPixelFormat      format,
-    uint32_t                      width,
-    uint32_t                      height,
-    uint32_t                      pitch,
-    const void*                   buffer)
-  {
-    _OrthancPluginCompressImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = target;
-    params.imageFormat = OrthancPluginImageFormat_Png;
-    params.pixelFormat = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    params.quality = 0;  /* Unused for PNG */
-
-    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
-  }
-
-
-  /**
-   * @brief Encode a JPEG image.
-   *
-   * This function compresses the given memory buffer containing an
-   * image using the JPEG specification, and stores the result of the
-   * compression into a newly allocated memory buffer.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer containing the uncompressed image.
-   * @param quality The quality of the JPEG encoding, between 1 (worst
-   * quality, best compression) and 100 (best quality, worst
-   * compression).
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
-    OrthancPluginContext*         context,
-    OrthancPluginMemoryBuffer*    target,
-    OrthancPluginPixelFormat      format,
-    uint32_t                      width,
-    uint32_t                      height,
-    uint32_t                      pitch,
-    const void*                   buffer,
-    uint8_t                       quality)
-  {
-    _OrthancPluginCompressImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = target;
-    params.imageFormat = OrthancPluginImageFormat_Jpeg;
-    params.pixelFormat = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    params.quality = quality;
-
-    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
-  }
-
-
-
-  /**
-   * @brief Answer to a REST request with a JPEG image.
-   *
-   * This function answers to a REST request with a JPEG image. The
-   * parameters of this function describe a memory buffer that
-   * contains an uncompressed image. The image will be automatically compressed
-   * as a JPEG image by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer containing the uncompressed image.
-   * @param quality The quality of the JPEG encoding, between 1 (worst
-   * quality, best compression) and 100 (best quality, worst
-   * compression).
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
-    OrthancPluginContext*     context,
-    OrthancPluginRestOutput*  output,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    const void*               buffer,
-    uint8_t                   quality)
-  {
-    _OrthancPluginCompressAndAnswerImage params;
-    params.output = output;
-    params.imageFormat = OrthancPluginImageFormat_Jpeg;
-    params.pixelFormat = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    params.quality = quality;
-    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
-  }
-
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    OrthancPluginHttpMethod     method;
-    const char*                 url;
-    const char*                 username;
-    const char*                 password;
-    const char*                 body;
-    uint32_t                    bodySize;
-  } _OrthancPluginCallHttpClient;
-
-
-  /**
-   * @brief Issue a HTTP GET call.
-   * 
-   * Make a HTTP GET call to the given URL. The result to the query is
-   * stored into a newly allocated memory buffer. Favor
-   * OrthancPluginRestApiGet() if calling the built-in REST API of the
-   * Orthanc instance that hosts this plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param url The URL of interest.
-   * @param username The username (can be <tt>NULL</tt> if no password protection).
-   * @param password The password (can be <tt>NULL</tt> if no password protection).
-   * @return 0 if success, or the error code if failure.
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpGet(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 url,
-    const char*                 username,
-    const char*                 password)
-  {
-    _OrthancPluginCallHttpClient params;
-    memset(&params, 0, sizeof(params));
-
-    params.target = target;
-    params.method = OrthancPluginHttpMethod_Get;
-    params.url = url;
-    params.username = username;
-    params.password = password;
-
-    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
-  }
-
-
-  /**
-   * @brief Issue a HTTP POST call.
-   * 
-   * Make a HTTP POST call to the given URL. The result to the query
-   * is stored into a newly allocated memory buffer. Favor
-   * OrthancPluginRestApiPost() if calling the built-in REST API of
-   * the Orthanc instance that hosts this plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param url The URL of interest.
-   * @param body The content of the body of the request.
-   * @param bodySize The size of the body of the request.
-   * @param username The username (can be <tt>NULL</tt> if no password protection).
-   * @param password The password (can be <tt>NULL</tt> if no password protection).
-   * @return 0 if success, or the error code if failure.
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 url,
-    const char*                 body,
-    uint32_t                    bodySize,
-    const char*                 username,
-    const char*                 password)
-  {
-    _OrthancPluginCallHttpClient params;
-    memset(&params, 0, sizeof(params));
-
-    params.target = target;
-    params.method = OrthancPluginHttpMethod_Post;
-    params.url = url;
-    params.body = body;
-    params.bodySize = bodySize;
-    params.username = username;
-    params.password = password;
-
-    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
-  }
-
-
-  /**
-   * @brief Issue a HTTP PUT call.
-   * 
-   * Make a HTTP PUT call to the given URL. The result to the query is
-   * stored into a newly allocated memory buffer. Favor
-   * OrthancPluginRestApiPut() if calling the built-in REST API of the
-   * Orthanc instance that hosts this plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param url The URL of interest.
-   * @param body The content of the body of the request.
-   * @param bodySize The size of the body of the request.
-   * @param username The username (can be <tt>NULL</tt> if no password protection).
-   * @param password The password (can be <tt>NULL</tt> if no password protection).
-   * @return 0 if success, or the error code if failure.
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 url,
-    const char*                 body,
-    uint32_t                    bodySize,
-    const char*                 username,
-    const char*                 password)
-  {
-    _OrthancPluginCallHttpClient params;
-    memset(&params, 0, sizeof(params));
-
-    params.target = target;
-    params.method = OrthancPluginHttpMethod_Put;
-    params.url = url;
-    params.body = body;
-    params.bodySize = bodySize;
-    params.username = username;
-    params.password = password;
-
-    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
-  }
-
-
-  /**
-   * @brief Issue a HTTP DELETE call.
-   * 
-   * Make a HTTP DELETE call to the given URL. Favor
-   * OrthancPluginRestApiDelete() if calling the built-in REST API of
-   * the Orthanc instance that hosts this plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param url The URL of interest.
-   * @param username The username (can be <tt>NULL</tt> if no password protection).
-   * @param password The password (can be <tt>NULL</tt> if no password protection).
-   * @return 0 if success, or the error code if failure.
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpDelete(
-    OrthancPluginContext*       context,
-    const char*                 url,
-    const char*                 username,
-    const char*                 password)
-  {
-    _OrthancPluginCallHttpClient params;
-    memset(&params, 0, sizeof(params));
-
-    params.method = OrthancPluginHttpMethod_Delete;
-    params.url = url;
-    params.username = username;
-    params.password = password;
-
-    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginImage**       target;
-    const OrthancPluginImage*  source;
-    OrthancPluginPixelFormat   targetFormat;
-  } _OrthancPluginConvertPixelFormat;
-
-
-  /**
-   * @brief Change the pixel format of an image.
-   *
-   * This function creates a new image, changing the memory layout of the pixels.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param source The source image.
-   * @param targetFormat The target pixel format.
-   * @return The resulting image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
-    OrthancPluginContext*      context,
-    const OrthancPluginImage*  source,
-    OrthancPluginPixelFormat   targetFormat)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginConvertPixelFormat params;
-    params.target = &target;
-    params.source = source;
-    params.targetFormat = targetFormat;
-
-    if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the number of available fonts.
-   *
-   * This function returns the number of fonts that are built in the
-   * Orthanc core. These fonts can be used to draw texts on images
-   * through OrthancPluginDrawText().
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The number of fonts.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-
-  typedef struct
-  {
-    uint32_t      fontIndex; /* in */
-    const char**  name; /* out */
-    uint32_t*     size; /* out */
-  } _OrthancPluginGetFontInfo;
-
-  /**
-   * @brief Return the name of a font.
-   *
-   * This function returns the name of a font that is built in the Orthanc core.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
-   * @return The font name. This is a statically-allocated string, do not free it.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
-    OrthancPluginContext*  context,
-    uint32_t               fontIndex)
-  {
-    const char* result = NULL;
-
-    _OrthancPluginGetFontInfo params;
-    memset(&params, 0, sizeof(params));
-    params.name = &result;
-    params.fontIndex = fontIndex;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the size of a font.
-   *
-   * This function returns the size of a font that is built in the Orthanc core.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
-   * @return The font size.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
-    OrthancPluginContext*  context,
-    uint32_t               fontIndex)
-  {
-    uint32_t result;
-
-    _OrthancPluginGetFontInfo params;
-    memset(&params, 0, sizeof(params));
-    params.size = &result;
-    params.fontIndex = fontIndex;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
-    {
-      return 0;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginImage*   image;
-    uint32_t              fontIndex;
-    const char*           utf8Text;
-    int32_t               x;
-    int32_t               y;
-    uint8_t               r;
-    uint8_t               g;
-    uint8_t               b;
-  } _OrthancPluginDrawText;
-
-
-  /**
-   * @brief Draw text on an image.
-   *
-   * This function draws some text on some image.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param image The image upon which to draw the text.
-   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
-   * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
-   * @param x The X position of the text over the image.
-   * @param y The Y position of the text over the image.
-   * @param r The value of the red color channel of the text.
-   * @param g The value of the green color channel of the text.
-   * @param b The value of the blue color channel of the text.
-   * @return 0 if success, other value if error.
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginDrawText(
-    OrthancPluginContext*  context,
-    OrthancPluginImage*    image,
-    uint32_t               fontIndex,
-    const char*            utf8Text,
-    int32_t                x,
-    int32_t                y,
-    uint8_t                r,
-    uint8_t                g,
-    uint8_t                b)
-  {
-    _OrthancPluginDrawText params;
-    memset(&params, 0, sizeof(params));
-    params.image = image;
-    params.fontIndex = fontIndex;
-    params.utf8Text = utf8Text;
-    params.x = x;
-    params.y = y;
-    params.r = r;
-    params.g = g;
-    params.b = b;
-
-    return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginStorageArea*   storageArea;
-    const char*                 uuid;
-    const void*                 content;
-    uint64_t                    size;
-    OrthancPluginContentType    type;
-  } _OrthancPluginStorageAreaCreate;
-
-
-  /**
-   * @brief Create a file inside the storage area.
-   *
-   * This function creates a new file inside the storage area that is
-   * currently used by Orthanc.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param storageArea The storage area.
-   * @param uuid The identifier of the file to be created.
-   * @param content The content to store in the newly created file.
-   * @param size The size of the content.
-   * @param type The type of the file content.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
-    OrthancPluginContext*       context,
-    OrthancPluginStorageArea*   storageArea,
-    const char*                 uuid,
-    const void*                 content,
-    uint64_t                    size,
-    OrthancPluginContentType    type)
-  {
-    _OrthancPluginStorageAreaCreate params;
-    params.storageArea = storageArea;
-    params.uuid = uuid;
-    params.content = content;
-    params.size = size;
-    params.type = type;
-
-    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    OrthancPluginStorageArea*   storageArea;
-    const char*                 uuid;
-    OrthancPluginContentType    type;
-  } _OrthancPluginStorageAreaRead;
-
-
-  /**
-   * @brief Read a file from the storage area.
-   *
-   * This function reads the content of a given file from the storage
-   * area that is currently used by Orthanc.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param storageArea The storage area.
-   * @param uuid The identifier of the file to be read.
-   * @param type The type of the file content.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    OrthancPluginStorageArea*   storageArea,
-    const char*                 uuid,
-    OrthancPluginContentType    type)
-  {
-    _OrthancPluginStorageAreaRead params;
-    params.target = target;
-    params.storageArea = storageArea;
-    params.uuid = uuid;
-    params.type = type;
-
-    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginStorageArea*   storageArea;
-    const char*                 uuid;
-    OrthancPluginContentType    type;
-  } _OrthancPluginStorageAreaRemove;
-
-  /**
-   * @brief Remove a file from the storage area.
-   *
-   * This function removes a given file from the storage area that is
-   * currently used by Orthanc.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param storageArea The storage area.
-   * @param uuid The identifier of the file to be removed.
-   * @param type The type of the file content.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
-    OrthancPluginContext*       context,
-    OrthancPluginStorageArea*   storageArea,
-    const char*                 uuid,
-    OrthancPluginContentType    type)
-  {
-    _OrthancPluginStorageAreaRemove params;
-    params.storageArea = storageArea;
-    params.uuid = uuid;
-    params.type = type;
-
-    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginErrorCode*  target;
-    int32_t                  code;
-    uint16_t                 httpStatus;
-    const char*              message;
-  } _OrthancPluginRegisterErrorCode;
-  
-  /**
-   * @brief Declare a custom error code for this plugin.
-   *
-   * This function declares a custom error code that can be generated
-   * by this plugin. This declaration is used to enrich the body of
-   * the HTTP answer in the case of an error, and to set the proper
-   * HTTP status code.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param code The error code that is internal to this plugin.
-   * @param httpStatus The HTTP status corresponding to this error.
-   * @param message The description of the error.
-   * @return The error code that has been assigned inside the Orthanc core.
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
-    OrthancPluginContext*    context,
-    int32_t                  code,
-    uint16_t                 httpStatus,
-    const char*              message)
-  {
-    OrthancPluginErrorCode target;
-
-    _OrthancPluginRegisterErrorCode params;
-    params.target = &target;
-    params.code = code;
-    params.httpStatus = httpStatus;
-    params.message = message;
-
-    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
-    {
-      return target;
-    }
-    else
-    {
-      /* There was an error while assigned the error. Use a generic code. */
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    uint16_t                          group;
-    uint16_t                          element;
-    OrthancPluginValueRepresentation  vr;
-    const char*                       name;
-    uint32_t                          minMultiplicity;
-    uint32_t                          maxMultiplicity;
-  } _OrthancPluginRegisterDictionaryTag;
-  
-  /**
-   * @brief Register a new tag into the DICOM dictionary.
-   *
-   * This function declares a new tag in the dictionary of DICOM tags
-   * that are known to Orthanc. This function should be used in the
-   * OrthancPluginInitialize() callback.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param group The group of the tag.
-   * @param element The element of the tag.
-   * @param vr The value representation of the tag.
-   * @param name The nickname of the tag.
-   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
-   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
-   * an arbitrary multiplicity ("<tt>n</tt>").
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
-    OrthancPluginContext*             context,
-    uint16_t                          group,
-    uint16_t                          element,
-    OrthancPluginValueRepresentation  vr,
-    const char*                       name,
-    uint32_t                          minMultiplicity,
-    uint32_t                          maxMultiplicity)
-  {
-    _OrthancPluginRegisterDictionaryTag params;
-    params.group = group;
-    params.element = element;
-    params.vr = vr;
-    params.name = name;
-    params.minMultiplicity = minMultiplicity;
-    params.maxMultiplicity = maxMultiplicity;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
-  }
-
-
-
-
-  typedef struct
-  {
-    OrthancPluginStorageArea*  storageArea;
-    OrthancPluginResourceType  level;
-  } _OrthancPluginReconstructMainDicomTags;
-
-  /**
-   * @brief Reconstruct the main DICOM tags.
-   *
-   * This function requests the Orthanc core to reconstruct the main
-   * DICOM tags of all the resources of the given type. This function
-   * can only be used as a part of the upgrade of a custom database
-   * back-end
-   * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A
-   * database transaction will be automatically setup.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param storageArea The storage area.
-   * @param level The type of the resources of interest.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
-    OrthancPluginContext*      context,
-    OrthancPluginStorageArea*  storageArea,
-    OrthancPluginResourceType  level)
-  {
-    _OrthancPluginReconstructMainDicomTags params;
-    params.level = level;
-    params.storageArea = storageArea;
-
-    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
-  }
-
-
-  typedef struct
-  {
-    char**                          result;
-    const char*                     instanceId;
-    const char*                     buffer;
-    uint32_t                        size;
-    OrthancPluginDicomToJsonFormat  format;
-    OrthancPluginDicomToJsonFlags   flags;
-    uint32_t                        maxStringLength;
-  } _OrthancPluginDicomToJson;
-
-
-  /**
-   * @brief Format a DICOM memory buffer as a JSON string.
-   *
-   * This function takes as input a memory buffer containing a DICOM
-   * file, and outputs a JSON string representing the tags of this
-   * DICOM file.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer containing the DICOM file.
-   * @param size The size of the memory buffer.
-   * @param format The output format.
-   * @param flags Flags governing the output.
-   * @param maxStringLength The maximum length of a field. Too long fields will
-   * be output as "null". The 0 value means no maximum length.
-   * @return The NULL value if the case of an error, or the JSON
-   * string. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
-    OrthancPluginContext*           context,
-    const char*                     buffer,
-    uint32_t                        size,
-    OrthancPluginDicomToJsonFormat  format,
-    OrthancPluginDicomToJsonFlags   flags, 
-    uint32_t                        maxStringLength)
-  {
-    char* result;
-
-    _OrthancPluginDicomToJson params;
-    memset(&params, 0, sizeof(params));
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-    params.format = format;
-    params.flags = flags;
-    params.maxStringLength = maxStringLength;
-
-    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Format a DICOM instance as a JSON string.
-   *
-   * This function formats a DICOM instance that is stored in Orthanc,
-   * and outputs a JSON string representing the tags of this DICOM
-   * instance.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instanceId The Orthanc identifier of the instance.
-   * @param format The output format.
-   * @param flags Flags governing the output.
-   * @param maxStringLength The maximum length of a field. Too long fields will
-   * be output as "null". The 0 value means no maximum length.
-   * @return The NULL value if the case of an error, or the JSON
-   * string. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
-    OrthancPluginContext*           context,
-    const char*                     instanceId,
-    OrthancPluginDicomToJsonFormat  format,
-    OrthancPluginDicomToJsonFlags   flags, 
-    uint32_t                        maxStringLength)
-  {
-    char* result;
-
-    _OrthancPluginDicomToJson params;
-    memset(&params, 0, sizeof(params));
-    params.result = &result;
-    params.instanceId = instanceId;
-    params.format = format;
-    params.flags = flags;
-    params.maxStringLength = maxStringLength;
-
-    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    uint32_t                    headersCount;
-    const char* const*          headersKeys;
-    const char* const*          headersValues;
-    int32_t                     afterPlugins;
-  } _OrthancPluginRestApiGet2;
-
-  /**
-   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
-   * 
-   * Make a GET call to the Orthanc REST API with extended
-   * parameters. The result to the query is stored into a newly
-   * allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param headersCount The number of HTTP headers.
-   * @param headersKeys Array containing the keys of the HTTP headers.
-   * @param headersValues Array containing the values of the HTTP headers.
-   * @param afterPlugins If 0, the built-in API of Orthanc is used.
-   * If 1, the API is tainted by the plugins.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    uint32_t                    headersCount,
-    const char* const*          headersKeys,
-    const char* const*          headersValues,
-    int32_t                     afterPlugins)
-  {
-    _OrthancPluginRestApiGet2 params;
-    params.target = target;
-    params.uri = uri;
-    params.headersCount = headersCount;
-    params.headersKeys = headersKeys;
-    params.headersValues = headersValues;
-    params.afterPlugins = afterPlugins;
-
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginWorklistCallback callback;
-  } _OrthancPluginWorklistCallback;
-
-  /**
-   * @brief Register a callback to handle modality worklists requests.
-   *
-   * This function registers a callback to handle C-Find SCP requests
-   * on modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginWorklistCallback  callback)
-  {
-    _OrthancPluginWorklistCallback params;
-    params.callback = callback;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
-  }
-
-
-  
-  typedef struct
-  {
-    OrthancPluginWorklistAnswers*      answers;
-    const OrthancPluginWorklistQuery*  query;
-    const void*                        dicom;
-    uint32_t                           size;
-  } _OrthancPluginWorklistAnswersOperation;
-
-  /**
-   * @brief Add one answer to some modality worklist request.
-   *
-   * This function adds one worklist (encoded as a DICOM file) to the
-   * set of answers corresponding to some C-Find SCP request against
-   * modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param answers The set of answers.
-   * @param query The worklist query, as received by the callback.
-   * @param dicom The worklist to answer, encoded as a DICOM file.
-   * @param size The size of the DICOM file.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
-    OrthancPluginContext*             context,
-    OrthancPluginWorklistAnswers*     answers,
-    const OrthancPluginWorklistQuery* query,
-    const void*                       dicom,
-    uint32_t                          size)
-  {
-    _OrthancPluginWorklistAnswersOperation params;
-    params.answers = answers;
-    params.query = query;
-    params.dicom = dicom;
-    params.size = size;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
-  }
-
-
-  /**
-   * @brief Mark the set of worklist answers as incomplete.
-   *
-   * This function marks as incomplete the set of answers
-   * corresponding to some C-Find SCP request against modality
-   * worklists. This must be used if canceling the handling of a
-   * request when too many answers are to be returned.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param answers The set of answers.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
-    OrthancPluginContext*          context,
-    OrthancPluginWorklistAnswers*  answers)
-  {
-    _OrthancPluginWorklistAnswersOperation params;
-    params.answers = answers;
-    params.query = NULL;
-    params.dicom = NULL;
-    params.size = 0;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
-  }
-
-
-  typedef struct
-  {
-    const OrthancPluginWorklistQuery*  query;
-    const void*                        dicom;
-    uint32_t                           size;
-    int32_t*                           isMatch;
-    OrthancPluginMemoryBuffer*         target;
-  } _OrthancPluginWorklistQueryOperation;
-
-  /**
-   * @brief Test whether a worklist matches the query.
-   *
-   * This function checks whether one worklist (encoded as a DICOM
-   * file) matches the C-Find SCP query against modality
-   * worklists. This function must be called before adding the
-   * worklist as an answer through OrthancPluginWorklistAddAnswer().
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param query The worklist query, as received by the callback.
-   * @param dicom The worklist to answer, encoded as a DICOM file.
-   * @param size The size of the DICOM file.
-   * @return 1 if the worklist matches the query, 0 otherwise.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
-    OrthancPluginContext*              context,
-    const OrthancPluginWorklistQuery*  query,
-    const void*                        dicom,
-    uint32_t                           size)
-  {
-    int32_t isMatch = 0;
-
-    _OrthancPluginWorklistQueryOperation params;
-    params.query = query;
-    params.dicom = dicom;
-    params.size = size;
-    params.isMatch = &isMatch;
-    params.target = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
-    {
-      return isMatch;
-    }
-    else
-    {
-      /* Error: Assume non-match */
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Retrieve the worklist query as a DICOM file.
-   *
-   * This function retrieves the DICOM file that underlies a C-Find
-   * SCP query against modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param query The worklist query, as received by the callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
-    OrthancPluginContext*              context,
-    OrthancPluginMemoryBuffer*         target,
-    const OrthancPluginWorklistQuery*  query)
-  {
-    _OrthancPluginWorklistQueryOperation params;
-    params.query = query;
-    params.dicom = NULL;
-    params.size = 0;
-    params.isMatch = NULL;
-    params.target = target;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
-  }
-
-
-  /**
-   * @brief Get the origin of a DICOM file.
-   *
-   * This function returns the origin of a DICOM instance that has been received by Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The origin of the instance.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    OrthancPluginInstanceOrigin origin;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultOrigin = &origin;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return OrthancPluginInstanceOrigin_Unknown;
-    }
-    else
-    {
-      return origin;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*     target;
-    const char*                    json;
-    const OrthancPluginImage*      pixelData;
-    OrthancPluginCreateDicomFlags  flags;
-  } _OrthancPluginCreateDicom;
-
-  /**
-   * @brief Create a DICOM instance from a JSON string and an image.
-   *
-   * This function takes as input a string containing a JSON file
-   * describing the content of a DICOM instance. As an output, it
-   * writes the corresponding DICOM instance to a newly allocated
-   * memory buffer. Additionally, an image to be encoded within the
-   * DICOM instance can also be provided.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param json The input JSON file.
-   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
-   * @param flags Flags governing the output.
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomBufferToJson
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
-    OrthancPluginContext*          context,
-    OrthancPluginMemoryBuffer*     target,
-    const char*                    json,
-    const OrthancPluginImage*      pixelData,
-    OrthancPluginCreateDicomFlags  flags)
-  {
-    _OrthancPluginCreateDicom params;
-    params.target = target;
-    params.json = json;
-    params.pixelData = pixelData;
-    params.flags = flags;
-
-    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginDecodeImageCallback callback;
-  } _OrthancPluginDecodeImageCallback;
-
-  /**
-   * @brief Register a callback to handle the decoding of DICOM images.
-   *
-   * This function registers a custom callback to the decoding of
-   * DICOM images, replacing the built-in decoder of Orthanc.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
-    OrthancPluginContext*             context,
-    OrthancPluginDecodeImageCallback  callback)
-  {
-    _OrthancPluginDecodeImageCallback params;
-    params.callback = callback;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
-  }
-  
-
-
-  typedef struct
-  {
-    OrthancPluginImage**       target;
-    OrthancPluginPixelFormat   format;
-    uint32_t                   width;
-    uint32_t                   height;
-    uint32_t                   pitch;
-    void*                      buffer;
-    const void*                constBuffer;
-    uint32_t                   bufferSize;
-    uint32_t                   frameIndex;
-  } _OrthancPluginCreateImage;
-
-
-  /**
-   * @brief Create an image.
-   *
-   * This function creates an image of given size and format.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param format The format of the pixels.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
-    OrthancPluginContext*     context,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-
-    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-  /**
-   * @brief Create an image pointing to a memory buffer.
-   *
-   * This function creates an image whose content points to a memory
-   * buffer managed by the plugin. Note that the buffer is directly
-   * accessed, no memory is allocated and no data is copied.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param format The format of the pixels.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer.
-   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
-    OrthancPluginContext*     context,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    void*                     buffer)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-
-    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  /**
-   * @brief Decode one frame from a DICOM instance.
-   *
-   * This function decodes one frame of a DICOM image that is stored
-   * in a memory buffer. This function will give the same result as
-   * OrthancPluginUncompressImage() for single-frame DICOM images.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer Pointer to a memory buffer containing the DICOM image.
-   * @param bufferSize Size of the memory buffer containing the DICOM image.
-   * @param frameIndex The index of the frame of interest in a multi-frame image.
-   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               bufferSize,
-    uint32_t               frameIndex)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.constBuffer = buffer;
-    params.bufferSize = bufferSize;
-    params.frameIndex = frameIndex;
-
-    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const void*  buffer;
-    uint32_t     size;
-  } _OrthancPluginComputeHash;
-
-  /**
-   * @brief Compute an MD5 hash.
-   *
-   * This functions computes the MD5 cryptographic hash of the given memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The source memory buffer.
-   * @param size The size in bytes of the source buffer.
-   * @return The NULL value in case of error, or a string containing the cryptographic hash.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               size)
-  {
-    char* result;
-
-    _OrthancPluginComputeHash params;
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-
-    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Compute a SHA-1 hash.
-   *
-   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The source memory buffer.
-   * @param size The size in bytes of the source buffer.
-   * @return The NULL value in case of error, or a string containing the cryptographic hash.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               size)
-  {
-    char* result;
-
-    _OrthancPluginComputeHash params;
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-
-    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginDictionaryEntry* target;
-    const char*                   name;
-  } _OrthancPluginLookupDictionary;
-
-  /**
-   * @brief Get information about the given DICOM tag.
-   *
-   * This functions makes a lookup in the dictionary of DICOM tags
-   * that are known to Orthanc, and returns information about this
-   * tag. The tag can be specified using its human-readable name
-   * (e.g. "PatientName") or a set of two hexadecimal numbers
-   * (e.g. "0010-0020").
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target Where to store the information about the tag.
-   * @param name The name of the DICOM tag.
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
-    OrthancPluginContext*          context,
-    OrthancPluginDictionaryEntry*  target,
-    const char*                    name)
-  {
-    _OrthancPluginLookupDictionary params;
-    params.target = target;
-    params.name = name;
-    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
-  }
-
-
-#ifdef  __cplusplus
-}
-#endif
-
-
-/** @} */
-
--- a/Plugin/ChunkedBuffer.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "ChunkedBuffer.h"
-
-#include <cassert>
-#include <string.h>
-
-
-namespace OrthancPlugins
-{
-  void ChunkedBuffer::Clear()
-  {
-    numBytes_ = 0;
-
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  void ChunkedBuffer::AddChunk(const char* chunkData,
-                               size_t chunkSize)
-  {
-    if (chunkSize == 0)
-    {
-      return;
-    }
-
-    assert(chunkData != NULL);
-    chunks_.push_back(new std::string(chunkData, chunkSize));
-    numBytes_ += chunkSize;
-  }
-
-
-  void ChunkedBuffer::AddChunk(const std::string& chunk)
-  {
-    if (chunk.size() > 0)
-    {
-      AddChunk(&chunk[0], chunk.size());
-    }
-  }
-
-
-  void ChunkedBuffer::Flatten(std::string& result)
-  {
-    result.resize(numBytes_);
-
-    size_t pos = 0;
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      assert(*it != NULL);
-
-      size_t s = (*it)->size();
-      if (s != 0)
-      {
-        memcpy(&result[pos], (*it)->c_str(), s);
-        pos += s;
-      }
-
-      delete *it;
-    }
-
-    chunks_.clear();
-  }
-}
--- a/Plugin/ChunkedBuffer.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 <list>
-#include <string>
-
-namespace OrthancPlugins
-{
-  class ChunkedBuffer
-  {
-  private:
-    typedef std::list<std::string*>  Chunks;
-    size_t numBytes_;
-    Chunks chunks_;
-  
-    void Clear();
-
-  public:
-    ChunkedBuffer() : numBytes_(0)
-    {
-    }
-
-    ~ChunkedBuffer()
-    {
-      Clear();
-    }
-
-    size_t GetNumBytes() const
-    {
-      return numBytes_;
-    }
-
-    void AddChunk(const char* chunkData,
-                  size_t chunkSize);
-
-    void AddChunk(const std::string& chunk);
-
-    void Flatten(std::string& result);
-  };
-}
--- a/Plugin/Configuration.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/Configuration.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -23,8 +24,12 @@
 #include <fstream>
 #include <json/reader.h>
 #include <boost/regex.hpp>
+#include <boost/lexical_cast.hpp>
 
-#include "../Orthanc/Core/Toolbox.h"
+#include "DicomWebServers.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Toolbox.h>
 
 namespace OrthancPlugins
 {
@@ -64,15 +69,15 @@
     Orthanc::Toolbox::StripSpaces(application);
     Orthanc::Toolbox::ToLowerCase(application);
 
-    boost::regex pattern("\\s*([^=]+)\\s*=\\s*([^=]+)\\s*");
-
+    boost::regex pattern("\\s*([^=]+)\\s*=\\s*(([^=\"]+)|\"([^=\"]+)\")\\s*");
+    
     for (size_t i = 1; i < tokens.size(); i++)
     {
       boost::cmatch what;
       if (boost::regex_match(tokens[i].c_str(), what, pattern))
       {
         std::string key(what[1]);
-        std::string value(what[2]);
+        std::string value(what.length(3) != 0 ? what[3] : what[4]);
         Orthanc::Toolbox::ToLowerCase(key);
         attributes[key] = value;
       }
@@ -80,232 +85,278 @@
   }
 
 
-  void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          const char* body,
-                          const uint64_t bodySize,
-                          const std::string& boundary)
-  {
-    result.clear();
+  static const boost::regex MULTIPART_HEADERS_ENDING("(.*?\r\n)\r\n(.*)");
+  static const boost::regex MULTIPART_HEADERS_LINE(".*?\r\n");
 
-    boost::regex header("\r?(\n?)--" + boundary + "(--|.*\r?\n\r?\n)");
-    boost::regex pattern(".*^Content-Type\\s*:\\s*([^\\s]*).*",
-                         boost::regex::icase /* case insensitive */);
-    
-    boost::cmatch what;
-    boost::match_flag_type flags = (boost::match_perl | 
-                                    boost::match_not_dot_null);
-    const char* start = body;
-    const char* end = body + bodySize;
-    std::string currentType;
+  static void ParseMultipartHeaders(bool& hasLength /* out */,
+                                    size_t& length /* out */,
+                                    std::string& contentType /* out */,
+                                    const char* startHeaders,
+                                    const char* endHeaders)
+  {
+    hasLength = false;
+    contentType = "application/octet-stream";
 
-    while (boost::regex_search(start, end, what, header, flags))   
+    // Loop over the HTTP headers of this multipart item
+    boost::cregex_token_iterator it(startHeaders, endHeaders, MULTIPART_HEADERS_LINE, 0);
+    boost::cregex_token_iterator iteratorEnd;
+
+    for (; it != iteratorEnd; ++it)
     {
-      if (start != body)
+      const std::string line(*it);
+      size_t colon = line.find(':');
+      size_t eol = line.find('\r');
+
+      if (colon != std::string::npos &&
+          eol != std::string::npos &&
+          colon < eol &&
+          eol + 2 == line.length())
       {
-        MultipartItem item;
-        item.data_ = start;
-        item.size_ = what[0].first - start;
-        item.contentType_ = currentType;
+        std::string key = Orthanc::Toolbox::StripSpaces(line.substr(0, colon));
+        Orthanc::Toolbox::ToLowerCase(key);
 
-        result.push_back(item);
-      }
+        const std::string value = Orthanc::Toolbox::StripSpaces(line.substr(colon + 1, eol - colon - 1));
 
-      boost::cmatch contentType;
-      if (boost::regex_match(what[0].first, what[0].second, contentType, pattern))
-      {
-        currentType = contentType[1];
+        if (key == "content-length")
+        {
+          try
+          {
+            int tmp = boost::lexical_cast<int>(value);
+            if (tmp >= 0)
+            {
+              hasLength = true;
+              length = tmp;
+            }
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            LogWarning("Unable to parse the Content-Length of a multipart item");
+          }
+        }
+        else if (key == "content-type")
+        {
+          contentType = value;
+        }
       }
-      else
-      {
-        currentType.clear();
-      }
-    
-      start = what[0].second;
-      flags |= boost::match_prev_avail;
     }
   }
 
 
-  bool RestApiGetString(std::string& result,
-                        OrthancPluginContext* context,
-                        const std::string& uri,
-                        bool applyPlugins)
+  static const char* ParseMultipartItem(std::vector<MultipartItem>& result,
+                                        const char* start,
+                                        const char* end,
+                                        const boost::regex& nextSeparator)
   {
-    OrthancPluginMemoryBuffer buffer;
-    int code;
+    // Just before "start", it is guaranteed that "--[BOUNDARY]\r\n" is present
+
+    boost::cmatch what;
+    if (!boost::regex_match(start, end, what, MULTIPART_HEADERS_ENDING, boost::match_perl))
+    {
+      // Cannot find the HTTP headers of this multipart item
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    // Some aliases for more clarity
+    assert(what[1].first == start);
+    const char* startHeaders = what[1].first;
+    const char* endHeaders = what[1].second;
+    const char* startBody = what[2].first;
 
-    if (applyPlugins)
+    bool hasLength;
+    size_t length;
+    std::string contentType;
+    ParseMultipartHeaders(hasLength, length, contentType, startHeaders, endHeaders);
+
+    boost::cmatch separator;
+
+    if (hasLength)
     {
-      code = OrthancPluginRestApiGetAfterPlugins(context, &buffer, uri.c_str());
+      if (!boost::regex_match(startBody + length, end, separator, nextSeparator, boost::match_perl) ||
+          startBody + length != separator[1].first)
+      {
+        // Cannot find the separator after skipping the "Content-Length" bytes
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
     }
     else
     {
-      code = OrthancPluginRestApiGet(context, &buffer, uri.c_str());
-    }
-
-    if (code)
-    {
-      // Error
-      return false;
+      if (!boost::regex_match(startBody, end, separator, nextSeparator, boost::match_perl))
+      {
+        // No more occurrence of the boundary separator
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
     }
 
-    bool ok = true;
+    MultipartItem item;
+    item.data_ = startBody;
+    item.size_ = separator[1].first - startBody;
+    item.contentType_ = contentType;
+    result.push_back(item);
 
-    try
-    {
-      if (buffer.size)
-      {
-        result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size);
-      }
-      else
-      {
-        result.clear();
-      }
-    }
-    catch (std::bad_alloc&)
-    {
-      ok = false;
-    }
-
-    OrthancPluginFreeMemoryBuffer(context, &buffer);
-
-    return ok;
+    return separator[1].second;  // Return the end of the separator
   }
 
 
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      bool applyPlugins)
+  void ParseMultipartBody(std::vector<MultipartItem>& result,
+                          const void* body,
+                          const uint64_t bodySize,
+                          const std::string& boundary)
   {
-    std::string content;
-    RestApiGetString(content, context, uri, applyPlugins);
-    
-    Json::Reader reader;
-    return reader.parse(content, result);
+    // Reference:
+    // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
+
+    result.clear();
+
+    // Look for the first boundary separator in the body (note the "?"
+    // to request non-greedy search)
+    const boost::regex firstSeparator1("--" + boundary + "(--|\r\n).*");
+    const boost::regex firstSeparator2(".*?\r\n--" + boundary + "(--|\r\n).*");
+
+    // Look for the next boundary separator in the body (note the "?"
+    // to request non-greedy search)
+    const boost::regex nextSeparator(".*?(\r\n--" + boundary + ").*");
+
+    const char* start = reinterpret_cast<const char*>(body);
+    const char* end = reinterpret_cast<const char*>(body) + bodySize;
+
+    boost::cmatch what;
+    if (boost::regex_match(start, end, what, firstSeparator1, boost::match_perl | boost::match_single_line) ||
+        boost::regex_match(start, end, what, firstSeparator2, boost::match_perl | boost::match_single_line))
+    {
+      const char* current = what[1].first;
+
+      while (current != NULL &&
+             current + 2 < end)
+      {
+        if (current[0] != '\r' ||
+            current[1] != '\n')
+        {
+          // We reached a separator with a trailing "--", which
+          // means that reading the multipart body is done
+          break;
+        }
+        else
+        {
+          current = ParseMultipartItem(result, current + 2, end, nextSeparator);
+        }
+      }
+    }
   }
 
 
-  bool RestApiPostString(std::string& result,
-                         OrthancPluginContext* context,
-                         const std::string& uri,
-                         const std::string& body)
+  void ParseAssociativeArray(std::map<std::string, std::string>& target,
+                             const Json::Value& value,
+                             const std::string& key)
   {
-    OrthancPluginMemoryBuffer buffer;
-    int code = OrthancPluginRestApiPost(context, &buffer, uri.c_str(), body.c_str(), body.size());
+    if (value.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "This is not a JSON object");
+    }
 
-    if (code)
+    if (!value.isMember(key))
     {
-      // Error
-      return false;
+      return;
     }
 
-    bool ok = true;
+    const Json::Value& tmp = value[key];
 
-    try
+    if (tmp.type() != Json::objectValue)
     {
-      if (buffer.size)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "The field \"" + key + "\" of a JSON object is "
+                                      "not a JSON associative array as expected");
+    }
+
+    Json::Value::Members names = tmp.getMemberNames();
+
+    for (size_t i = 0; i < names.size(); i++)
+    {
+      if (tmp[names[i]].type() != Json::stringValue)
       {
-        result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Some value in the associative array \"" + key + 
+                                        "\" is not a string as expected");
       }
       else
       {
-        result.clear();
+        target[names[i]] = tmp[names[i]].asString();
       }
     }
-    catch (std::bad_alloc&)
-    {
-      ok = false;
-    }
-
-    OrthancPluginFreeMemoryBuffer(context, &buffer);
-
-    return ok;
   }
 
 
-  bool RestApiPostJson(Json::Value& result,
-                       OrthancPluginContext* context,
-                       const std::string& uri,
-                       const std::string& body)
+  bool ParseTag(Orthanc::DicomTag& target,
+                const std::string& name)
   {
-    std::string content;
-    RestApiPostString(content, context, uri, body);
+    OrthancPluginDictionaryEntry entry;
     
-    Json::Reader reader;
-    return reader.parse(content, result);
+    if (OrthancPluginLookupDictionary(OrthancPlugins::GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success)
+    {
+      target = Orthanc::DicomTag(entry.group, entry.element);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
   }
 
 
   namespace Configuration
   {
-    bool Read(Json::Value& configuration,
-              OrthancPluginContext* context)
+    // Assume Latin-1 encoding by default (as in the Orthanc core)
+    static Orthanc::Encoding defaultEncoding_ = Orthanc::Encoding_Latin1;
+    static std::auto_ptr<OrthancConfiguration> configuration_;
+
+
+    void Initialize()
     {
+      configuration_.reset(new OrthancConfiguration);
+      
+      OrthancPlugins::OrthancConfiguration global;
+      global.GetSection(*configuration_, "DicomWeb");
+
       std::string s;
-
+      if (global.LookupStringValue(s, "DefaultEncoding"))
       {
-        char* tmp = OrthancPluginGetConfiguration(context);
-        if (tmp == NULL)
-        {
-          OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc");
-          return false;
-        }
-
-        s.assign(tmp);
-        OrthancPluginFreeString(context, tmp);      
+        defaultEncoding_ = Orthanc::StringToEncoding(s.c_str());
       }
 
-      Json::Reader reader;
-      if (reader.parse(s, configuration))
-      {
-        return true;
-      }
-      else
-      {
-        OrthancPluginLogError(context, "Unable to parse the configuration");
-        return false;
-      }
+      OrthancPlugins::OrthancConfiguration servers;
+      configuration_->GetSection(servers, "Servers");
+      OrthancPlugins::DicomWebServers::GetInstance().Load(servers.GetJson());
     }
 
 
-    std::string GetStringValue(const Json::Value& configuration,
-                               const std::string& key,
+    std::string GetStringValue(const std::string& key,
                                const std::string& defaultValue)
     {
-      if (configuration.type() != Json::objectValue ||
-          !configuration.isMember(key) ||
-          configuration[key].type() != Json::stringValue)
-      {
-        return defaultValue;
-      }
-      else
-      {
-        return configuration[key].asString();
-      }
+      assert(configuration_.get() != NULL);
+      return configuration_->GetStringValue(key, defaultValue);
     }
 
 
-    bool GetBoolValue(const Json::Value& configuration,
-                      const std::string& key,
-                      bool defaultValue)
+    bool GetBooleanValue(const std::string& key,
+                         bool defaultValue)
     {
-      if (configuration.type() != Json::objectValue ||
-          !configuration.isMember(key) ||
-          configuration[key].type() != Json::booleanValue)
-      {
-        return defaultValue;
-      }
-      else
-      {
-        return configuration[key].asBool();
-      }
+      assert(configuration_.get() != NULL);
+      return configuration_->GetBooleanValue(key, defaultValue);
     }
 
 
-    std::string GetRoot(const Json::Value& configuration)
+    unsigned int GetUnsignedIntegerValue(const std::string& key,
+                                         unsigned int defaultValue)
     {
-      std::string root = GetStringValue(configuration, "Root", "/dicom-web/");
+      assert(configuration_.get() != NULL);
+      return configuration_->GetUnsignedIntegerValue(key, defaultValue);
+    }
+
+
+    std::string GetRoot()
+    {
+      assert(configuration_.get() != NULL);
+      std::string root = configuration_->GetStringValue("Root", "/dicom-web/");
 
       // Make sure the root URI starts and ends with a slash
       if (root.size() == 0 ||
@@ -323,9 +374,10 @@
     }
 
 
-    std::string GetWadoRoot(const Json::Value& configuration)
+    std::string GetWadoRoot()
     {
-      std::string root = GetStringValue(configuration, "WadoRoot", "/wado/");
+      assert(configuration_.get() != NULL);
+      std::string root = configuration_->GetStringValue("WadoRoot", "/wado/");
 
       // Make sure the root URI starts with a slash
       if (root.size() == 0 ||
@@ -344,11 +396,69 @@
     }
 
 
-    std::string  GetBaseUrl(const Json::Value& configuration,
-                            const OrthancPluginHttpRequest* request)
+    static bool IsHttpsProto(const std::string& proto,
+                             bool defaultValue)
+    {
+      if (proto == "http")
+      {
+        return false;
+      }
+      else if (proto == "https")
+      {
+        return true;
+      }
+      else
+      {
+        return defaultValue;
+      }
+    }
+
+
+    std::string GetBaseUrl(const OrthancPluginHttpRequest* request)
     {
-      std::string host = GetStringValue(configuration, "Host", "");
-      bool ssl = GetBoolValue(configuration, "Ssl", false);
+      assert(configuration_.get() != NULL);
+      std::string host = configuration_->GetStringValue("Host", "");
+      bool https = configuration_->GetBooleanValue("Ssl", false);
+
+      std::string forwarded;
+      if (host.empty() &&
+          LookupHttpHeader(forwarded, request, "forwarded"))
+      {
+        // There is a "Forwarded" HTTP header in the query
+        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
+        
+        std::vector<std::string> forwarders;
+        Orthanc::Toolbox::TokenizeString(forwarders, forwarded, ',');
+
+        // Only consider the first forwarder, if any
+        if (!forwarders.empty())
+        {
+          std::vector<std::string> tokens;
+          Orthanc::Toolbox::TokenizeString(tokens, forwarders[0], ';');
+
+          for (size_t j = 0; j < tokens.size(); j++)
+          {
+            std::vector<std::string> args;
+            Orthanc::Toolbox::TokenizeString(args, tokens[j], '=');
+            
+            if (args.size() == 2)
+            {
+              std::string key = Orthanc::Toolbox::StripSpaces(args[0]);
+              std::string value = Orthanc::Toolbox::StripSpaces(args[1]);
+
+              Orthanc::Toolbox::ToLowerCase(key);
+              if (key == "host")
+              {
+                host = value;
+              }
+              else if (key == "proto")
+              {
+                https = IsHttpsProto(value, https);
+              }
+            }
+          }
+        }
+      }
 
       if (host.empty() &&
           !LookupHttpHeader(host, request, "host"))
@@ -358,7 +468,34 @@
         host = "localhost:8042";
       }
 
-      return (ssl ? "https://" : "http://") + host + GetRoot(configuration);
+      return (https ? "https://" : "http://") + host + GetRoot();
+    }
+
+
+    std::string GetWadoUrl(const std::string& wadoBase,
+                           const std::string& studyInstanceUid,
+                           const std::string& seriesInstanceUid,
+                           const std::string& sopInstanceUid)
+    {
+      if (studyInstanceUid.empty() ||
+          seriesInstanceUid.empty() ||
+          sopInstanceUid.empty())
+      {
+        return "";
+      }
+      else
+      {
+        return (wadoBase + 
+                "studies/" + studyInstanceUid + 
+                "/series/" + seriesInstanceUid + 
+                "/instances/" + sopInstanceUid + "/");
+      }
+    }
+
+
+    Orthanc::Encoding GetDefaultEncoding()
+    {
+      return defaultEncoding_;
     }
   }
 }
--- a/Plugin/Configuration.h	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/Configuration.h	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -20,12 +21,30 @@
 
 #pragma once
 
+#include <Core/DicomFormat/DicomTag.h>
+#include <Core/Enumerations.h>
+
 #include <orthanc/OrthancCPlugin.h>
 #include <json/value.h>
 
+#if (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER <= 0 && \
+     ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER <= 9 && \
+     ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER <= 6)
+#  define HAS_SEND_MULTIPART_ITEM_2   0
+#else
+#  define HAS_SEND_MULTIPART_ITEM_2   1
+#endif
 
 namespace OrthancPlugins
 {
+  static const Orthanc::DicomTag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
+  static const Orthanc::DicomTag DICOM_TAG_FAILURE_REASON(0x0008, 0x1197);
+  static const Orthanc::DicomTag DICOM_TAG_WARNING_REASON(0x0008, 0x1196);
+  static const Orthanc::DicomTag DICOM_TAG_FAILED_SOP_SEQUENCE(0x0008, 0x1198);
+  static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_SEQUENCE(0x0008, 0x1199);
+  static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150);
+  static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
+
   struct MultipartItem
   {
     const char*   data_;
@@ -37,53 +56,47 @@
                         const OrthancPluginHttpRequest* request,
                         const std::string& header);
 
+  // TODO => REMOVE (use Orthanc core instead)
   void ParseContentType(std::string& application,
                         std::map<std::string, std::string>& attributes,
                         const std::string& header);
 
   void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          const char* body,
+                          const void* body,
                           const uint64_t bodySize,
                           const std::string& boundary);
 
-  bool RestApiGetString(std::string& result,
-                        OrthancPluginContext* context,
-                        const std::string& uri,
-                        bool applyPlugins = false);
-
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      bool applyPlugins = false);
+  void ParseAssociativeArray(std::map<std::string, std::string>& target,
+                             const Json::Value& value,
+                             const std::string& key);
 
-  bool RestApiPostString(std::string& result,
-                         OrthancPluginContext* context,
-                         const std::string& uri,
-                         const std::string& body);
-
-  bool RestApiPostJson(Json::Value& result,
-                       OrthancPluginContext* context,
-                       const std::string& uri,
-                       const std::string& body);
+  bool ParseTag(Orthanc::DicomTag& target,
+                const std::string& name);
 
   namespace Configuration
   {
-    bool Read(Json::Value& configuration,
-              OrthancPluginContext* context);
+    void Initialize();
 
-    std::string GetStringValue(const Json::Value& configuration,
-                               const std::string& key,
+    std::string GetStringValue(const std::string& key,
                                const std::string& defaultValue);
-    
-    bool GetBoolValue(const Json::Value& configuration,
-                      const std::string& key,
-                      bool defaultValue);
+
+    bool GetBooleanValue(const std::string& key,
+                         bool defaultValue);
+
+    unsigned int GetUnsignedIntegerValue(const std::string& key,
+                                         unsigned int defaultValue);
+
+    std::string GetRoot();
 
-    std::string GetRoot(const Json::Value& configuration);
+    std::string GetWadoRoot();
+      
+    std::string GetBaseUrl(const OrthancPluginHttpRequest* request);
 
-    std::string GetWadoRoot(const Json::Value& configuration);
-      
-    std::string GetBaseUrl(const Json::Value& configuration,
-                           const OrthancPluginHttpRequest* request);
+    std::string GetWadoUrl(const std::string& wadoBase,
+                           const std::string& studyInstanceUid,
+                           const std::string& seriesInstanceUid,
+                           const std::string& sopInstanceUid);
+
+    Orthanc::Encoding GetDefaultEncoding();
   }
 }
--- a/Plugin/Dicom.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,577 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Dicom.h"
-
-#include "ChunkedBuffer.h"
-
-#include <gdcmDictEntry.h>
-#include <gdcmStringFilter.h>
-#include <boost/lexical_cast.hpp>
-#include <json/writer.h>
-
-#include "../Orthanc/Core/OrthancException.h"
-#include "../Orthanc/Core/Toolbox.h"
-
-namespace OrthancPlugins
-{
-  namespace
-  {
-    class ChunkedBufferWriter : public pugi::xml_writer
-    {
-    private:
-      ChunkedBuffer buffer_;
-
-    public:
-      virtual void write(const void *data, size_t size)
-      {
-        if (size > 0)
-        {
-          buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
-        }
-      }
-
-      void Flatten(std::string& s)
-      {
-        buffer_.Flatten(s);
-      }
-    };
-  }
-
-
-
-  void ParsedDicomFile::Setup(const std::string& dicom)
-  {
-    // Prepare a memory stream over the DICOM instance
-    std::stringstream stream(dicom);
-
-    // Parse the DICOM instance using GDCM
-    reader_.SetStream(stream);
-    if (!reader_.Read())
-    {
-      /* "GDCM cannot read this DICOM instance of length " +
-         boost::lexical_cast<std::string>(dicom.size()) */
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(const OrthancPlugins::MultipartItem& item)
-  {
-    std::string dicom(item.data_, item.data_ + item.size_);
-    Setup(dicom);
-  }
-
-
-  static bool GetTag(std::string& result,
-                     const gdcm::DataSet& dataset,
-                     const gdcm::Tag& tag,
-                     bool stripSpaces)
-  {
-    if (dataset.FindDataElement(tag))
-    {
-      const gdcm::ByteValue* value = dataset.GetDataElement(tag).GetByteValue();
-      if (value)
-      {
-        result = std::string(value->GetPointer(), value->GetLength());
-
-        if (stripSpaces)
-        {
-          result = Orthanc::Toolbox::StripSpaces(result);
-        }
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-  static std::string GetTagWithDefault(const gdcm::DataSet& dataset,
-                                       const gdcm::Tag& tag,
-                                       const std::string& defaultValue,
-                                       bool stripSpaces)
-  {
-    std::string result;
-    if (!GetTag(result, dataset, tag, false))
-    {
-      result = defaultValue;
-    }
-
-    if (stripSpaces)
-    {
-      result = Orthanc::Toolbox::StripSpaces(result);
-    }
-
-    return result;
-  }
-
-
-  bool ParsedDicomFile::GetTag(std::string& result,
-                               const gdcm::Tag& tag,
-                               bool stripSpaces) const
-  {
-    return OrthancPlugins::GetTag(result, GetDataSet(), tag, stripSpaces);
-  }
-
-
-  std::string ParsedDicomFile::GetTagWithDefault(const gdcm::Tag& tag,
-                                                 const std::string& defaultValue,
-                                                 bool stripSpaces) const
-  {
-    return OrthancPlugins::GetTagWithDefault(GetDataSet(), tag, defaultValue, stripSpaces);
-  }
-
-
-  static std::string FormatTag(const gdcm::Tag& tag)
-  {
-    char tmp[16];
-    sprintf(tmp, "%04X%04X", tag.GetGroup(), tag.GetElement());
-    return std::string(tmp);
-  }
-
-
-  static const char* GetKeyword(const gdcm::Dict& dictionary,
-                                const gdcm::Tag& tag)
-  {
-    const gdcm::DictEntry &entry = dictionary.GetDictEntry(tag);
-    const char* keyword = entry.GetKeyword();
-
-    if (strlen(keyword) != 0)
-    {
-      return keyword;
-    }
-
-    if (tag == DICOM_TAG_RETRIEVE_URL)
-    {
-      return "RetrieveURL";
-    }
-
-    //throw Orthanc::OrthancException("Unknown keyword for tag: " + FormatTag(tag));
-    return NULL;
-  }
-
-
-
-  static const char* GetVRName(bool& isSequence,
-                               const gdcm::Dict& dictionary,
-                               const gdcm::DataElement& element)
-  {
-    gdcm::VR vr = element.GetVR();
-    if (vr == gdcm::VR::INVALID)
-    {
-      const gdcm::DictEntry &entry = dictionary.GetDictEntry(element.GetTag());
-      vr = entry.GetVR();
-
-      if (vr == gdcm::VR::OB_OW)
-      {
-        vr = gdcm::VR::OB;
-      }
-    }
-
-    isSequence = (vr == gdcm::VR::SQ);
-
-    const char* str = gdcm::VR::GetVRString(vr);
-    if (isSequence)
-    {
-      return str;
-    }
-
-    if (str == NULL ||
-        strlen(str) != 2 ||
-        !(str[0] >= 'A' && str[0] <= 'Z') ||
-        !(str[1] >= 'A' && str[1] <= 'Z'))
-    {
-      return "UN";
-    }
-    else
-    {
-      return str;
-    }
-  }
-
-
-  static bool IsBulkData(const std::string& vr)
-  {
-    /**
-     * Full list of VR (Value Representations) that are admissible for
-     * being retrieved as bulk data. We commented out some of them, as
-     * they correspond to strings and not to binary data.
-     **/
-    return (//vr == "FL" ||
-            //vr == "FD" ||
-            //vr == "IS" ||
-      vr == "LT" ||
-      vr == "OB" ||
-      vr == "OD" ||
-      vr == "OF" ||
-      vr == "OW" ||
-      //vr == "SL" ||
-      //vr == "SS" ||
-      //vr == "ST" ||
-      //vr == "UL" ||
-      vr == "UN" ||
-      //vr == "US" ||
-      vr == "UT");
-  }
-
-
-  static std::string GetBulkUriRoot(const std::string& wadoBase,
-                                    const gdcm::DataSet& dicom)
-  {
-    std::string study, series, instance;
-
-    if (!GetTag(study, dicom, DICOM_TAG_STUDY_INSTANCE_UID, true) ||
-        !GetTag(series, dicom, DICOM_TAG_SERIES_INSTANCE_UID, true) ||
-        !GetTag(instance, dicom, DICOM_TAG_SOP_INSTANCE_UID, true))
-    {
-      return "";
-    }
-    else
-    {
-      return wadoBase + "studies/" + study + "/series/" + series + "/instances/" + instance + "/bulk/";
-    }
-  }
-
-
-  static Orthanc::Encoding DetectEncoding(const gdcm::DataSet& dicom)
-  {
-    if (!dicom.FindDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET))
-    {
-      return Orthanc::Encoding_Ascii;
-    }
-
-    const gdcm::DataElement& element = 
-      dicom.GetDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-
-    const gdcm::ByteValue* data = element.GetByteValue();
-    if (!data)
-    {
-      // Assume Latin-1 encoding (TODO add a parameter as in Orthanc)
-      return Orthanc::Encoding_Latin1;
-    }
-
-    std::string tmp(data->GetPointer(), data->GetLength());
-    tmp = Orthanc::Toolbox::StripSpaces(tmp);
-
-    Orthanc::Encoding encoding;
-    if (Orthanc::GetDicomEncoding(encoding, tmp.c_str()))
-    {
-      return encoding;
-    }
-    else
-    {
-      // Assume Latin-1 encoding (TODO add a parameter as in Orthanc)
-      return Orthanc::Encoding_Latin1;
-    }
-  }
-
-
-  static bool ConvertDicomStringToUf8(std::string& result,
-                                      const gdcm::Dict& dictionary,
-                                      const gdcm::File* file,
-                                      const gdcm::DataElement& element,
-                                      const Orthanc::Encoding sourceEncoding)
-  {
-    const gdcm::ByteValue* data = element.GetByteValue();
-    if (!data)
-    {
-      return false;
-    }
-
-    if (file != NULL)
-    {
-      bool isSequence;
-      std::string vr = GetVRName(isSequence, dictionary, element);
-      if (!isSequence && (
-            vr == "FL" ||
-            vr == "FD" ||
-            vr == "SL" ||
-            vr == "SS" ||
-            vr == "UL" ||
-            vr == "US"
-            ))
-      {      
-        gdcm::StringFilter f;
-        f.SetFile(*file);
-        result = f.ToString(element.GetTag());
-        return true;
-      }
-    }
-
-    if (sourceEncoding == Orthanc::Encoding_Utf8)
-    {
-      result.assign(data->GetPointer(), data->GetLength());
-    }
-    else
-    {
-      std::string tmp(data->GetPointer(), data->GetLength());
-      result = Orthanc::Toolbox::ConvertToUtf8(tmp, sourceEncoding);
-    }
-
-    result = Orthanc::Toolbox::StripSpaces(result);
-    return true;
-  }
-
-
-  static void DicomToXmlInternal(pugi::xml_node& target,
-                                 const gdcm::Dict& dictionary,
-                                 const gdcm::File* file,
-                                 const gdcm::DataSet& dicom,
-                                 const Orthanc::Encoding sourceEncoding,
-                                 const std::string& bulkUri)
-  {
-    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
-         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
-    {
-      char path[16];
-      sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement());
-
-      pugi::xml_node node = target.append_child("DicomAttribute");
-      node.append_attribute("tag").set_value(FormatTag(it->GetTag()).c_str());
-
-      const char* keyword = GetKeyword(dictionary, it->GetTag());
-      if (keyword != NULL)
-      {
-        node.append_attribute("keyword").set_value(keyword);
-      }
-
-      bool isSequence = false;
-      std::string vr;
-      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
-      {
-        // The VR of this attribute has changed from UT to UR.
-        vr = "UR";
-      }
-      else
-      {
-        vr = GetVRName(isSequence, dictionary, *it);
-      }
-
-      node.append_attribute("vr").set_value(vr.c_str());
-
-      if (isSequence)
-      {
-        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();
-        if (seq.GetPointer() != NULL)
-        {
-          for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
-          {
-            pugi::xml_node item = node.append_child("Item");
-            std::string number = boost::lexical_cast<std::string>(i);
-            item.append_attribute("number").set_value(number.c_str());
-
-            std::string childUri;
-            if (!bulkUri.empty())
-            {
-              childUri = bulkUri + std::string(path) + "/" + number + "/";
-            }
-
-            DicomToXmlInternal(item, dictionary, file, seq->GetItem(i).GetNestedDataSet(), sourceEncoding, childUri);
-          }
-        }
-      }
-      else if (IsBulkData(vr))
-      {
-        // Bulk data
-        if (!bulkUri.empty())
-        {
-          pugi::xml_node value = node.append_child("BulkData");
-          std::string uri = bulkUri + std::string(path);
-          value.append_attribute("uri").set_value(uri.c_str());
-        }
-      }
-      else
-      {
-        // Deal with other value representations
-        pugi::xml_node value = node.append_child("Value");
-        value.append_attribute("number").set_value("1");
-
-        std::string tmp;
-        if (ConvertDicomStringToUf8(tmp, dictionary, file, *it, sourceEncoding)) 
-        {
-          value.append_child(pugi::node_pcdata).set_value(tmp.c_str());
-        }
-      }
-    }
-  }
-
-
-  static void DicomToXml(pugi::xml_document& target,
-                         const gdcm::Dict& dictionary,
-                         const gdcm::File* file,
-                         const gdcm::DataSet& dicom,
-                         const std::string& bulkUriRoot)
-  {
-    pugi::xml_node root = target.append_child("NativeDicomModel");
-    root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
-    root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
-    root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance");
-
-    Orthanc::Encoding encoding = DetectEncoding(dicom);
-    DicomToXmlInternal(root, dictionary, file, dicom, encoding, bulkUriRoot);
-
-    pugi::xml_node decl = target.prepend_child(pugi::node_declaration);
-    decl.append_attribute("version").set_value("1.0");
-    decl.append_attribute("encoding").set_value("utf-8");
-  }
-
-
-  static void DicomToJsonInternal(Json::Value& target,
-                                  const gdcm::Dict& dictionary,
-                                  const gdcm::File* file,
-                                  const gdcm::DataSet& dicom,
-                                  const std::string& bulkUri,
-                                  Orthanc::Encoding sourceEncoding)
-  {
-    target = Json::objectValue;
-
-    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
-         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
-    {
-      char path[16];
-      sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement());
-
-      Json::Value node = Json::objectValue;
-
-      bool isSequence = false;
-      std::string vr;
-      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
-      {
-        // The VR of this attribute has changed from UT to UR.
-        vr = "UR";
-      }
-      else
-      {
-        vr = GetVRName(isSequence, dictionary, *it);
-      }
-
-      node["vr"] = vr.c_str();
-
-      if (isSequence)
-      {
-        // Deal with sequences
-        node["Value"] = Json::arrayValue;
-
-        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();
-        if (seq.GetPointer() != NULL)
-        {
-          for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
-          {
-            Json::Value child;
-
-            std::string childUri;
-            if (!bulkUri.empty())
-            {
-              std::string number = boost::lexical_cast<std::string>(i);
-              childUri = bulkUri + std::string(path) + "/" + number + "/";
-            }
-
-            DicomToJsonInternal(child, dictionary, file, seq->GetItem(i).GetNestedDataSet(), childUri, sourceEncoding);
-            node["Value"].append(child);
-          }
-        }
-      }
-      else if (IsBulkData(vr))
-      {
-        // Bulk data
-        if (!bulkUri.empty())
-        {
-          node["BulkDataURI"] = bulkUri + std::string(path);
-        }
-      }
-      else
-      {
-        // Deal with other value representations
-        node["Value"] = Json::arrayValue;
-
-        std::string value;
-        if (ConvertDicomStringToUf8(value, dictionary, file, *it, sourceEncoding)) 
-        {
-          node["Value"].append(value.c_str());
-        }
-      }
-
-      target[FormatTag(it->GetTag())] = node;
-    }
-  }
-
-
-  static void DicomToJson(Json::Value& target,
-                          const gdcm::Dict& dictionary,
-                          const gdcm::File* file,
-                          const gdcm::DataSet& dicom,
-                          const std::string& bulkUriRoot)
-  {
-    Orthanc::Encoding encoding = DetectEncoding(dicom);
-    DicomToJsonInternal(target, dictionary, file, dicom, bulkUriRoot, encoding);
-  }
-
-
-  void GenerateSingleDicomAnswer(std::string& result,
-                                 const std::string& wadoBase,
-                                 const gdcm::Dict& dictionary,
-                                 const gdcm::File* file,  // Can be NULL
-                                 const gdcm::DataSet& dicom,
-                                 bool isXml,
-                                 bool isBulkAccessible)
-  {
-    std::string bulkUriRoot;
-    if (isBulkAccessible)
-    {
-      bulkUriRoot = GetBulkUriRoot(wadoBase, dicom);
-    }
-
-    if (isXml)
-    {
-      pugi::xml_document doc;
-      DicomToXml(doc, dictionary, file, dicom, bulkUriRoot);
-    
-      ChunkedBufferWriter writer;
-      doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
-
-      writer.Flatten(result);
-    }
-    else
-    {
-      Json::Value v;
-      DicomToJson(v, dictionary, file, dicom, bulkUriRoot);
-
-      Json::FastWriter writer;
-      result = writer.write(v); 
-    }
-  }
-
-
-  void AnswerDicom(OrthancPluginContext* context,
-                   OrthancPluginRestOutput* output,
-                   const std::string& wadoBase,
-                   const gdcm::Dict& dictionary,
-                   const gdcm::DataSet& dicom,
-                   bool isXml,
-                   bool isBulkAccessible)
-  {
-    std::string answer;
-    GenerateSingleDicomAnswer(answer, wadoBase, dictionary, NULL, dicom, isXml, isBulkAccessible);
-    OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), 
-                              isXml ? "application/dicom+xml" : "application/json");
-  }
-}
--- a/Plugin/Dicom.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Configuration.h"
-
-#include <gdcmReader.h>
-#include <gdcmDataSet.h>
-#include <pugixml.hpp>
-#include <gdcmDict.h>
-#include <list>
-
-
-namespace OrthancPlugins
-{
-  static const gdcm::Tag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
-  static const gdcm::Tag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
-  static const gdcm::Tag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
-  static const gdcm::Tag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
-  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150);
-  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
-  static const gdcm::Tag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
-  static const gdcm::Tag DICOM_TAG_FAILED_SOP_SEQUENCE(0x0008, 0x1198);
-  static const gdcm::Tag DICOM_TAG_FAILURE_REASON(0x0008, 0x1197);
-  static const gdcm::Tag DICOM_TAG_WARNING_REASON(0x0008, 0x1196);
-  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_SEQUENCE(0x0008, 0x1199);
-  static const gdcm::Tag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
-  static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
-
-  class ParsedDicomFile
-  {
-  private:
-    gdcm::Reader reader_;
-
-    void Setup(const std::string& dicom);
-
-  public:
-    ParsedDicomFile(const OrthancPlugins::MultipartItem& item);
-
-    ParsedDicomFile(const std::string& dicom)
-    {
-      Setup(dicom);
-    }
-
-    const gdcm::File& GetFile() const
-    {
-      return reader_.GetFile();
-    }
-
-    const gdcm::DataSet& GetDataSet() const
-    {
-      return reader_.GetFile().GetDataSet();
-    }
-
-    bool GetTag(std::string& result,
-                const gdcm::Tag& tag,
-                bool stripSpaces) const;
-
-    std::string GetTagWithDefault(const gdcm::Tag& tag,
-                                  const std::string& defaultValue,
-                                  bool stripSpaces) const;
-  };
-
-
-  void GenerateSingleDicomAnswer(std::string& result,
-                                 const std::string& wadoBase,
-                                 const gdcm::Dict& dictionary,
-                                 const gdcm::File* file,  // Can be NULL
-                                 const gdcm::DataSet& dicom,
-                                 bool isXml,
-                                 bool isBulkAccessible);
-
-  void AnswerDicom(OrthancPluginContext* context,
-                   OrthancPluginRestOutput* output,
-                   const std::string& wadoBase,
-                   const gdcm::Dict& dictionary,
-                   const gdcm::DataSet& dicom,
-                   bool isXml,
-                   bool isBulkAccessible);
-}
--- a/Plugin/DicomResults.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "DicomResults.h"
-
-#include "Dicom.h"
-#include "../Orthanc/Core/OrthancException.h"
-
-namespace OrthancPlugins
-{
-  DicomResults::DicomResults(OrthancPluginContext* context,
-                             OrthancPluginRestOutput* output,
-                             const std::string& wadoBase,
-                             const gdcm::Dict& dictionary,
-                             bool isXml,
-                             bool isBulkAccessible) :
-    context_(context),
-    output_(output),
-    wadoBase_(wadoBase),
-    dictionary_(dictionary),
-    isFirst_(true),
-    isXml_(isXml),
-    isBulkAccessible_(isBulkAccessible)
-  {
-    if (isXml_ &&
-        OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml") != 0)
-    {
-      OrthancPluginLogError(context_, "Unable to create a multipart stream of DICOM+XML answers");
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-
-    jsonWriter_.AddChunk("[\n");
-  }
-
-
-  void DicomResults::AddInternal(const gdcm::File* file,
-                                 const gdcm::DataSet& dicom)
-  {
-    if (isXml_)
-    {
-      std::string answer;
-      GenerateSingleDicomAnswer(answer, wadoBase_, dictionary_, file, dicom, true, isBulkAccessible_);
-
-      if (OrthancPluginSendMultipartItem(context_, output_, answer.c_str(), answer.size()) != 0)
-      {
-        OrthancPluginLogError(context_, "Unable to create a multipart stream of DICOM+XML answers");
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-    }
-    else
-    {
-      if (!isFirst_)
-      {
-        jsonWriter_.AddChunk(",\n");
-      }
-
-      std::string item;
-      GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, false, isBulkAccessible_);
-      jsonWriter_.AddChunk(item);
-    }
-
-    isFirst_ = false;
-  }
-
-  void DicomResults::Answer()
-  {
-    if (isXml_)
-    {
-      // Nothing to do in this case
-    }
-    else
-    {
-      jsonWriter_.AddChunk("]\n");
-
-      std::string answer;
-      jsonWriter_.Flatten(answer);
-      OrthancPluginAnswerBuffer(context_, output_, answer.c_str(), answer.size(), "application/json");
-    }
-  }
-}
--- a/Plugin/DicomResults.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "ChunkedBuffer.h"
-
-#include <orthanc/OrthancCPlugin.h>
-#include <gdcmDataSet.h>
-#include <gdcmDict.h>
-#include <gdcmFile.h>
-
-namespace OrthancPlugins
-{
-  class DicomResults
-  {
-  private:
-    OrthancPluginContext*     context_;
-    OrthancPluginRestOutput*  output_;
-    std::string               wadoBase_;
-    const gdcm::Dict&         dictionary_;
-    ChunkedBuffer             jsonWriter_;  // Used for JSON output
-    bool                      isFirst_; 
-    bool                      isXml_;
-    bool                      isBulkAccessible_;
-
-    void AddInternal(const gdcm::File* file,
-                     const gdcm::DataSet& dicom);
-
-  public:
-    DicomResults(OrthancPluginContext* context,
-                 OrthancPluginRestOutput* output,
-                 const std::string& wadoBase,
-                 const gdcm::Dict& dictionary,
-                 bool isXml,
-                 bool isBulkAccessible);
-
-    void Add(const gdcm::File& file)
-    {
-      AddInternal(&file, file.GetDataSet());
-    }
-
-    void Add(const gdcm::File& file,
-             const gdcm::DataSet& subset)
-    {
-      AddInternal(&file, subset);
-    }
-
-    void Answer();
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebClient.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,693 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomWebClient.h"
+
+#include "DicomWebServers.h"
+
+#include <json/reader.h>
+#include <list>
+#include <set>
+#include <boost/lexical_cast.hpp>
+
+#include <Core/ChunkedBuffer.h>
+#include <Core/Toolbox.h>
+
+
+static void AddInstance(std::list<std::string>& target,
+                        const Json::Value& instance)
+{
+  if (instance.type() != Json::objectValue ||
+      !instance.isMember("ID") ||
+      instance["ID"].type() != Json::stringValue)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+  else
+  {
+    target.push_back(instance["ID"].asString());
+  }
+}
+
+
+static bool GetSequenceSize(size_t& result,
+                            const Json::Value& answer,
+                            const std::string& tag,
+                            bool isMandatory,
+                            const std::string& server)
+{
+  const Json::Value* value = NULL;
+
+  std::string upper, lower;
+  Orthanc::Toolbox::ToUpperCase(upper, tag);
+  Orthanc::Toolbox::ToLowerCase(lower, tag);
+  
+  if (answer.isMember(upper))
+  {
+    value = &answer[upper];
+  }
+  else if (answer.isMember(lower))
+  {
+    value = &answer[lower];
+  }
+  else if (isMandatory)
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "The STOW-RS JSON response from DICOMweb server " + server + 
+      " does not contain the mandatory tag " + upper);
+  }
+  else
+  {
+    return false;
+  }
+
+  if (value->type() != Json::objectValue ||
+      (value->isMember("Value") &&
+       (*value) ["Value"].type() != Json::arrayValue))
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "Unable to parse STOW-RS JSON response from DICOMweb server " + server);
+  }
+
+  if (value->isMember("Value"))
+  {
+    result = (*value) ["Value"].size();
+  }
+  else
+  {
+    result = 0;
+  }
+
+  return true;
+}
+
+
+
+static void ParseStowRequest(std::list<std::string>& instances /* out */,
+                             std::map<std::string, std::string>& httpHeaders /* out */,
+                             std::map<std::string, std::string>& queryArguments /* out */,
+                             const OrthancPluginHttpRequest* request /* in */)
+{
+  static const char* RESOURCES = "Resources";
+  static const char* HTTP_HEADERS = "HttpHeaders";
+  static const char* QUERY_ARGUMENTS = "Arguments";
+
+  Json::Value body;
+  Json::Reader reader;
+  if (!reader.parse(reinterpret_cast<const char*>(request->body),
+                    reinterpret_cast<const char*>(request->body) + request->bodySize, body) ||
+      body.type() != Json::objectValue ||
+      !body.isMember(RESOURCES) ||
+      body[RESOURCES].type() != Json::arrayValue)
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_BadFileFormat,
+      "A request to the DICOMweb STOW-RS client must provide a JSON object "
+      "with the field \"" + std::string(RESOURCES) + 
+      "\" containing an array of resources to be sent");
+  }
+
+  OrthancPlugins::ParseAssociativeArray(queryArguments, body, QUERY_ARGUMENTS);
+  OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
+
+  Json::Value& resources = body[RESOURCES];
+
+  // Extract information about all the child instances
+  for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
+  {
+    if (resources[i].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    std::string resource = resources[i].asString();
+    if (resource.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    // Test whether this resource is an instance
+    Json::Value tmp;
+    if (OrthancPlugins::RestApiGet(tmp, "/instances/" + resource, false))
+    {
+      AddInstance(instances, tmp);
+    }
+    // This was not an instance, successively try with series/studies/patients
+    else if ((OrthancPlugins::RestApiGet(tmp, "/series/" + resource, false) &&
+              OrthancPlugins::RestApiGet(tmp, "/series/" + resource + "/instances", false)) ||
+             (OrthancPlugins::RestApiGet(tmp, "/studies/" + resource, false) &&
+              OrthancPlugins::RestApiGet(tmp, "/studies/" + resource + "/instances", false)) ||
+             (OrthancPlugins::RestApiGet(tmp, "/patients/" + resource, false) &&
+              OrthancPlugins::RestApiGet(tmp, "/patients/" + resource + "/instances", false)))
+    {
+      if (tmp.type() != Json::arrayValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      for (Json::Value::ArrayIndex j = 0; j < tmp.size(); j++)
+      {
+        AddInstance(instances, tmp[j]);
+      }
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }   
+  }
+}
+
+
+static void SendStowChunks(const Orthanc::WebServiceParameters& server,
+                           const std::map<std::string, std::string>& httpHeaders,
+                           const std::map<std::string, std::string>& queryArguments,
+                           const std::string& boundary,
+                           Orthanc::ChunkedBuffer& chunks,
+                           size_t& countInstances,
+                           bool force)
+{
+  unsigned int maxInstances = OrthancPlugins::Configuration::GetUnsignedIntegerValue("StowMaxInstances", 10);
+  size_t maxSize = static_cast<size_t>(OrthancPlugins::Configuration::GetUnsignedIntegerValue("StowMaxSize", 10)) * 1024 * 1024;
+
+  if ((force && countInstances > 0) ||
+      (maxInstances != 0 && countInstances >= maxInstances) ||
+      (maxSize != 0 && chunks.GetNumBytes() >= maxSize))
+  {
+    chunks.AddChunk("\r\n--" + boundary + "--\r\n");
+
+    std::string body;
+    chunks.Flatten(body);
+
+    OrthancPlugins::MemoryBuffer answerBody;
+    std::map<std::string, std::string> answerHeaders;
+
+    std::string uri;
+    OrthancPlugins::UriEncode(uri, "studies", queryArguments);
+
+    OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Post,
+                               httpHeaders, uri, body);
+
+    Json::Value response;
+    Json::Reader reader;
+    bool success = reader.parse(reinterpret_cast<const char*>((*answerBody)->data),
+                                reinterpret_cast<const char*>((*answerBody)->data) + (*answerBody)->size, response);
+    answerBody.Clear();
+
+    if (!success ||
+        response.type() != Json::objectValue ||
+        !response.isMember("00081199"))
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NetworkProtocol,
+        "Unable to parse STOW-RS JSON response from DICOMweb server " + server.GetUrl());
+    }
+
+    size_t size;
+    if (!GetSequenceSize(size, response, "00081199", true, server.GetUrl()) ||
+        size != countInstances)
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NetworkProtocol,
+        "The STOW-RS server was only able to receive " + 
+        boost::lexical_cast<std::string>(size) + " instances out of " +
+        boost::lexical_cast<std::string>(countInstances));
+    }
+
+    if (GetSequenceSize(size, response, "00081198", false, server.GetUrl()) &&
+        size != 0)
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NetworkProtocol,
+        "The response from the STOW-RS server contains " + 
+        boost::lexical_cast<std::string>(size) + 
+        " items in its Failed SOP Sequence (0008,1198) tag");
+    }
+
+    if (GetSequenceSize(size, response, "0008119A", false, server.GetUrl()) &&
+        size != 0)
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NetworkProtocol,
+        "The response from the STOW-RS server contains " + 
+        boost::lexical_cast<std::string>(size) + 
+        " items in its Other Failures Sequence (0008,119A) tag");
+    }
+
+    countInstances = 0;
+  }
+}
+
+
+void StowClient(OrthancPluginRestOutput* output,
+                const char* /*url*/,
+                const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->groupsCount != 1)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
+  }
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+    return;
+  }
+
+  Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
+
+  std::string boundary;
+
+  {
+    char* uuid = OrthancPluginGenerateUuid(context);
+    try
+    {
+      boundary.assign(uuid);
+    }
+    catch (...)
+    {
+      OrthancPluginFreeString(context, uuid);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+
+    OrthancPluginFreeString(context, uuid);
+  }
+
+  std::string mime = "multipart/related; type=\"application/dicom\"; boundary=" + boundary;
+
+  std::map<std::string, std::string> queryArguments;
+  std::map<std::string, std::string> httpHeaders;
+  httpHeaders["Accept"] = "application/dicom+json";
+  httpHeaders["Expect"] = "";
+  httpHeaders["Content-Type"] = mime;
+
+  std::list<std::string> instances;
+  ParseStowRequest(instances, httpHeaders, queryArguments, request);
+
+  OrthancPlugins::LogInfo("Sending " + boost::lexical_cast<std::string>(instances.size()) +
+                          " instances using STOW-RS to DICOMweb server: " + server.GetUrl());
+
+  Orthanc::ChunkedBuffer chunks;
+  size_t countInstances = 0;
+
+  for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it)
+  {
+    OrthancPlugins::MemoryBuffer dicom;
+    if (dicom.RestApiGet("/instances/" + *it + "/file", false))
+    {
+      chunks.AddChunk("\r\n--" + boundary + "\r\n" +
+                      "Content-Type: application/dicom\r\n" +
+                      "Content-Length: " + boost::lexical_cast<std::string>(dicom.GetSize()) +
+                      "\r\n\r\n");
+      chunks.AddChunk(dicom.GetData(), dicom.GetSize());
+      countInstances ++;
+
+      SendStowChunks(server, httpHeaders, queryArguments, boundary, chunks, countInstances, false);
+    }
+  }
+
+  SendStowChunks(server, httpHeaders, queryArguments, boundary, chunks, countInstances, true);
+
+  std::string answer = "{}\n";
+  OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+}
+
+
+static bool GetStringValue(std::string& target,
+                           const Json::Value& json,
+                           const std::string& key)
+{
+  if (json.type() != Json::objectValue)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+  }
+  else if (!json.isMember(key))
+  {
+    target.clear();
+    return false;
+  }
+  else if (json[key].type() != Json::stringValue)
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_BadFileFormat,
+      "The field \"" + key + "\" in a JSON object should be a string");
+  }
+  else
+  {
+    target = json[key].asString();
+    return true;
+  }
+}
+
+
+void GetFromServer(OrthancPluginRestOutput* output,
+                   const char* /*url*/,
+                   const OrthancPluginHttpRequest* request)
+{
+  static const char* URI = "Uri";
+  static const char* HTTP_HEADERS = "HttpHeaders";
+  static const char* GET_ARGUMENTS = "Arguments";
+
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+    return;
+  }
+
+  Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
+
+  std::string tmp;
+  Json::Value body;
+  Json::Reader reader;
+  if (!reader.parse(reinterpret_cast<const char*>(request->body),
+                    reinterpret_cast<const char*>(request->body) + request->bodySize, body) ||
+      body.type() != Json::objectValue ||
+      !GetStringValue(tmp, body, URI))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                    "A request to the DICOMweb client must provide a JSON object "
+                                    "with the field \"Uri\" containing the URI of interest");
+  }
+
+  std::map<std::string, std::string> getArguments;
+  OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
+
+  std::string uri;
+  OrthancPlugins::UriEncode(uri, tmp, getArguments);
+
+  std::map<std::string, std::string> httpHeaders;
+  OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
+
+  OrthancPlugins::MemoryBuffer answerBody;
+  std::map<std::string, std::string> answerHeaders;
+  OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
+
+  std::string contentType = "application/octet-stream";
+
+  for (std::map<std::string, std::string>::const_iterator
+         it = answerHeaders.begin(); it != answerHeaders.end(); ++it)
+  {
+    std::string key = it->first;
+    Orthanc::Toolbox::ToLowerCase(key);
+
+    if (key == "content-type")
+    {
+      contentType = it->second;
+    }
+    else if (key == "transfer-encoding")
+    {
+      // Do not forward this header
+    }
+    else
+    {
+      OrthancPluginSetHttpHeader(context, output, it->first.c_str(), it->second.c_str());
+    }
+  }
+
+  OrthancPluginAnswerBuffer(context, output, 
+                            reinterpret_cast<const char*>(answerBody.GetData()),
+                            answerBody.GetSize(), contentType.c_str());
+}
+
+
+
+static void RetrieveFromServerInternal(std::set<std::string>& instances,
+                                       const Orthanc::WebServiceParameters& server,
+                                       const std::map<std::string, std::string>& httpHeaders,
+                                       const std::map<std::string, std::string>& getArguments,
+                                       const Json::Value& resource)
+{
+  static const std::string STUDY = "Study";
+  static const std::string SERIES = "Series";
+  static const std::string INSTANCE = "Instance";
+  static const std::string MULTIPART_RELATED = "multipart/related";
+  static const std::string APPLICATION_DICOM = "application/dicom";
+
+  if (resource.type() != Json::objectValue)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                    "Resources of interest for the DICOMweb WADO-RS Retrieve client "
+                                    "must be provided as a JSON object");
+  }
+
+  std::string study, series, instance;
+  if (!GetStringValue(study, resource, STUDY) ||
+      study.empty())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                    "A non-empty \"" + STUDY + "\" field is mandatory for the "
+                                    "DICOMweb WADO-RS Retrieve client");
+  }
+
+  GetStringValue(series, resource, SERIES);
+  GetStringValue(instance, resource, INSTANCE);
+
+  if (series.empty() && 
+      !instance.empty())
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_BadFileFormat,
+      "When specifying a \"" + INSTANCE + "\" field in a call to DICOMweb "
+      "WADO-RS Retrieve client, the \"" + SERIES + "\" field is mandatory");
+  }
+
+  std::string tmpUri = "studies/" + study;
+  if (!series.empty())
+  {
+    tmpUri += "/series/" + series;
+    if (!instance.empty())
+    {
+      tmpUri += "/instances/" + instance;
+    }
+  }
+
+  std::string uri;
+  OrthancPlugins::UriEncode(uri, tmpUri, getArguments);
+
+  OrthancPlugins::MemoryBuffer answerBody;
+  std::map<std::string, std::string> answerHeaders;
+  OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
+
+  std::string contentTypeFull;
+  std::vector<std::string> contentType;
+  for (std::map<std::string, std::string>::const_iterator 
+         it = answerHeaders.begin(); it != answerHeaders.end(); ++it)
+  {
+    std::string s = Orthanc::Toolbox::StripSpaces(it->first);
+    Orthanc::Toolbox::ToLowerCase(s);
+    if (s == "content-type")
+    {
+      contentTypeFull = it->second;
+      Orthanc::Toolbox::TokenizeString(contentType, it->second, ';');
+      break;
+    }
+  }
+
+  OrthancPlugins::LogInfo("Got " + boost::lexical_cast<std::string>(answerBody.GetSize()) +
+                          " bytes from a WADO-RS query with content type: " + contentTypeFull);
+  
+  if (contentType.empty())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                    "No Content-Type provided by the remote WADO-RS server");
+  }
+
+  Orthanc::Toolbox::ToLowerCase(contentType[0]);
+  if (Orthanc::Toolbox::StripSpaces(contentType[0]) != MULTIPART_RELATED)
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "The remote WADO-RS server answers with a \"" + contentType[0] +
+      "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected");
+  }
+
+  std::string type, boundary;
+  for (size_t i = 1; i < contentType.size(); i++)
+  {
+    std::vector<std::string> tokens;
+    Orthanc::Toolbox::TokenizeString(tokens, contentType[i], '=');
+
+    if (tokens.size() == 2)
+    {
+      std::string s = Orthanc::Toolbox::StripSpaces(tokens[0]);
+      Orthanc::Toolbox::ToLowerCase(s);
+
+      if (s == "type")
+      {
+        type = Orthanc::Toolbox::StripSpaces(tokens[1]);
+
+        // This covers the case where the content-type is quoted,
+        // which COULD be the case 
+        // cf. https://tools.ietf.org/html/rfc7231#section-3.1.1.1
+        size_t len = type.length();
+        if (len >= 2 &&
+            type[0] == '"' &&
+            type[len - 1] == '"')
+        {
+          type = type.substr(1, len - 2);
+        }
+        
+        Orthanc::Toolbox::ToLowerCase(type);
+      }
+      else if (s == "boundary")
+      {
+        boundary = Orthanc::Toolbox::StripSpaces(tokens[1]);
+      }
+    }
+  }
+
+  // Strip the trailing and heading quotes if present
+  if (boundary.length() > 2 &&
+      boundary[0] == '"' &&
+      boundary[boundary.size() - 1] == '"')
+  {
+    boundary = boundary.substr(1, boundary.size() - 2);
+  }
+
+  OrthancPlugins::LogInfo("  Parsing the multipart content type: " + type +
+                          " with boundary: " + boundary);
+
+  if (type != APPLICATION_DICOM)
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "The remote WADO-RS server answers with a \"" + type +
+      "\" multipart Content-Type, but \"" + APPLICATION_DICOM + "\" is expected");
+  }
+
+  if (boundary.empty())
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "The remote WADO-RS server does not provide a boundary for its multipart answer");
+  }
+
+  std::vector<OrthancPlugins::MultipartItem> parts;
+  OrthancPlugins::ParseMultipartBody(parts, 
+                                     reinterpret_cast<const char*>(answerBody.GetData()),
+                                     answerBody.GetSize(), boundary);
+
+  OrthancPlugins::LogInfo("The remote WADO-RS server has provided " +
+                          boost::lexical_cast<std::string>(parts.size()) + 
+                          " DICOM instances");
+
+  for (size_t i = 0; i < parts.size(); i++)
+  {
+    std::vector<std::string> tokens;
+    Orthanc::Toolbox::TokenizeString(tokens, parts[i].contentType_, ';');
+
+    std::string partType;
+    if (tokens.size() > 0)
+    {
+      partType = Orthanc::Toolbox::StripSpaces(tokens[0]);
+    }
+
+    if (partType != APPLICATION_DICOM)
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NetworkProtocol,
+        "The remote WADO-RS server has provided a non-DICOM file in its multipart answer"
+        " (content type: " + parts[i].contentType_ + ")");
+    }
+
+    OrthancPlugins::MemoryBuffer tmp;
+    tmp.RestApiPost("/instances", parts[i].data_, parts[i].size_, false);
+
+    Json::Value result;
+    tmp.ToJson(result);
+
+    if (result.type() != Json::objectValue ||
+        !result.isMember("ID") ||
+        result["ID"].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
+    }
+    else
+    {
+      instances.insert(result["ID"].asString());
+    }
+  }
+}
+
+
+
+void RetrieveFromServer(OrthancPluginRestOutput* output,
+                        const char* /*url*/,
+                        const OrthancPluginHttpRequest* request)
+{
+  static const std::string RESOURCES("Resources");
+  static const char* HTTP_HEADERS = "HttpHeaders";
+  static const std::string GET_ARGUMENTS = "Arguments";
+
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+    return;
+  }
+
+  Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
+
+  Json::Value body;
+  Json::Reader reader;
+  if (!reader.parse(reinterpret_cast<const char*>(request->body),
+                    reinterpret_cast<const char*>(request->body) + request->bodySize, body) ||
+      body.type() != Json::objectValue ||
+      !body.isMember(RESOURCES) ||
+      body[RESOURCES].type() != Json::arrayValue)
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_BadFileFormat,
+      "A request to the DICOMweb WADO-RS Retrieve client must provide a JSON object "
+      "with the field \"" + RESOURCES + "\" containing an array of resources");
+  }
+
+  std::map<std::string, std::string> httpHeaders;
+  OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
+
+  std::map<std::string, std::string> getArguments;
+  OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
+
+
+  std::set<std::string> instances;
+  for (Json::Value::ArrayIndex i = 0; i < body[RESOURCES].size(); i++)
+  {
+    RetrieveFromServerInternal(instances, server, httpHeaders, getArguments, body[RESOURCES][i]);
+  }
+
+  Json::Value status = Json::objectValue;
+  status["Instances"] = Json::arrayValue;
+  
+  for (std::set<std::string>::const_iterator
+         it = instances.begin(); it != instances.end(); ++it)
+  {
+    status["Instances"].append(*it);
+  }
+
+  std::string s = status.toStyledString();
+  OrthancPluginAnswerBuffer(context, output, s.c_str(), s.size(), "application/json");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebClient.h	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,37 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Configuration.h"
+
+
+void StowClient(OrthancPluginRestOutput* output,
+                const char* url,
+                const OrthancPluginHttpRequest* request);
+
+void GetFromServer(OrthancPluginRestOutput* output,
+                   const char* /*url*/,
+                   const OrthancPluginHttpRequest* request);
+
+void RetrieveFromServer(OrthancPluginRestOutput* output,
+                        const char* /*url*/,
+                        const OrthancPluginHttpRequest* request);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebFormatter.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,194 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomWebFormatter.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+
+namespace OrthancPlugins
+{
+  static std::string FormatTag(uint16_t group,
+                               uint16_t element)
+  {
+    char buf[16];
+    sprintf(buf, "%04x%04x", group, element);
+    return std::string(buf);
+  }
+
+
+  void DicomWebFormatter::Callback(OrthancPluginDicomWebNode *node,
+                                   OrthancPluginDicomWebSetBinaryNode setter,
+                                   uint32_t levelDepth,
+                                   const uint16_t *levelTagGroup,
+                                   const uint16_t *levelTagElement,
+                                   const uint32_t *levelIndex,
+                                   uint16_t tagGroup,
+                                   uint16_t tagElement,
+                                   OrthancPluginValueRepresentation vr)
+  {
+    const DicomWebFormatter& that = GetSingleton();
+
+    switch (that.mode_)
+    {
+      case OrthancPluginDicomWebBinaryMode_Ignore:
+      case OrthancPluginDicomWebBinaryMode_InlineBinary:
+        setter(node, that.mode_, NULL);
+        break;
+
+      case OrthancPluginDicomWebBinaryMode_BulkDataUri:
+      {
+        std::string uri = GetSingleton().bulkRoot_;
+
+        for (size_t i = 0; i < levelDepth; i++)
+        {
+          uri += ("/" + FormatTag(levelTagGroup[i], levelTagElement[i]) + "/" +
+                  boost::lexical_cast<std::string>(levelIndex[i] + 1));
+        }
+    
+        uri += "/" + FormatTag(tagGroup, tagElement);
+    
+        setter(node, that.mode_, uri.c_str());
+        break;
+      }
+    }
+  }
+
+
+  DicomWebFormatter::Locker::Locker(OrthancPluginDicomWebBinaryMode mode,
+                                    const std::string& bulkRoot) :
+    that_(GetSingleton()),
+    lock_(that_.mutex_)
+  {
+    that_.mode_ = mode;
+    that_.bulkRoot_ = bulkRoot;
+  }
+
+
+  void DicomWebFormatter::Locker::Apply(std::string& target,
+                                        OrthancPluginContext* context,
+                                        const void* data,
+                                        size_t size,
+                                        bool xml)
+  {
+    OrthancString s;
+
+    if (xml)
+    {
+      s.Assign(OrthancPluginEncodeDicomWebXml(context, data, size, Callback));
+    }
+    else
+    {
+      s.Assign(OrthancPluginEncodeDicomWebJson(context, data, size, Callback));
+    }
+
+    s.ToString(target);
+  }
+
+
+  void DicomWebFormatter::Locker::Apply(std::string& target,
+                                        OrthancPluginContext* context,
+                                        const Json::Value& value,
+                                        bool xml)
+  {
+    MemoryBuffer dicom;
+    dicom.CreateDicom(value, OrthancPluginCreateDicomFlags_None);
+    Apply(target, context, dicom.GetData(), dicom.GetSize(), xml);
+  }
+
+
+  DicomWebFormatter::HttpWriter::HttpWriter(OrthancPluginRestOutput* output,
+                                            bool isXml) :
+    context_(OrthancPlugins::GetGlobalContext()),
+    output_(output),
+    isXml_(isXml),
+    first_(true)
+  {
+    if (context_ == NULL ||
+        output_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (isXml_)
+    {
+      OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml");
+    }
+    else
+    {
+      jsonBuffer_.AddChunk("[");
+    }
+  }
+
+
+  void DicomWebFormatter::HttpWriter::AddInternal(const void* dicom,
+                                                  size_t size,
+                                                  OrthancPluginDicomWebBinaryMode mode,
+                                                  const std::string& bulkRoot)
+  {
+    if (!first_ &&
+        !isXml_)
+    {
+      jsonBuffer_.AddChunk(",");      
+    }
+
+    first_ = false;
+
+    std::string item;
+
+    {
+      // TODO - Avoid a global mutex => Need to change Orthanc SDK
+      OrthancPlugins::DicomWebFormatter::Locker locker(mode, bulkRoot);
+      locker.Apply(item, context_, dicom, size, isXml_);
+    }
+   
+    if (isXml_)
+    {
+      OrthancPluginSendMultipartItem(context_, output_, item.c_str(), item.size());
+    }
+    else
+    {
+      jsonBuffer_.AddChunk(item);
+    }
+  }
+
+                  
+  void DicomWebFormatter::HttpWriter::AddJson(const Json::Value& value)
+  {
+    MemoryBuffer dicom;
+    dicom.CreateDicom(value, OrthancPluginCreateDicomFlags_None);
+
+    AddInternal(dicom.GetData(), dicom.GetSize(), OrthancPluginDicomWebBinaryMode_Ignore, "");
+  }
+
+
+  void DicomWebFormatter::HttpWriter::Send()
+  {
+    if (!isXml_)
+    {
+      jsonBuffer_.AddChunk("]");
+      
+      std::string answer;
+      jsonBuffer_.Flatten(answer);
+      OrthancPluginAnswerBuffer(context_, output_, answer.c_str(), answer.size(), "application/dicom+json");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebFormatter.h	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <Core/ChunkedBuffer.h>
+
+#include <orthanc/OrthancCPlugin.h>
+
+#include <json/value.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/thread/mutex.hpp>
+
+
+namespace OrthancPlugins
+{
+  class DicomWebFormatter : public boost::noncopyable
+  {
+  private:
+    boost::mutex                     mutex_;
+    OrthancPluginDicomWebBinaryMode  mode_;
+    std::string                      bulkRoot_;
+
+    static DicomWebFormatter& GetSingleton()
+    {
+      static DicomWebFormatter formatter;
+      return formatter;
+    }
+
+    static void Callback(OrthancPluginDicomWebNode *node,
+                         OrthancPluginDicomWebSetBinaryNode setter,
+                         uint32_t levelDepth,
+                         const uint16_t *levelTagGroup,
+                         const uint16_t *levelTagElement,
+                         const uint32_t *levelIndex,
+                         uint16_t tagGroup,
+                         uint16_t tagElement,
+                         OrthancPluginValueRepresentation vr);
+
+  public:
+    class Locker : public boost::noncopyable
+    {
+    private:
+      DicomWebFormatter&         that_;
+      boost::mutex::scoped_lock  lock_;
+
+    public:
+      Locker(OrthancPluginDicomWebBinaryMode mode,
+             const std::string& bulkRoot);
+
+      void Apply(std::string& target,
+                 OrthancPluginContext* context,
+                 const void* data,
+                 size_t size,
+                 bool xml);
+
+      void Apply(std::string& target,
+                 OrthancPluginContext* context,
+                 const Json::Value& value,
+                 bool xml);
+    };
+
+    class HttpWriter : public boost::noncopyable
+    {
+    private:
+      OrthancPluginContext*     context_;
+      OrthancPluginRestOutput*  output_;
+      bool                      isXml_;
+      bool                      first_;
+      Orthanc::ChunkedBuffer    jsonBuffer_;
+
+      void AddInternal(const void* dicom,
+                       size_t size,
+                       OrthancPluginDicomWebBinaryMode mode,
+                       const std::string& bulkRoot);
+
+    public:
+      HttpWriter(OrthancPluginRestOutput* output,
+                 bool isXml);
+
+      void AddDicom(const void* dicom,
+                    size_t size,
+                    const std::string& bulkRoot)
+      {
+        AddInternal(dicom, size, OrthancPluginDicomWebBinaryMode_BulkDataUri, bulkRoot);
+      }
+
+      void AddJson(const Json::Value& value);
+
+      void Send();
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebServers.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,296 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomWebServers.h"
+
+#include "Configuration.h"
+
+#include <Core/Toolbox.h>
+
+namespace OrthancPlugins
+{
+  void DicomWebServers::Clear()
+  {
+    for (Servers::iterator it = servers_.begin(); it != servers_.end(); ++it)
+    {
+      delete it->second;
+    }
+  }
+
+
+  void DicomWebServers::Load(const Json::Value& servers)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Clear();
+
+    bool ok = true;
+
+    try
+    {
+      if (servers.type() != Json::objectValue)
+      {
+        ok = false;
+      }
+      else
+      {
+        Json::Value::Members members = servers.getMemberNames();
+
+        for (size_t i = 0; i < members.size(); i++)
+        {
+          std::auto_ptr<Orthanc::WebServiceParameters> parameters
+            (new Orthanc::WebServiceParameters(servers[members[i]]));
+
+          servers_[members[i]] = parameters.release();
+        }
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      OrthancPlugins::LogError("Exception while parsing the \"DicomWeb.Servers\" section "
+                               "of the configuration file: " + std::string(e.What()));
+      throw;
+    }
+
+    if (!ok)
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_BadFileFormat,
+        "Cannot parse the \"DicomWeb.Servers\" section of the configuration file");
+    }
+  }
+
+
+  DicomWebServers& DicomWebServers::GetInstance()
+  {
+    static DicomWebServers singleton;
+    return singleton;
+  }
+
+
+  Orthanc::WebServiceParameters DicomWebServers::GetServer(const std::string& name)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    Servers::const_iterator server = servers_.find(name);
+
+    if (server == servers_.end() ||
+        server->second == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
+                                      "Inexistent server: " + name);
+    }
+    else
+    {
+      return *server->second;
+    }
+  }
+
+
+  void DicomWebServers::ListServers(std::list<std::string>& servers)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    servers.clear();
+    for (Servers::const_iterator it = servers_.begin(); it != servers_.end(); ++it)
+    {
+      servers.push_back(it->first);
+    }
+  }
+
+
+  static const char* ConvertToCString(const std::string& s)
+  {
+    if (s.empty())
+    {
+      return NULL;
+    }
+    else
+    {
+      return s.c_str();
+    }
+  }
+
+
+
+  void CallServer(OrthancPlugins::MemoryBuffer& answerBody /* out */,
+                  std::map<std::string, std::string>& answerHeaders /* out */,
+                  const Orthanc::WebServiceParameters& server,
+                  OrthancPluginHttpMethod method,
+                  const std::map<std::string, std::string>& httpHeaders,
+                  const std::string& uri,
+                  const std::string& body)
+  {
+    answerBody.Clear();
+    answerHeaders.clear();
+
+    std::string url = server.GetUrl();
+    assert(!url.empty() && url[url.size() - 1] == '/');
+
+    // Remove the leading "/" in the URI if need be
+    if (!uri.empty() &&
+        uri[0] == '/')
+    {
+      url += uri.substr(1);
+    }
+    else
+    {
+      url += uri;
+    }
+
+    std::map<std::string, std::string> allHttpHeaders = server.GetHttpHeaders();
+    
+    {
+      // Add the user-specified HTTP headers to the HTTP headers
+      // coming from the Orthanc configuration file
+      for (std::map<std::string, std::string>::const_iterator
+             it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
+      {
+        allHttpHeaders[it->first] = it->second;
+      }
+    }
+
+    std::vector<const char*> httpHeadersKeys(allHttpHeaders.size());
+    std::vector<const char*> httpHeadersValues(allHttpHeaders.size());
+
+    {
+      size_t pos = 0;
+      for (std::map<std::string, std::string>::const_iterator
+             it = allHttpHeaders.begin(); it != allHttpHeaders.end(); ++it)
+      {
+        httpHeadersKeys[pos] = it->first.c_str();
+        httpHeadersValues[pos] = it->second.c_str();
+        pos += 1;
+      }
+
+      assert(pos == allHttpHeaders.size());
+    }
+
+    const char* bodyContent = NULL;
+    size_t bodySize = 0;
+
+    if ((method == OrthancPluginHttpMethod_Put ||
+         method == OrthancPluginHttpMethod_Post) &&
+        !body.empty())
+    {
+      bodyContent = body.c_str();
+      bodySize = body.size();
+    }
+
+    OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+    uint16_t status = 0;
+    MemoryBuffer answerHeadersTmp;
+    OrthancPluginErrorCode code = OrthancPluginHttpClient(
+      context, 
+      /* Outputs */
+      *answerBody, *answerHeadersTmp, &status, 
+      method,
+      url.c_str(), 
+      /* HTTP headers*/
+      allHttpHeaders.size(),
+      httpHeadersKeys.empty() ? NULL : &httpHeadersKeys[0],
+      httpHeadersValues.empty() ? NULL : &httpHeadersValues[0],
+      bodyContent, bodySize,
+      ConvertToCString(server.GetUsername()), /* Authentication */
+      ConvertToCString(server.GetPassword()), 
+      0,                                      /* Timeout */
+      ConvertToCString(server.GetCertificateFile()),
+      ConvertToCString(server.GetCertificateKeyFile()),
+      ConvertToCString(server.GetCertificateKeyPassword()),
+      server.IsPkcs11Enabled() ? 1 : 0);
+
+    if (code != OrthancPluginErrorCode_Success ||
+        (status < 200 || status >= 300))
+    {
+      throw Orthanc::OrthancException(
+        static_cast<Orthanc::ErrorCode>(code),
+        "Cannot issue an HTTP query to " + url + 
+        " (HTTP status: " + boost::lexical_cast<std::string>(status) + ")");
+    }
+
+    Json::Value json;
+    answerHeadersTmp.ToJson(json);
+    answerHeadersTmp.Clear();
+
+    if (json.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    Json::Value::Members members = json.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const std::string& key = members[i];
+
+      if (json[key].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      else
+      {
+        answerHeaders[key] = json[key].asString();        
+      }
+    }
+  }
+
+
+  void UriEncode(std::string& uri,
+                 const std::string& resource,
+                 const std::map<std::string, std::string>& getArguments)
+  {
+    if (resource.find('?') != std::string::npos)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "The GET arguments must be provided in a separate field "
+                                      "(explicit \"?\" is disallowed): " + resource);
+    }
+
+    uri = resource;
+
+    bool isFirst = true;
+    for (std::map<std::string, std::string>::const_iterator
+           it = getArguments.begin(); it != getArguments.end(); ++it)
+    {
+      if (isFirst)
+      {
+        uri += '?';
+        isFirst = false;
+      }
+      else
+      {
+        uri += '&';
+      }
+
+      std::string key, value;
+      Orthanc::Toolbox::UriEncode(key, it->first);
+      Orthanc::Toolbox::UriEncode(value, it->second);
+
+      if (value.empty())
+      {
+        uri += key;
+      }
+      else
+      {
+        uri += key + "=" + value;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebServers.h	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <Core/WebServiceParameters.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <list>
+#include <string>
+#include <boost/thread/mutex.hpp>
+#include <json/value.h>
+
+namespace OrthancPlugins
+{
+  class DicomWebServers
+  {
+  private:
+    typedef std::map<std::string, Orthanc::WebServiceParameters*>  Servers;
+
+    boost::mutex  mutex_;
+    Servers       servers_;
+
+    void Clear();
+
+    DicomWebServers()  // Forbidden (singleton pattern)
+    {
+    }
+
+  public:
+    void Load(const Json::Value& configuration);
+
+    ~DicomWebServers()
+    {
+      Clear();
+    }
+
+    static DicomWebServers& GetInstance();
+
+    Orthanc::WebServiceParameters GetServer(const std::string& name);
+
+    void ListServers(std::list<std::string>& servers);
+  };
+
+
+  void CallServer(OrthancPlugins::MemoryBuffer& answerBody /* out */,
+                  std::map<std::string, std::string>& answerHeaders /* out */,
+                  const Orthanc::WebServiceParameters& server,
+                  OrthancPluginHttpMethod method,
+                  const std::map<std::string, std::string>& httpHeaders,
+                  const std::string& uri,
+                  const std::string& body);
+
+  void UriEncode(std::string& uri,
+                 const std::string& resource,
+                 const std::map<std::string, std::string>& getArguments);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/GdcmParsedDicomFile.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,435 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GdcmParsedDicomFile.h"
+
+#include "ChunkedBuffer.h"
+
+#include <Core/Toolbox.h>
+
+#include <gdcmDict.h>
+#include <gdcmDictEntry.h>
+#include <gdcmDicts.h>
+#include <gdcmGlobal.h>
+#include <gdcmStringFilter.h>
+
+#include <boost/lexical_cast.hpp>
+#include <json/writer.h>
+
+
+namespace OrthancPlugins
+{
+  static const gdcm::Dict* dictionary_ = NULL;
+
+  
+  void GdcmParsedDicomFile::Initialize()
+  {
+    if (dictionary_ != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      dictionary_ = &gdcm::Global::GetInstance().GetDicts().GetPublicDict();
+
+      if (dictionary_ == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                        "Cannot initialize the DICOM dictionary of GDCM");
+      }
+    }
+  }
+  
+
+  static std::string MyStripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           (isspace(source[first]) || 
+            source[first] == '\0'))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           (isspace(source[last - 1]) ||
+            source[last - 1] == '\0'))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  static const char* GetVRName(bool& isSequence,
+                               const gdcm::Tag& tag,
+                               gdcm::VR vr)
+  {
+    if (dictionary_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                      "GDCM has not been initialized");
+    }
+    
+    if (vr == gdcm::VR::INVALID)
+    {
+      const gdcm::DictEntry &entry = dictionary_->GetDictEntry(tag);
+      vr = entry.GetVR();
+
+      if (vr == gdcm::VR::OB_OW)
+      {
+        vr = gdcm::VR::OB;
+      }
+    }
+
+    isSequence = (vr == gdcm::VR::SQ);
+
+    const char* str = gdcm::VR::GetVRString(vr);
+    if (isSequence)
+    {
+      return str;
+    }
+
+    if (str == NULL ||
+        strlen(str) != 2 ||
+        !(str[0] >= 'A' && str[0] <= 'Z') ||
+        !(str[1] >= 'A' && str[1] <= 'Z'))
+    {
+      return "UN";
+    }
+    else
+    {
+      return str;
+    }
+  }
+
+
+  static const char* GetVRName(bool& isSequence,
+                               const gdcm::DataElement& element)
+  {
+    return GetVRName(isSequence, element.GetTag(), element.GetVR());
+  }
+
+
+  template <int T>
+  static void ConvertNumberTag(std::string& target,
+                               const gdcm::DataElement& source)
+  {
+    if (source.IsEmpty())
+    {
+      target.clear();
+    }
+    else
+    {
+      typename gdcm::Element<T, gdcm::VM::VM1_n> element;
+
+      element.Set(source.GetValue());
+
+      for (unsigned int i = 0; i < element.GetLength(); i++)
+      {
+        if (i != 0)
+        {
+          target += "\\";
+        }
+      
+        target = boost::lexical_cast<std::string>(element.GetValue());
+      }
+    }
+  }
+
+
+  static bool ConvertDicomStringToUtf8(std::string& result,
+                                       const gdcm::DataElement& element,
+                                       const Orthanc::Encoding sourceEncoding)
+  {
+    const gdcm::ByteValue* data = element.GetByteValue();
+    if (!data)
+    {
+      return false;
+    }
+
+    bool isSequence;
+    std::string vr = GetVRName(isSequence, element);
+
+    if (!isSequence)
+    {
+      if (vr == "FL")
+      {
+        ConvertNumberTag<gdcm::VR::FL>(result, element);
+        return true;
+      }
+      else if (vr == "FD")
+      {
+        ConvertNumberTag<gdcm::VR::FD>(result, element);
+        return true;
+      }
+      else if (vr == "SL")
+      {
+        ConvertNumberTag<gdcm::VR::SL>(result, element);
+        return true;
+      }
+      else if (vr == "SS")
+      {
+        ConvertNumberTag<gdcm::VR::SS>(result, element);
+        return true;
+      }
+      else if (vr == "UL")
+      {
+        ConvertNumberTag<gdcm::VR::UL>(result, element);
+        return true;
+      }
+      else if (vr == "US")
+      {
+        ConvertNumberTag<gdcm::VR::US>(result, element);
+        return true;
+      }
+    }
+
+    if (sourceEncoding == Orthanc::Encoding_Utf8)
+    {
+      result.assign(data->GetPointer(), data->GetLength());
+    }
+    else
+    {
+      std::string tmp(data->GetPointer(), data->GetLength());
+      result = Orthanc::Toolbox::ConvertToUtf8(tmp, sourceEncoding, false);
+    }
+
+    result = MyStripSpaces(result);
+    return true;
+  }
+
+
+
+  void GdcmParsedDicomFile::Setup(const std::string& dicom)
+  {
+    // Prepare a memory stream over the DICOM instance
+    std::stringstream stream(dicom);
+
+    // Parse the DICOM instance using GDCM
+    reader_.SetStream(stream);
+
+    if (!reader_.Read())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "GDCM cannot decode this DICOM instance of length " +
+                                      boost::lexical_cast<std::string>(dicom.size()));
+    }
+  }
+
+
+  GdcmParsedDicomFile::GdcmParsedDicomFile(const OrthancPlugins::MemoryBuffer& buffer)
+  {
+    // TODO Avoid this unnecessary memcpy by defining a stream over the MemoryBuffer
+    std::string dicom(buffer.GetData(), buffer.GetData() + buffer.GetSize());
+    Setup(dicom);
+  }
+
+
+  static bool GetRawTag(std::string& result,
+                        const gdcm::DataSet& dataset,
+                        const gdcm::Tag& tag,
+                        bool stripSpaces)
+  {
+    if (dataset.FindDataElement(tag))
+    {
+      const gdcm::ByteValue* value = dataset.GetDataElement(tag).GetByteValue();
+      if (value)
+      {
+        result.assign(value->GetPointer(), value->GetLength());
+
+        if (stripSpaces)
+        {
+          result = MyStripSpaces(result);
+        }
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  bool GdcmParsedDicomFile::GetRawTag(std::string& result,
+                                      const gdcm::Tag& tag,
+                                      bool stripSpaces) const
+  {
+    return OrthancPlugins::GetRawTag(result, GetDataSet(), tag, stripSpaces);
+  }
+
+
+  std::string GdcmParsedDicomFile::GetRawTagWithDefault(const gdcm::Tag& tag,
+                                                        const std::string& defaultValue,
+                                                        bool stripSpaces) const
+  {
+    std::string result;
+    if (!GetRawTag(result, tag, stripSpaces))
+    {
+      return defaultValue;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  std::string GdcmParsedDicomFile::GetRawTagWithDefault(const Orthanc::DicomTag& tag,
+                                                        const std::string& defaultValue,
+                                                        bool stripSpaces) const
+  {
+    gdcm::Tag t(tag.GetGroup(), tag.GetElement());
+    return GetRawTagWithDefault(t, defaultValue, stripSpaces);
+  }
+
+
+  bool GdcmParsedDicomFile::GetStringTag(std::string& result,
+                                         const gdcm::Tag& tag,
+                                         bool stripSpaces) const
+  {
+    if (!GetDataSet().FindDataElement(tag))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
+    }
+
+    const gdcm::DataElement& element = GetDataSet().GetDataElement(tag);
+
+    if (!ConvertDicomStringToUtf8(result, element, GetEncoding()))
+    {
+      return false;
+    }
+
+    if (stripSpaces)
+    {
+      result = MyStripSpaces(result);
+    }
+
+    return true;
+  }
+
+
+  bool GdcmParsedDicomFile::GetIntegerTag(int& result,
+                                          const gdcm::Tag& tag) const
+  {
+    std::string tmp;
+    if (!GetStringTag(tmp, tag, true))
+    {
+      return false;
+    }
+
+    try
+    {
+      result = boost::lexical_cast<int>(tmp);
+      return true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      return false;
+    }
+  }
+
+
+  std::string FormatTag(const gdcm::Tag& tag)
+  {
+    char tmp[16];
+    sprintf(tmp, "%04X%04X", tag.GetGroup(), tag.GetElement());
+    return std::string(tmp);
+  }
+
+
+  static std::string GetWadoUrl(const std::string& wadoBase,
+                                const gdcm::DataSet& dicom)
+  {
+    static const gdcm::Tag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
+    static const gdcm::Tag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+    static const gdcm::Tag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+
+    std::string study, series, instance;
+
+    if (!GetRawTag(study, dicom, DICOM_TAG_STUDY_INSTANCE_UID, true) ||
+        !GetRawTag(series, dicom, DICOM_TAG_SERIES_INSTANCE_UID, true) ||
+        !GetRawTag(instance, dicom, DICOM_TAG_SOP_INSTANCE_UID, true))
+    {
+      return "";
+    }
+    else
+    {
+      return Configuration::GetWadoUrl(wadoBase, study, series, instance);
+    }
+  }
+
+
+  static Orthanc::Encoding DetectEncoding(const gdcm::DataSet& dicom)
+  {
+    static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
+
+    if (!dicom.FindDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET))
+    {
+      return Orthanc::Encoding_Ascii;
+    }
+
+    const gdcm::DataElement& element = 
+      dicom.GetDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET);
+
+    const gdcm::ByteValue* data = element.GetByteValue();
+    if (!data)
+    {
+      return Configuration::GetDefaultEncoding();
+    }
+
+    std::string tmp(data->GetPointer(), data->GetLength());
+    tmp = MyStripSpaces(tmp);
+
+    Orthanc::Encoding encoding;
+    if (Orthanc::GetDicomEncoding(encoding, tmp.c_str()))
+    {
+      return encoding;
+    }
+    else
+    {
+      return Configuration::GetDefaultEncoding();
+    }
+  }
+
+
+  Orthanc::Encoding  GdcmParsedDicomFile::GetEncoding() const
+  {
+    return DetectEncoding(GetDataSet());
+  }
+  
+
+  std::string GdcmParsedDicomFile::GetWadoUrl(const OrthancPluginHttpRequest* request) const
+  {
+    const std::string base = OrthancPlugins::Configuration::GetBaseUrl(request);
+    return OrthancPlugins::GetWadoUrl(base, GetDataSet());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/GdcmParsedDicomFile.h	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Configuration.h"
+
+#include <Core/ChunkedBuffer.h>
+#include <Core/Enumerations.h>
+#include <Core/DicomFormat/DicomTag.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <gdcmReader.h>
+#include <gdcmDataSet.h>
+#include <pugixml.hpp>
+#include <list>
+
+
+namespace OrthancPlugins
+{
+  class GdcmParsedDicomFile : public boost::noncopyable
+  {
+  private:
+    gdcm::Reader reader_;
+
+    void Setup(const std::string& dicom);
+
+    Orthanc::Encoding  GetEncoding() const;
+
+  public:
+    static void Initialize();
+    
+    explicit GdcmParsedDicomFile(const OrthancPlugins::MemoryBuffer& item);
+
+    explicit GdcmParsedDicomFile(const std::string& dicom)
+    {
+      Setup(dicom);
+    }
+
+    const gdcm::File& GetFile() const
+    {
+      return reader_.GetFile();
+    }
+
+    const gdcm::DataSet& GetDataSet() const
+    {
+      return reader_.GetFile().GetDataSet();
+    }
+
+    bool GetRawTag(std::string& result,
+                   const gdcm::Tag& tag,
+                   bool stripSpaces) const;
+
+    std::string GetRawTagWithDefault(const gdcm::Tag& tag,
+                                     const std::string& defaultValue,
+                                     bool stripSpaces) const;
+
+    std::string GetRawTagWithDefault(const Orthanc::DicomTag& tag,
+                                     const std::string& defaultValue,
+                                     bool stripSpaces) const;
+
+    bool GetStringTag(std::string& result,
+                      const gdcm::Tag& tag,
+                      bool stripSpaces) const;
+
+    bool GetIntegerTag(int& result,
+                       const gdcm::Tag& tag) const;
+
+    std::string GetWadoUrl(const OrthancPluginHttpRequest* request) const;
+  };
+}
--- a/Plugin/Plugin.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/Plugin.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -18,172 +19,423 @@
  **/
 
 
-#include "Plugin.h"
-
+#include "DicomWebClient.h"
+#include "DicomWebServers.h"
+#include "GdcmParsedDicomFile.h"
 #include "QidoRs.h"
 #include "StowRs.h"
 #include "WadoRs.h"
-#include "Wado.h"
-#include "Configuration.h"
-
+#include "WadoUri.h"
 
-#include <gdcmDictEntry.h>
-#include <gdcmDict.h>
-#include <gdcmDicts.h>
-#include <gdcmGlobal.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Toolbox.h>
 
 
-// Global state
-OrthancPluginContext* context_ = NULL;
-Json::Value configuration_;
-const gdcm::Dict* dictionary_ = NULL;
-
-
-static OrthancPluginErrorCode SwitchStudies(OrthancPluginRestOutput* output,
-                                            const char* url,
-                                            const OrthancPluginHttpRequest* request)
+void SwitchStudies(OrthancPluginRestOutput* output,
+                   const char* url,
+                   const OrthancPluginHttpRequest* request)
 {
   switch (request->method)
   {
     case OrthancPluginHttpMethod_Get:
       // This is QIDO-RS
-      return SearchForStudies(output, url, request);
+      SearchForStudies(output, url, request);
+      break;
 
     case OrthancPluginHttpMethod_Post:
       // This is STOW-RS
-      return StowCallback(output, url, request);
+      StowCallback(output, url, request);
+      break;
 
     default:
-      OrthancPluginSendMethodNotAllowed(context_, output, "GET,POST");
-      return OrthancPluginErrorCode_Success;
+      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");
+      break;
   }
 }
 
 
-static OrthancPluginErrorCode SwitchStudy(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request)
+void SwitchStudy(OrthancPluginRestOutput* output,
+                 const char* url,
+                 const OrthancPluginHttpRequest* request)
 {
   switch (request->method)
   {
     case OrthancPluginHttpMethod_Get:
       // This is WADO-RS
-      return RetrieveDicomStudy(output, url, request);
+      RetrieveDicomStudy(output, url, request);
+      break;
 
     case OrthancPluginHttpMethod_Post:
       // This is STOW-RS
-      return StowCallback(output, url, request);
+      StowCallback(output, url, request);
+      break;
 
     default:
-      OrthancPluginSendMethodNotAllowed(context_, output, "GET,POST");
-      return OrthancPluginErrorCode_Success;
+      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");
+      break;
+  }
+}
+
+bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key)
+{
+  for (uint32_t i = 0; i < request->getCount; i++)
+  {
+    if (strcmp(key, request->getKeys[i]) == 0)
+      return true;
+  }
+  return false;
+}
+
+
+void ListServers(OrthancPluginRestOutput* output,
+                 const char* url,
+                 const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    std::list<std::string> servers;
+    OrthancPlugins::DicomWebServers::GetInstance().ListServers(servers);
+
+    if (RequestHasKey(request, "expand"))
+    {
+      Json::Value result = Json::objectValue;
+      for (std::list<std::string>::const_iterator it = servers.begin(); it != servers.end(); ++it)
+      {
+        Orthanc::WebServiceParameters server = OrthancPlugins::DicomWebServers::GetInstance().GetServer(*it);
+        Json::Value jsonServer;
+        // only return the minimum information to identify the destination, do not include "security" information like passwords
+        jsonServer["Url"] = server.GetUrl();
+        if (!server.GetUsername().empty())
+        {
+          jsonServer["Username"] = server.GetUsername();
+        }
+        result[*it] = jsonServer;
+      }
+
+      std::string answer = result.toStyledString();
+      OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+    }
+    else // if expand is not present, keep backward compatibility and return an array of server names
+    {
+      Json::Value json = Json::arrayValue;
+      for (std::list<std::string>::const_iterator it = servers.begin(); it != servers.end(); ++it)
+      {
+        json.append(*it);
+      }
+
+      std::string answer = json.toStyledString();
+      OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+    }
+  }
+}
+
+void ListServerOperations(OrthancPluginRestOutput* output,
+                          const char* /*url*/,
+                          const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    // Make sure the server does exist
+    OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]);
+
+    Json::Value json = Json::arrayValue;
+    json.append("get");
+    json.append("retrieve");
+    json.append("stow");
+
+    std::string answer = json.toStyledString(); 
+    OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
   }
 }
 
 
-static void Register(const std::string& root,
-                     const std::string& uri,
-                     OrthancPluginRestCallback callback)
+static bool DisplayPerformanceWarning(OrthancPluginContext* context)
 {
-  assert(!uri.empty() && uri[0] != '/');
-  std::string s = root + uri;
-  OrthancPluginRegisterRestCallback(context_, s.c_str(), callback);
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context, "Performance warning in DICOMweb: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
 }
 
 
 
+#include <boost/filesystem.hpp>
+#include <Core/SystemToolbox.h>
+
+
+class StowServer : public OrthancPlugins::MultipartRestCallback
+{
+private:
+  class Handler : public IHandler
+  {
+  private:
+    unsigned int count_;
+
+  public:
+    Handler(unsigned int count) :
+    count_(count)
+    {
+      printf("  created handler: %d\n", count_);
+    }
+
+    virtual OrthancPluginErrorCode AddPart(const std::string& contentType,
+                                           const std::map<std::string, std::string>& headers,
+                                           const void* data,
+                                           size_t size)
+    {
+      printf("  %d - part received: [%s] %d\n", count_, contentType.c_str(), size);
+
+      Json::Value v;
+      OrthancPlugins::RestApiPost(v, "/instances", data, size, false);
+      std::cout << v << std::endl;
+
+      return OrthancPluginErrorCode_Success;
+    }
+
+    virtual OrthancPluginErrorCode Execute(OrthancPluginRestOutput* output)
+    {
+      printf("  %d - execute\n", count_);
+      //throw Orthanc::OrthancException(Orthanc::ErrorCode_CanceledJob);
+      
+      std::string s = "{}\n";
+
+      OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json");
+      return OrthancPluginErrorCode_Success;
+    }
+  };
+
+  unsigned int count_;
+
+public:
+  StowServer() : count_(0)
+  {
+  }
+
+  virtual IHandler* CreateHandler(OrthancPluginHttpMethod method,
+                                  const std::string& url,
+                                  const std::string& contentType,
+                                  const std::string& subType,
+                                  const std::vector<std::string>& groups,
+                                  const std::map<std::string, std::string>& headers)
+  {
+    printf("new handler: [%s] [%s]\n", contentType.c_str(), subType.c_str());
+    return new Handler(count_++);
+  }
+};
+
+
+static StowServer stowServer_;
+
+
+class StowClientBody : public OrthancPlugins::HttpClient::IRequestBody
+{
+private:
+  std::vector<std::string>  files_;
+  size_t                    position_;
+  std::string               boundary_;
+
+public:
+  StowClientBody() :
+    position_(0),
+    boundary_(Orthanc::Toolbox::GenerateUuid())
+  {
+    //boost::filesystem::path p("/home/jodogne/DICOM/Demo/KNIX/Knee (R)/AX.  FSE PD - 5");
+    boost::filesystem::path p("/tmp/dicom");
+
+    boost::filesystem::directory_iterator end;
+
+    // cycle through the directory
+    for (boost::filesystem::directory_iterator it(p); it != end; ++it)
+    {
+      if (is_regular_file(it->path()))
+      {
+        files_.push_back(it->path().string());
+      }
+    }
+  }
+
+  const std::string& GetBoundary() const
+  {
+    return boundary_;
+  }
+
+  virtual bool ReadNextChunk(std::string& chunk)
+  {
+    if (position_ == files_.size() + 1)
+    {
+      return false;
+    }
+    else
+    {
+      if (position_ == files_.size())
+      {
+        chunk = ("--" + boundary_ + "--");
+      }
+      else
+      {
+        std::string f;
+        Orthanc::SystemToolbox::ReadFile(f, files_[position_]);
+        chunk = ("--" + boundary_ + "\r\n" +
+                 "Content-Type: application/dicom\r\n" +
+                 "Content-Length: " + boost::lexical_cast<std::string>(f.size()) + "\r\n" +
+                 "\r\n" + f + "\r\n");
+      }
+
+      position_++;
+      return true;
+    }
+  }
+};
+
+
+ORTHANC_PLUGINS_API OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
+                                                            OrthancPluginResourceType resourceType,
+                                                            const char* resourceId)
+{
+  if (changeType == OrthancPluginChangeType_OrthancStarted)
+  {
+    try
+    {
+      StowClientBody stow;
+      
+      OrthancPlugins::HttpClient client;
+      client.SetUrl("http://localhost:8080/dicom-web/studies");
+      client.SetMethod(OrthancPluginHttpMethod_Post);
+      client.AddHeader("Accept", "application/dicom+json");
+      client.AddHeader("Expect", "");
+      client.AddHeader("Content-Type", "multipart/related; type=application/dicom; boundary=" + stow.GetBoundary());
+      client.SetTimeout(120);
+      client.SetBody(stow);
+
+      OrthancPlugins::HttpClient::HttpHeaders headers;
+      std::string answer;
+      client.Execute(headers, answer);
+
+      Json::Value v;
+      Json::Reader reader;
+      if (reader.parse(answer, v))
+      {
+        std::cout << v["00081190"].toStyledString() << std::endl;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "EXCEPTION: " << e.What();
+    }
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
-    context_ = context;
+    assert(DisplayPerformanceWarning(context));
+    OrthancPlugins::SetGlobalContext(context);
+    Orthanc::Logging::Initialize(context);
 
     /* Check the version of the Orthanc core */
-    if (OrthancPluginCheckVersion(context_) == 0)
+    if (OrthancPluginCheckVersion(context) == 0)
     {
       char info[1024];
       sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
-              context_->orthancVersion,
+              context->orthancVersion,
               ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
               ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
               ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
-      OrthancPluginLogError(context_, info);
+      OrthancPluginLogError(context, info);
       return -1;
     }
 
+    OrthancPluginSetDescription(context, "Implementation of DICOMweb (QIDO-RS, STOW-RS and WADO-RS) and WADO-URI.");
+
+    try
     {
-      std::string version(context_->orthancVersion);
-      if (version == "0.9.1")
+      // Read the configuration
+      OrthancPlugins::Configuration::Initialize();
+
+      //OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);  // TODO => REMOVE
+      //stowServer_.Register("/toto");  // TODO => REMOVE
+      
+      // Initialize GDCM
+      OrthancPlugins::GdcmParsedDicomFile::Initialize();
+
+      // Configure the DICOMweb callbacks
+      if (OrthancPlugins::Configuration::GetBooleanValue("Enable", true))
       {
-        OrthancPluginLogWarning(context_, "If using STOW-RS, the DICOMweb plugin can lead to "
-                                "deadlocks in Orthanc version 0.9.1. Please upgrade Orthanc!");
-      }
-    }
+        std::string root = OrthancPlugins::Configuration::GetRoot();
+        assert(!root.empty() && root[root.size() - 1] == '/');
 
-
-    OrthancPluginSetDescription(context_, "Implementation of DICOM Web (QIDO-RS, STOW-RS and WADO-RS) and WADO.");
+        OrthancPlugins::LogWarning("URI to the DICOMweb REST API: " + root);
 
-    // Read the configuration
-    dictionary_ = &gdcm::Global::GetInstance().GetDicts().GetPublicDict();
-
-    configuration_ = Json::objectValue;
+        OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "instances", true);
+        OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "series", true);    
+        OrthancPlugins::RegisterRestCallback<SwitchStudies>(root + "studies", true);
+        OrthancPlugins::RegisterRestCallback<SwitchStudy>(root + "studies/([^/]*)", true);
+        OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "studies/([^/]*)/instances", true);    
+        OrthancPlugins::RegisterRestCallback<RetrieveStudyMetadata>(root + "studies/([^/]*)/metadata", true);
+        OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "studies/([^/]*)/series", true);    
+        OrthancPlugins::RegisterRestCallback<RetrieveDicomSeries>(root + "studies/([^/]*)/series/([^/]*)", true);
+        OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "studies/([^/]*)/series/([^/]*)/instances", true);    
+        OrthancPlugins::RegisterRestCallback<RetrieveDicomInstance>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveBulkData>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/bulk/(.*)", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveInstanceMetadata>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/metadata", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveSeriesMetadata>(root + "studies/([^/]*)/series/([^/]*)/metadata", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFrames>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFrames>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)", true);
 
-    {
-      Json::Value tmp;
-      if (!OrthancPlugins::Configuration::Read(tmp, context) ||
-          tmp.type() != Json::objectValue)
+        OrthancPlugins::RegisterRestCallback<ListServers>(root + "servers", true);
+        OrthancPlugins::RegisterRestCallback<ListServerOperations>(root + "servers/([^/]*)", true);
+        OrthancPlugins::RegisterRestCallback<StowClient>(root + "servers/([^/]*)/stow", true);
+        OrthancPlugins::RegisterRestCallback<GetFromServer>(root + "servers/([^/]*)/get", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFromServer>(root + "servers/([^/]*)/retrieve", true);
+      }
+      else
       {
-        OrthancPluginLogError(context_, "Unable to read the configuration file");
-        return -1;
+        OrthancPlugins::LogWarning("DICOMweb support is disabled");
       }
 
-      if (tmp.isMember("DicomWeb") &&
-          tmp["DicomWeb"].type() == Json::objectValue)
+      // Configure the WADO callback
+      if (OrthancPlugins::Configuration::GetBooleanValue("EnableWado", true))
       {
-        configuration_ = tmp["DicomWeb"];
+        std::string wado = OrthancPlugins::Configuration::GetWadoRoot();
+        OrthancPlugins::LogWarning("URI to the WADO-URI API: " + wado);
+
+        OrthancPlugins::RegisterRestCallback<WadoUriCallback>(wado, true);
+      }
+      else
+      {
+        OrthancPlugins::LogWarning("WADO-URI support is disabled");
       }
     }
-
-    // Configure the DICOMweb callbacks
-    if (OrthancPlugins::Configuration::GetBoolValue(configuration_, "Enable", true))
+    catch (Orthanc::OrthancException& e)
     {
-      std::string root = OrthancPlugins::Configuration::GetRoot(configuration_);
-
-      std::string message = "URI to the DICOMweb REST API: " + root;
-      OrthancPluginLogWarning(context_, message.c_str());
-
-      Register(root, "instances", SearchForInstances);    
-      Register(root, "series", SearchForSeries);    
-      Register(root, "studies", SwitchStudies);
-      Register(root, "studies/([^/]*)", SwitchStudy);
-      Register(root, "studies/([^/]*)/instances", SearchForInstances);    
-      Register(root, "studies/([^/]*)/metadata", RetrieveStudyMetadata);
-      Register(root, "studies/([^/]*)/series", SearchForSeries);    
-      Register(root, "studies/([^/]*)/series/([^/]*)", RetrieveDicomSeries);
-      Register(root, "studies/([^/]*)/series/([^/]*)/instances", SearchForInstances);    
-      Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)", RetrieveDicomInstance);
-      Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/bulk/(.*)", RetrieveBulkData);
-      Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/metadata", RetrieveInstanceMetadata);
-      Register(root, "studies/([^/]*)/series/([^/]*)/metadata", RetrieveSeriesMetadata);
+      OrthancPlugins::LogError("Exception while initializing the DICOMweb plugin: " + 
+                               std::string(e.What()));
+      return -1;
     }
-    else
-    {
-      OrthancPluginLogWarning(context_, "DICOMweb support is disabled");
-    }
-
-    // Configure the WADO callback
-    if (OrthancPlugins::Configuration::GetBoolValue(configuration_, "EnableWado", true))
+    catch (...)
     {
-      std::string wado = OrthancPlugins::Configuration::GetWadoRoot(configuration_);
-
-      std::string message = "URI to the WADO API: " + wado;
-      OrthancPluginLogWarning(context_, message.c_str());
-
-      OrthancPluginRegisterRestCallback(context_, wado.c_str(), WadoCallback);
-    }
-    else
-    {
-      OrthancPluginLogWarning(context_, "WADO support is disabled");
+      OrthancPlugins::LogError("Exception while initializing the DICOMweb plugin");
+      return -1;
     }
 
     return 0;
--- a/Plugin/Plugin.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 <orthanc/OrthancCPlugin.h>
-#include <json/value.h>
-#include <gdcmDict.h>
-
-// Global state
-extern OrthancPluginContext* context_;
-extern Json::Value configuration_;
-extern const gdcm::Dict* dictionary_;
--- a/Plugin/QidoRs.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/QidoRs.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -20,183 +21,125 @@
 
 #include "QidoRs.h"
 
-#include "Plugin.h"
 #include "StowRs.h"  // For IsXmlExpected()
-#include "Dicom.h"
-#include "DicomResults.h"
 #include "Configuration.h"
-#include "../Orthanc/Core/Toolbox.h"
-#include "../Orthanc/Core/OrthancException.h"
+#include "DicomWebFormatter.h"
 
-#include <gdcmTag.h>
+#include <Core/DicomFormat/DicomTag.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
 #include <list>
-#include <stdexcept>
 #include <boost/lexical_cast.hpp>
-#include <gdcmDict.h>
-#include <gdcmDicts.h>
-#include <gdcmGlobal.h>
-#include <gdcmDictEntry.h>
 #include <boost/regex.hpp>
 #include <boost/algorithm/string/replace.hpp>
 
 
 namespace
 {
-
-  enum QueryLevel
+  static std::string GetOrthancTag(const Json::Value& source,
+                                   const Orthanc::DicomTag& tag,
+                                   const std::string& defaultValue)
   {
-    QueryLevel_Study,
-    QueryLevel_Series,
-    QueryLevel_Instance
-  };
+    const std::string s = tag.Format();
+
+    if (source.isMember(s))
+    {
+      switch (source[s].type())
+      {
+        case Json::stringValue:
+          return source[s].asString();
+
+          // The conversions below should *not* be necessary
+
+        case Json::intValue:
+          return boost::lexical_cast<std::string>(source[s].asInt());
+
+        case Json::uintValue:
+          return boost::lexical_cast<std::string>(source[s].asUInt());
+
+        case Json::realValue:
+          return boost::lexical_cast<std::string>(source[s].asFloat());
+
+        default:
+          return defaultValue;
+      }
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
 
 
   class ModuleMatcher
   {
-  private:
-    typedef std::map<gdcm::Tag, std::string>  Filters;
-
-    const gdcm::Dict&     dictionary_;
-    bool                  fuzzy_;
-    unsigned int          offset_;
-    unsigned int          limit_;
-    std::list<gdcm::Tag>  includeFields_;
-    bool                  includeAllFields_;
-    Filters               filters_;
-
-
+  public:
+    typedef std::map<Orthanc::DicomTag, std::string>  Filters;
 
-    static inline uint16_t GetCharValue(char c)
-    {
-      if (c >= '0' && c <= '9')
-        return c - '0';
-      else if (c >= 'a' && c <= 'f')
-        return c - 'a' + 10;
-      else if (c >= 'A' && c <= 'F')
-        return c - 'A' + 10;
-      else
-        return 0;
-    }
-
-    static inline uint16_t GetTagValue(const char* c)
-    {
-      return ((GetCharValue(c[0]) << 12) + 
-              (GetCharValue(c[1]) << 8) + 
-              (GetCharValue(c[2]) << 4) + 
-              GetCharValue(c[3]));
-    }
-
-
-    static std::string Format(const gdcm::Tag& tag)
-    {
-      char b[16];
-      sprintf(b, "%04x,%04x", tag.GetGroup(), tag.GetElement());
-      return std::string(b);
-    }
+  private:
+    bool                          fuzzy_;
+    unsigned int                  offset_;
+    unsigned int                  limit_;
+    std::list<Orthanc::DicomTag>  includeFields_;
+    bool                          includeAllFields_;
+    Filters                       filters_;
 
 
-    gdcm::Tag  ParseTag(const std::string& key) const
-    {
-      if (key.find('.') != std::string::npos)
-      {
-        std::string s = "This DICOMweb plugin does not support hierarchical queries: " + key;
-        OrthancPluginLogError(context_, s.c_str());
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      if (key.size() == 8 &&
-          isxdigit(key[0]) &&
-          isxdigit(key[1]) &&
-          isxdigit(key[2]) &&
-          isxdigit(key[3]) &&
-          isxdigit(key[4]) &&
-          isxdigit(key[5]) &&
-          isxdigit(key[6]) &&
-          isxdigit(key[7]))        
-      {
-        return gdcm::Tag(GetTagValue(key.c_str()),
-                         GetTagValue(key.c_str() + 4));
-      }
-      else
-      {
-        gdcm::Tag tag;
-        dictionary_.GetDictEntryByKeyword(key.c_str(), tag);
-
-        if (tag.IsIllegal() || tag.IsPrivate())
-        {
-          if (key.find('.') != std::string::npos)
-          {
-            std::string s = "This QIDO-RS implementation does not support search over sequences: " + key;
-            OrthancPluginLogError(context_, s.c_str());
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          }
-          else
-          {
-            std::string s = "Illegal tag name in QIDO-RS: " + key;
-            OrthancPluginLogError(context_, s.c_str());
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownDicomTag);
-          }
-        }
-
-        return tag;
-      }
-    }
-
-
-    static void AddResultAttributesForLevel(std::list<gdcm::Tag>& result,
-                                            QueryLevel level)
+    static void AddResultAttributesForLevel(std::list<Orthanc::DicomTag>& result,
+                                            Orthanc::ResourceType level)
     {
       switch (level)
       {
-        case QueryLevel_Study:
+        case Orthanc::ResourceType_Study:
           // http://medical.nema.org/medical/dicom/current/output/html/part18.html#table_6.7.1-2
-          result.push_back(gdcm::Tag(0x0008, 0x0005));  // Specific Character Set
-          result.push_back(gdcm::Tag(0x0008, 0x0020));  // Study Date
-          result.push_back(gdcm::Tag(0x0008, 0x0030));  // Study Time
-          result.push_back(gdcm::Tag(0x0008, 0x0050));  // Accession Number
-          result.push_back(gdcm::Tag(0x0008, 0x0056));  // Instance Availability
-          result.push_back(gdcm::Tag(0x0008, 0x0061));  // Modalities in Study
-          result.push_back(gdcm::Tag(0x0008, 0x0090));  // Referring Physician's Name
-          result.push_back(gdcm::Tag(0x0008, 0x0201));  // Timezone Offset From UTC
-          //result.push_back(gdcm::Tag(0x0008, 0x1190));  // Retrieve URL  => SPECIAL CASE
-          result.push_back(gdcm::Tag(0x0010, 0x0010));  // Patient's Name
-          result.push_back(gdcm::Tag(0x0010, 0x0020));  // Patient ID
-          result.push_back(gdcm::Tag(0x0010, 0x0030));  // Patient's Birth Date
-          result.push_back(gdcm::Tag(0x0010, 0x0040));  // Patient's Sex
-          result.push_back(gdcm::Tag(0x0020, 0x000D));  // Study Instance UID
-          result.push_back(gdcm::Tag(0x0020, 0x0010));  // Study ID
-          result.push_back(gdcm::Tag(0x0020, 0x1206));  // Number of Study Related Series
-          result.push_back(gdcm::Tag(0x0020, 0x1208));  // Number of Study Related Instances
+          //result.push_back(Orthanc::DicomTag(0x0008, 0x0005));  // Specific Character Set  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0020));  // Study Date
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0030));  // Study Time
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0050));  // Accession Number
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0056));  // Instance Availability
+          //result.push_back(Orthanc::DicomTag(0x0008, 0x0061));  // Modalities in Study  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0090));  // Referring Physician's Name
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+          //result.push_back(Orthanc::DicomTag(0x0008, 0x1190));  // Retrieve URL  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0010, 0x0010));  // Patient's Name
+          result.push_back(Orthanc::DicomTag(0x0010, 0x0020));  // Patient ID
+          result.push_back(Orthanc::DicomTag(0x0010, 0x0030));  // Patient's Birth Date
+          result.push_back(Orthanc::DicomTag(0x0010, 0x0040));  // Patient's Sex
+          result.push_back(Orthanc::DicomTag(0x0020, 0x000D));  // Study Instance UID
+          result.push_back(Orthanc::DicomTag(0x0020, 0x0010));  // Study ID
+          //result.push_back(Orthanc::DicomTag(0x0020, 0x1206));  // Number of Study Related Series  => SPECIAL CASE
+          //result.push_back(Orthanc::DicomTag(0x0020, 0x1208));  // Number of Study Related Instances  => SPECIAL CASE
           break;
 
-        case QueryLevel_Series:
+        case Orthanc::ResourceType_Series:
           // http://medical.nema.org/medical/dicom/current/output/html/part18.html#table_6.7.1-2a
-          result.push_back(gdcm::Tag(0x0008, 0x0005));  // Specific Character Set
-          result.push_back(gdcm::Tag(0x0008, 0x0056));  // Modality
-          result.push_back(gdcm::Tag(0x0008, 0x0201));  // Timezone Offset From UTC
-          result.push_back(gdcm::Tag(0x0008, 0x103E));  // Series Description
-          //result.push_back(gdcm::Tag(0x0008, 0x1190));  // Retrieve URL  => SPECIAL CASE
-          result.push_back(gdcm::Tag(0x0020, 0x000E));  // Series Instance UID
-          result.push_back(gdcm::Tag(0x0020, 0x0011));  // Series Number
-          result.push_back(gdcm::Tag(0x0020, 0x1209));  // Number of Series Related Instances
-          result.push_back(gdcm::Tag(0x0040, 0x0244));  // Performed Procedure Step Start Date
-          result.push_back(gdcm::Tag(0x0040, 0x0245));  // Performed Procedure Step Start Time
-          result.push_back(gdcm::Tag(0x0040, 0x0275));  // Request Attribute Sequence
+          //result.push_back(Orthanc::DicomTag(0x0008, 0x0005));  // Specific Character Set  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0060));  // Modality
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+          result.push_back(Orthanc::DicomTag(0x0008, 0x103E));  // Series Description
+          //result.push_back(Orthanc::DicomTag(0x0008, 0x1190));  // Retrieve URL  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0020, 0x000E));  // Series Instance UID
+          result.push_back(Orthanc::DicomTag(0x0020, 0x0011));  // Series Number
+          //result.push_back(Orthanc::DicomTag(0x0020, 0x1209));  // Number of Series Related Instances  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0040, 0x0244));  // Performed Procedure Step Start Date
+          result.push_back(Orthanc::DicomTag(0x0040, 0x0245));  // Performed Procedure Step Start Time
+          result.push_back(Orthanc::DicomTag(0x0040, 0x0275));  // Request Attribute Sequence
           break;
 
-        case QueryLevel_Instance:
+        case Orthanc::ResourceType_Instance:
           // http://medical.nema.org/medical/dicom/current/output/html/part18.html#table_6.7.1-2b
-          result.push_back(gdcm::Tag(0x0008, 0x0005));  // Specific Character Set
-          result.push_back(gdcm::Tag(0x0008, 0x0016));  // SOP Class UID
-          result.push_back(gdcm::Tag(0x0008, 0x0018));  // SOP Instance UID
-          result.push_back(gdcm::Tag(0x0008, 0x0056));  // Instance Availability
-          result.push_back(gdcm::Tag(0x0008, 0x0201));  // Timezone Offset From UTC
-          result.push_back(gdcm::Tag(0x0008, 0x1190));  // Retrieve URL
-          result.push_back(gdcm::Tag(0x0020, 0x0013));  // Instance Number
-          result.push_back(gdcm::Tag(0x0028, 0x0010));  // Rows
-          result.push_back(gdcm::Tag(0x0028, 0x0011));  // Columns
-          result.push_back(gdcm::Tag(0x0028, 0x0100));  // Bits Allocated
-          result.push_back(gdcm::Tag(0x0028, 0x0008));  // Number of Frames
+          //result.push_back(Orthanc::DicomTag(0x0008, 0x0005));  // Specific Character Set  => SPECIAL CASE
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0016));  // SOP Class UID
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0018));  // SOP Instance UID
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0056));  // Instance Availability
+          result.push_back(Orthanc::DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+          result.push_back(Orthanc::DicomTag(0x0008, 0x1190));  // Retrieve URL
+          result.push_back(Orthanc::DicomTag(0x0020, 0x0013));  // Instance Number
+          result.push_back(Orthanc::DicomTag(0x0028, 0x0010));  // Rows
+          result.push_back(Orthanc::DicomTag(0x0028, 0x0011));  // Columns
+          result.push_back(Orthanc::DicomTag(0x0028, 0x0100));  // Bits Allocated
+          result.push_back(Orthanc::DicomTag(0x0028, 0x0008));  // Number of Frames
           break;
 
         default:
@@ -205,19 +148,20 @@
     }
 
 
-
   public:
-    ModuleMatcher(const OrthancPluginHttpRequest* request) :
-    dictionary_(gdcm::Global::GetInstance().GetDicts().GetPublicDict()),
-    fuzzy_(false),
-    offset_(0),
-    limit_(0),
-    includeAllFields_(false)
+    explicit ModuleMatcher(const OrthancPluginHttpRequest* request) :
+      fuzzy_(false),
+      offset_(0),
+      limit_(0),
+      includeAllFields_(false)
     {
+      std::string args;
+      
       for (uint32_t i = 0; i < request->getCount; i++)
       {
         std::string key(request->getKeys[i]);
         std::string value(request->getValues[i]);
+        args += " [" + key + "=" + value + "]";
 
         if (key == "limit")
         {
@@ -239,14 +183,14 @@
           }
           else
           {
-            std::string s = "Not a proper value for fuzzy matching (true or false): " + value;
-            OrthancPluginLogError(context_, s.c_str());
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
+            throw Orthanc::OrthancException(
+              Orthanc::ErrorCode_BadRequest,
+              "Not a proper value for fuzzy matching (true or false): " + value);
           }
         }
         else if (key == "includefield")
         {
-          if (key == "all")
+          if (value == "all")
           {
             includeAllFields_ = true;
           }
@@ -255,17 +199,28 @@
             // Split a comma-separated list of tags
             std::vector<std::string> tags;
             Orthanc::Toolbox::TokenizeString(tags, value, ',');
+            
             for (size_t i = 0; i < tags.size(); i++)
             {
-              includeFields_.push_back(ParseTag(tags[i]));
+              Orthanc::DicomTag tag(0, 0);
+              if (OrthancPlugins::ParseTag(tag, tags[i]))
+              {
+                includeFields_.push_back(tag);
+              }
             }
           }
         }
         else
         {
-          filters_[ParseTag(key)] = value;
+          Orthanc::DicomTag tag(0, 0);
+          if (OrthancPlugins::ParseTag(tag, key))
+          {
+            filters_[tag] = value;
+          }
         }
       }
+
+      OrthancPlugins::LogInfo("Arguments of QIDO-RS request:" + args);
     }
 
     unsigned int GetLimit() const
@@ -278,7 +233,7 @@
       return offset_;
     }
 
-    void AddFilter(const gdcm::Tag& tag,
+    void AddFilter(const Orthanc::DicomTag& tag,
                    const std::string& constraint)
     {
       filters_[tag] = constraint;
@@ -294,21 +249,21 @@
     }
 
     void ConvertToOrthanc(Json::Value& result,
-                          QueryLevel level) const
+                          Orthanc::ResourceType level) const
     {
       result = Json::objectValue;
 
       switch (level)
       {
-        case QueryLevel_Study:
+        case Orthanc::ResourceType_Study:
           result["Level"] = "Study";
           break;
 
-        case QueryLevel_Series:
+        case Orthanc::ResourceType_Series:
           result["Level"] = "Series";
           break;
 
-        case QueryLevel_Instance:
+        case Orthanc::ResourceType_Instance:
           result["Level"] = "Instance";
           break;
 
@@ -317,23 +272,111 @@
       }
 
       result["Expand"] = false;
-      result["CaseSensitive"] = true;
+      result["CaseSensitive"] = OrthancPlugins::Configuration::GetBooleanValue("QidoCaseSensitive", true);
       result["Query"] = Json::objectValue;
+      result["Limit"] = limit_;
+      result["Since"] = offset_;
 
+      if (offset_ != 0 &&
+          !OrthancPlugins::CheckMinimalOrthancVersion(1, 3, 0))
+      {
+        OrthancPlugins::LogError(
+          "QIDO-RS request with \"offset\" argument: "
+          "Only available if the Orthanc core version is >= 1.3.0");
+      }
+      
       for (Filters::const_iterator it = filters_.begin(); 
            it != filters_.end(); ++it)
       {
-        result["Query"][Format(it->first)] = it->second;
+        result["Query"][it->first.Format()] = it->second;
       }
     }
 
 
-    void ExtractFields(gdcm::DataSet& result,
-                       const OrthancPlugins::ParsedDicomFile& dicom,
+    void ComputeDerivedTags(Filters& target,
+                            Orthanc::ResourceType level,
+                            const std::string& resource) const
+    {
+      target.clear();
+
+      switch (level)
+      {
+        case Orthanc::ResourceType_Study:
+        {
+          Json::Value series, instances;
+          if (OrthancPlugins::RestApiGet(series, "/studies/" + resource + "/series?expand", false) &&
+              OrthancPlugins::RestApiGet(instances, "/studies/" + resource + "/instances", false))
+          {
+            target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES] = 
+              boost::lexical_cast<std::string>(series.size());
+            target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES] = 
+              boost::lexical_cast<std::string>(instances.size());
+
+            // Collect the Modality of all the child series
+            std::set<std::string> modalities;
+            for (Json::Value::ArrayIndex i = 0; i < series.size(); i++)
+            {
+              if (series[i].isMember("MainDicomTags") &&
+                  series[i]["MainDicomTags"].isMember("Modality"))
+              {
+                modalities.insert(series[i]["MainDicomTags"]["Modality"].asString());
+              }
+            }
+
+            std::string s;
+            for (std::set<std::string>::const_iterator 
+                   it = modalities.begin(); it != modalities.end(); ++it)
+            {
+              if (!s.empty())
+              {
+                s += "\\";
+              }
+
+              s += *it;
+            }
+
+            target[Orthanc::DICOM_TAG_MODALITIES_IN_STUDY] = s;
+          }
+          else
+          {
+            target[Orthanc::DICOM_TAG_MODALITIES_IN_STUDY] = "";
+            target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES] = "0";
+            target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES] = "0"; 
+          }
+
+          break;
+        }
+
+        case Orthanc::ResourceType_Series:
+        {
+          Json::Value instances;
+          if (OrthancPlugins::RestApiGet(instances, "/series/" + resource + "/instances", false))
+          {
+            // Number of Series Related Instances
+            target[Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES] = 
+              boost::lexical_cast<std::string>(instances.size());
+          }
+          else
+          {
+            target[Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES] = "0";
+          }
+
+          break;
+        }
+
+        default:
+          break;
+      }
+    }                              
+
+
+    void ExtractFields(Json::Value& result,
+                       const Json::Value& source,
                        const std::string& wadoBase,
-                       QueryLevel level) const
+                       Orthanc::ResourceType level) const
     {
-      std::list<gdcm::Tag> fields = includeFields_;
+      result = Json::objectValue;
+      std::list<Orthanc::DicomTag> fields = includeFields_;
 
       // The list of attributes for this query level
       AddResultAttributesForLevel(fields, level);
@@ -347,50 +390,53 @@
 
       // For instances and series, add all Study-level attributes if
       // {StudyInstanceUID} is not specified.
-      if ((level == QueryLevel_Instance  || level == QueryLevel_Series) 
-          && filters_.find(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID) == filters_.end()
+      if ((level == Orthanc::ResourceType_Instance  || level == Orthanc::ResourceType_Series) 
+          && filters_.find(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID) == filters_.end()
         )
       {
-        AddResultAttributesForLevel(fields, QueryLevel_Study);
+        AddResultAttributesForLevel(fields, Orthanc::ResourceType_Study);
       }
 
       // For instances, add all Series-level attributes if
       // {SeriesInstanceUID} is not specified.
-      if (level == QueryLevel_Instance
-          && filters_.find(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID) == filters_.end()
+      if (level == Orthanc::ResourceType_Instance
+          && filters_.find(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID) == filters_.end()
         )
       {
-        AddResultAttributesForLevel(fields, QueryLevel_Series);
+        AddResultAttributesForLevel(fields, Orthanc::ResourceType_Series);
       }
 
       // Copy all the required fields to the target
-      for (std::list<gdcm::Tag>::const_iterator
-             it = fields.begin(); it != fields.end(); it++)
+      for (std::list<Orthanc::DicomTag>::const_iterator
+             it = fields.begin(); it != fields.end(); ++it)
       {
-        if (dicom.GetDataSet().FindDataElement(*it))
+        // Complies to the JSON produced internally by Orthanc
+        char tag[16];
+        sprintf(tag, "%04x,%04x", it->GetGroup(), it->GetElement());
+        
+        if (source.isMember(tag))
         {
-          const gdcm::DataElement& element = dicom.GetDataSet().GetDataElement(*it);
-          result.Replace(element);
+          result[tag] = source[tag];
         }
       }
 
       // Set the retrieve URL for WADO-RS
       std::string url = (wadoBase + "studies/" + 
-                         dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, "", true));
+                         GetOrthancTag(source, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, ""));
 
-      if (level == QueryLevel_Series || level == QueryLevel_Instance)
+      if (level == Orthanc::ResourceType_Series || 
+          level == Orthanc::ResourceType_Instance)
       {
-        url += "/series/" + dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, "", true);
+        url += "/series/" + GetOrthancTag(source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, "");
       }
 
-      if (level == QueryLevel_Instance)
+      if (level == Orthanc::ResourceType_Instance)
       {
-        url += "/instances/" + dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID, "", true);
+        url += "/instances/" + GetOrthancTag(source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "");
       }
     
-      gdcm::DataElement element(OrthancPlugins::DICOM_TAG_RETRIEVE_URL);
-      element.SetByteValue(url.c_str(), url.size());
-      result.Replace(element);
+      static const Orthanc::DicomTag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
+      result[DICOM_TAG_RETRIEVE_URL.Format()] = url;
     }
   };
 }
@@ -400,187 +446,155 @@
 static void ApplyMatcher(OrthancPluginRestOutput* output,
                          const OrthancPluginHttpRequest* request,
                          const ModuleMatcher& matcher,
-                         QueryLevel level)
+                         Orthanc::ResourceType level)
 {
   Json::Value find;
   matcher.ConvertToOrthanc(find, level);
 
-  Json::FastWriter writer;
-  std::string body = writer.write(find);
+  std::string body;
+
+  {
+    Json::FastWriter writer;
+    body = writer.write(find);
+  }
   
   Json::Value resources;
-  if (!OrthancPlugins::RestApiPostJson(resources, context_, "/tools/find", body) ||
+  if (!OrthancPlugins::RestApiPost(resources, "/tools/find", body, false) ||
       resources.type() != Json::arrayValue)
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 
-  std::list<std::string> instances;
-  std::string root = (level == QueryLevel_Study ? "/studies/" : "/series/");
+  typedef std::list< std::pair<std::string, std::string> > ResourcesAndInstances;
+
+  ResourcesAndInstances resourcesAndInstances;
+  std::string root = (level == Orthanc::ResourceType_Study ? "/studies/" : "/series/");
     
   for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
   {
-    if (level == QueryLevel_Study ||
-        level == QueryLevel_Series)
+    const std::string resource = resources[i].asString();
+
+    if (level == Orthanc::ResourceType_Study ||
+        level == Orthanc::ResourceType_Series)
     {
       // Find one child instance of this resource
       Json::Value tmp;
-      if (OrthancPlugins::RestApiGetJson(tmp, context_, root + resources[i].asString() + "/instances") &&
+      if (OrthancPlugins::RestApiGet(tmp, root + resource + "/instances", false) &&
           tmp.type() == Json::arrayValue &&
           tmp.size() > 0)
       {
-        instances.push_back(tmp[0]["ID"].asString());
+        resourcesAndInstances.push_back(std::make_pair(resource, tmp[0]["ID"].asString()));
       }
     }
     else
     {
-      instances.push_back(resources[i].asString());
+      resourcesAndInstances.push_back(std::make_pair(resource, resource));
     }
   }
   
-  std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(configuration_, request);
+  std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
+
+  OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, IsXmlExpected(request));
 
-  OrthancPlugins::DicomResults results(context_, output, wadoBase, *dictionary_, IsXmlExpected(request), true);
-
-  for (std::list<std::string>::const_iterator
-         it = instances.begin(); it != instances.end(); it++)
+  // Fix of issue #13
+  for (ResourcesAndInstances::const_iterator
+         it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it)
   {
-    std::string file;
-    if (OrthancPlugins::RestApiGetString(file, context_, "/instances/" + *it + "/file"))
+    Json::Value tags;
+    if (OrthancPlugins::RestApiGet(tags, "/instances/" + it->second + "/tags?short", false))
     {
-      OrthancPlugins::ParsedDicomFile dicom(file);
+      std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl(
+        wadoBase, 
+        GetOrthancTag(tags, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, ""),
+        GetOrthancTag(tags, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, ""),
+        GetOrthancTag(tags, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, ""));
+
+      Json::Value result;
+      matcher.ExtractFields(result, tags, wadoBase, level);
 
-      std::auto_ptr<gdcm::DataSet> result(new gdcm::DataSet);
-      matcher.ExtractFields(*result, dicom, wadoBase, level);
-      results.Add(dicom.GetFile(), *result);
+      // Inject the derived tags
+      ModuleMatcher::Filters derivedTags;
+      matcher.ComputeDerivedTags(derivedTags, level, it->first);
+
+      for (ModuleMatcher::Filters::const_iterator
+             tag = derivedTags.begin(); tag != derivedTags.end(); ++tag)
+      {
+        result[tag->first.Format()] = tag->second;
+      }
+
+      writer.AddJson(result);
     }
   }
 
-  results.Answer();
+  writer.Send();
 }
 
 
 
-OrthancPluginErrorCode SearchForStudies(OrthancPluginRestOutput* output,
-                                        const char* url,
-                                        const OrthancPluginHttpRequest* request)
+void SearchForStudies(OrthancPluginRestOutput* output,
+                      const char* url,
+                      const OrthancPluginHttpRequest* request)
 {
-  try
+  if (request->method != OrthancPluginHttpMethod_Get)
   {
-    if (request->method != OrthancPluginHttpMethod_Get)
-    {
-      OrthancPluginSendMethodNotAllowed(context_, output, "GET");
-      return OrthancPluginErrorCode_Success;
-    }
-
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+  }
+  else
+  {
     ModuleMatcher matcher(request);
-    ApplyMatcher(output, request, matcher, QueryLevel_Study);
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (boost::bad_lexical_cast& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
+    ApplyMatcher(output, request, matcher, Orthanc::ResourceType_Study);
   }
 }
 
 
-OrthancPluginErrorCode SearchForSeries(OrthancPluginRestOutput* output,
-                                       const char* url,
-                                       const OrthancPluginHttpRequest* request)
+void SearchForSeries(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request)
 {
-  try
+  if (request->method != OrthancPluginHttpMethod_Get)
   {
-    if (request->method != OrthancPluginHttpMethod_Get)
-    {
-      OrthancPluginSendMethodNotAllowed(context_, output, "GET");
-      return OrthancPluginErrorCode_Success;
-    }
-
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+  }
+  else
+  {
     ModuleMatcher matcher(request);
 
     if (request->groupsCount == 1)
     {
       // The "StudyInstanceUID" is provided by the regular expression
-      matcher.AddFilter(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, request->groups[0]);
+      matcher.AddFilter(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, request->groups[0]);
     }
 
-    ApplyMatcher(output, request, matcher, QueryLevel_Series);
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (boost::bad_lexical_cast& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
+    ApplyMatcher(output, request, matcher, Orthanc::ResourceType_Series);
   }
 }
 
 
-OrthancPluginErrorCode SearchForInstances(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request)
+void SearchForInstances(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request)
 {
-  try
+  if (request->method != OrthancPluginHttpMethod_Get)
   {
-    if (request->method != OrthancPluginHttpMethod_Get)
-    {
-      OrthancPluginSendMethodNotAllowed(context_, output, "GET");
-      return OrthancPluginErrorCode_Success;
-    }
-
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+  }
+  else
+  {
     ModuleMatcher matcher(request);
 
-    if (request->groupsCount == 1 || request->groupsCount == 2)
+    if (request->groupsCount == 1 || 
+        request->groupsCount == 2)
     {
       // The "StudyInstanceUID" is provided by the regular expression
-      matcher.AddFilter(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, request->groups[0]);
+      matcher.AddFilter(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, request->groups[0]);
     }
 
     if (request->groupsCount == 2)
     {
       // The "SeriesInstanceUID" is provided by the regular expression
-      matcher.AddFilter(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, request->groups[1]);
+      matcher.AddFilter(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, request->groups[1]);
     }
 
-    ApplyMatcher(output, request, matcher, QueryLevel_Instance);
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (boost::bad_lexical_cast& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
+    ApplyMatcher(output, request, matcher, Orthanc::ResourceType_Instance);
   }
 }
--- a/Plugin/QidoRs.h	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/QidoRs.h	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -23,14 +24,14 @@
 #include "Configuration.h"
 
 
-OrthancPluginErrorCode SearchForStudies(OrthancPluginRestOutput* output,
-                                        const char* url,
-                                        const OrthancPluginHttpRequest* request);
+void SearchForStudies(OrthancPluginRestOutput* output,
+                      const char* url,
+                      const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode SearchForSeries(OrthancPluginRestOutput* output,
-                                       const char* url,
-                                       const OrthancPluginHttpRequest* request);
+void SearchForSeries(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode SearchForInstances(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request);
+void SearchForInstances(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request);
--- a/Plugin/StowRs.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/StowRs.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -19,41 +20,12 @@
 
 
 #include "StowRs.h"
-#include "Plugin.h"
 
 #include "Configuration.h"
-#include "Dicom.h"
-#include "../Orthanc/Core/Toolbox.h"
-#include "../Orthanc/Core/OrthancException.h"
-
-#include <stdexcept>
-
+#include "DicomWebFormatter.h"
 
-static void SetTag(gdcm::DataSet& dataset,
-                   const gdcm::Tag& tag,
-                   const gdcm::VR& vr,
-                   const std::string& value)
-{
-  gdcm::DataElement element(tag);
-  element.SetVR(vr);
-  element.SetByteValue(value.c_str(), value.size());
-  dataset.Insert(element);
-}
-
-
-static void SetSequenceTag(gdcm::DataSet& dataset,
-                           const gdcm::Tag& tag,
-                           gdcm::SmartPointer<gdcm::SequenceOfItems>& sequence)
-{
-  gdcm::DataElement element;
-  element.SetTag(tag);
-  element.SetVR(gdcm::VR::SQ);
-  element.SetValue(*sequence);
-  element.SetVLToUndefined();
-  dataset.Insert(element);
-}
-
-
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
 
 bool IsXmlExpected(const OrthancPluginHttpRequest* request)
 {
@@ -61,184 +33,214 @@
 
   if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
   {
-    return true;   // By default, return XML Native DICOM Model
+    return false;   // By default, return DICOM+JSON
   }
 
   Orthanc::Toolbox::ToLowerCase(accept);
-  if (accept == "application/json")
+  if (accept == "application/dicom+json" ||
+      accept == "application/json" ||
+      accept == "*/*")
   {
     return false;
   }
-
-  if (accept != "application/dicom+xml" &&
-      accept != "application/xml" &&
-      accept != "text/xml" &&
-      accept != "*/*")
+  else if (accept == "application/dicom+xml" ||
+           accept == "application/xml" ||
+           accept == "text/xml")
   {
-    std::string s = "Unsupported return MIME type: " + accept + ", will return XML";
-    OrthancPluginLogError(context_, s.c_str());
+    return true;
   }
-
-  return true;
+  else
+  {
+    OrthancPlugins::LogError("Unsupported return MIME type: " + accept +
+                             ", will return DICOM+JSON");
+    return false;
+  }
 }
 
 
 
-OrthancPluginErrorCode StowCallback(OrthancPluginRestOutput* output,
-                                    const char* url,
-                                    const OrthancPluginHttpRequest* request)
+void StowCallback(OrthancPluginRestOutput* output,
+                  const char* url,
+                  const OrthancPluginHttpRequest* request)
 {
-  try
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+    return;
+  }
+
+  std::string expectedStudy;
+  if (request->groupsCount == 1)
+  {
+    expectedStudy = request->groups[0];
+  }
+
+  if (expectedStudy.empty())
+  {
+    OrthancPlugins::LogInfo("STOW-RS request without study");
+  }
+  else
+  {
+    OrthancPlugins::LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
+  }
+
+  std::string header;
+  if (!OrthancPlugins::LookupHttpHeader(header, request, "content-type"))
   {
-    const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(configuration_, request);
+    OrthancPlugins::LogError("No content type in the HTTP header of a STOW-RS request");
+    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
+    return;
+  }
+
+  std::string application;
+  std::map<std::string, std::string> attributes;
+  OrthancPlugins::ParseContentType(application, attributes, header);
+
+  if (application != "multipart/related" ||
+      attributes.find("type") == attributes.end() ||
+      attributes.find("boundary") == attributes.end())
+  {
+    OrthancPlugins::LogError("Unable to parse the content type of a STOW-RS request (" + application + ")");
+    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
+    return;
+  }
+
+
+  std::string boundary = attributes["boundary"]; 
+
+  if (attributes["type"] != "application/dicom")
+  {
+    OrthancPlugins::LogError("The STOW-RS plugin currently only supports application/dicom");
+    OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
+    return;
+  }
 
 
-    if (request->method != OrthancPluginHttpMethod_Post)
+  bool isFirst = true;
+
+  Json::Value result = Json::objectValue;
+  Json::Value success = Json::arrayValue;
+  Json::Value failed = Json::arrayValue;
+  
+  std::vector<OrthancPlugins::MultipartItem> items;
+  OrthancPlugins::ParseMultipartBody(items, request->body, request->bodySize, boundary);
+
+  for (size_t i = 0; i < items.size(); i++)
+  {
+    OrthancPlugins::LogInfo("Detected multipart item with content type \"" + 
+                            items[i].contentType_ + "\" of size " + 
+                            boost::lexical_cast<std::string>(items[i].size_));
+  }  
+
+  for (size_t i = 0; i < items.size(); i++)
+  {
+    if (!items[i].contentType_.empty() &&
+        items[i].contentType_ != "application/dicom")
     {
-      OrthancPluginSendMethodNotAllowed(context_, output, "POST");
-      return OrthancPluginErrorCode_Success;
+      OrthancPlugins::LogError("The STOW-RS request contains a part that is not "
+                               "\"application/dicom\" (it is: \"" + items[i].contentType_ + "\")");
+      OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
+      return;
     }
 
-    std::string expectedStudy;
-    if (request->groupsCount == 1)
+    Json::Value dicom;
+
+    try
+    {
+      OrthancPlugins::OrthancString s;
+      s.Assign(OrthancPluginDicomBufferToJson(context, items[i].data_, items[i].size_,
+                                              OrthancPluginDicomToJsonFormat_Short,
+                                              OrthancPluginDicomToJsonFlags_None, 256));
+      s.ToJson(dicom);
+    }
+    catch (Orthanc::OrthancException&)
     {
-      expectedStudy = request->groups[0];
+      // Bad DICOM file => TODO add to error
+      OrthancPlugins::LogWarning("STOW-RS cannot parse an incoming DICOM file");
+      continue;
+    }           
+
+    if (dicom.type() != Json::objectValue ||
+        !dicom.isMember(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()) ||
+        !dicom.isMember(Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()) ||
+        !dicom.isMember(Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()) ||
+        !dicom.isMember(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()) ||
+        dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].type() != Json::stringValue ||
+        dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].type() != Json::stringValue ||
+        dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue ||
+        dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue)
+    {
+      OrthancPlugins::LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
+      continue;
     }
 
-    if (expectedStudy.empty())
+    const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString();
+    const std::string sopClassUid = dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].asString();
+    const std::string sopInstanceUid = dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].asString();
+    const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString();
+
+    Json::Value item = Json::objectValue;
+    item[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
+    item[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
+
+    if (!expectedStudy.empty() &&
+        studyInstanceUid != expectedStudy)
     {
-      OrthancPluginLogInfo(context_, "STOW-RS request without study");
+      OrthancPlugins::LogInfo("STOW-RS request restricted to study [" + expectedStudy + 
+                              "]: Ignoring instance from study [" + studyInstanceUid + "]");
+
+      /*item[OrthancPlugins::DICOM_TAG_WARNING_REASON.Format()] =
+        boost::lexical_cast<std::string>(0xB006);  // Elements discarded
+        success.append(item);*/
     }
     else
     {
-      std::string s = "STOW-RS request restricted to study UID " + expectedStudy;
-      OrthancPluginLogInfo(context_, s.c_str());
-    }
-
-    bool isXml = IsXmlExpected(request);
-
-    std::string header;
-    if (!OrthancPlugins::LookupHttpHeader(header, request, "content-type"))
-    {
-      OrthancPluginLogError(context_, "No content type in the HTTP header of a STOW-RS request");
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;    
-    }
-
-    std::string application;
-    std::map<std::string, std::string> attributes;
-    OrthancPlugins::ParseContentType(application, attributes, header);
-
-    if (application != "multipart/related" ||
-        attributes.find("type") == attributes.end() ||
-        attributes.find("boundary") == attributes.end())
-    {
-      std::string s = "Unable to parse the content type of a STOW-RS request (" + application + ")";
-      OrthancPluginLogError(context_, s.c_str());
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
-    }
-
-
-    std::string boundary = attributes["boundary"]; 
-
-    if (attributes["type"] != "application/dicom")
-    {
-      OrthancPluginLogError(context_, "The STOW-RS plugin currently only supports application/dicom");
-      OrthancPluginSendHttpStatusCode(context_, output, 415 /* Unsupported media type */);
-      return OrthancPluginErrorCode_Success;
-    }
-
-
-
-    bool isFirst = true;
-    gdcm::DataSet result;
-    gdcm::SmartPointer<gdcm::SequenceOfItems> success = new gdcm::SequenceOfItems();
-    gdcm::SmartPointer<gdcm::SequenceOfItems> failed = new gdcm::SequenceOfItems();
-  
-    std::vector<OrthancPlugins::MultipartItem> items;
-    OrthancPlugins::ParseMultipartBody(items, request->body, request->bodySize, boundary);
-    for (size_t i = 0; i < items.size(); i++)
-    {
-      if (!items[i].contentType_.empty() &&
-          items[i].contentType_ != "application/dicom")
+      if (isFirst)
       {
-        std::string s = "The STOW-RS request contains a part that is not application/dicom (it is: \"" + items[i].contentType_ + "\")";
-        OrthancPluginLogError(context_, s.c_str());
-        OrthancPluginSendHttpStatusCode(context_, output, 415 /* Unsupported media type */);
-        return OrthancPluginErrorCode_Success;
+        std::string url = wadoBase + "studies/" + studyInstanceUid;
+        result[OrthancPlugins::DICOM_TAG_RETRIEVE_URL.Format()] = url;
+        isFirst = false;
       }
 
-      OrthancPlugins::ParsedDicomFile dicom(items[i]);
-
-      std::string studyInstanceUid = dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, "", true);
-      std::string sopClassUid = dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_SOP_CLASS_UID, "", true);
-      std::string sopInstanceUid = dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID, "", true);
-
-      gdcm::Item item;
-      item.SetVLToUndefined();
-      gdcm::DataSet &status = item.GetNestedDataSet();
+      OrthancPlugins::MemoryBuffer tmp;
+      bool ok = tmp.RestApiPost("/instances", items[i].data_, items[i].size_, false);
+      tmp.Clear();
 
-      SetTag(status, OrthancPlugins::DICOM_TAG_REFERENCED_SOP_CLASS_UID, gdcm::VR::UI, sopClassUid);
-      SetTag(status, OrthancPlugins::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, gdcm::VR::UI, sopInstanceUid);
-
-      if (!expectedStudy.empty() &&
-          studyInstanceUid != expectedStudy)
+      if (ok)
       {
-        std::string s = ("STOW-RS request restricted to study [" + expectedStudy + 
-                         "]: Ignoring instance from study [" + studyInstanceUid + "]");
-        OrthancPluginLogInfo(context_, s.c_str());
+        std::string url = (wadoBase + 
+                           "studies/" + studyInstanceUid +
+                           "/series/" + seriesInstanceUid +
+                           "/instances/" + sopInstanceUid);
 
-        SetTag(status, OrthancPlugins::DICOM_TAG_WARNING_REASON, gdcm::VR::US, "B006");  // Elements discarded
-        success->AddItem(item);      
+        item[OrthancPlugins::DICOM_TAG_RETRIEVE_URL.Format()] = url;
+        success.append(item);      
       }
       else
       {
-        if (isFirst)
-        {
-          std::string url = wadoBase + "studies/" + studyInstanceUid;
-          SetTag(result, OrthancPlugins::DICOM_TAG_RETRIEVE_URL, gdcm::VR::UT, url);
-          isFirst = false;
-        }
-
-        OrthancPluginMemoryBuffer result;
-        bool ok = OrthancPluginRestApiPost(context_, &result, "/instances", items[i].data_, items[i].size_) == 0;
-        OrthancPluginFreeMemoryBuffer(context_, &result);
-
-        if (ok)
-        {
-          std::string url = (wadoBase + 
-                             "studies/" + studyInstanceUid +
-                             "/series/" + dicom.GetTagWithDefault(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, "", true) +
-                             "/instances/" + sopInstanceUid);
-
-          SetTag(status, OrthancPlugins::DICOM_TAG_RETRIEVE_URL, gdcm::VR::UT, url);
-          success->AddItem(item);
-        }
-        else
-        {
-          OrthancPluginLogError(context_, "Orthanc was unable to store instance through STOW-RS request");
-          SetTag(status, OrthancPlugins::DICOM_TAG_FAILURE_REASON, gdcm::VR::US, "0110");  // Processing failure
-          failed->AddItem(item);
-        }
+        OrthancPlugins::LogError("Orthanc was unable to store instance through STOW-RS request");
+        item[OrthancPlugins::DICOM_TAG_FAILURE_REASON.Format()] =
+          boost::lexical_cast<std::string>(0x0110);  // Processing failure
+        failed.append(item);      
       }
     }
-
-    SetSequenceTag(result, OrthancPlugins::DICOM_TAG_FAILED_SOP_SEQUENCE, failed);
-    SetSequenceTag(result, OrthancPlugins::DICOM_TAG_REFERENCED_SOP_SEQUENCE, success);
+  }
 
-    OrthancPlugins::AnswerDicom(context_, output, wadoBase, *dictionary_, result, isXml, false);
+  result[OrthancPlugins::DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed;
+  result[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success;
 
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
+  const bool isXml = IsXmlExpected(request);
+  std::string answer;
+  
   {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
+    OrthancPlugins::DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
+    locker.Apply(answer, context, result, isXml);
   }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
-  }
+
+  OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(),
+                            isXml ? "application/dicom+xml" : "application/dicom+json");
 }
--- a/Plugin/StowRs.h	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/StowRs.h	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -24,6 +25,6 @@
 
 bool IsXmlExpected(const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode StowCallback(OrthancPluginRestOutput* output,
-                                    const char* url,
-                                    const OrthancPluginHttpRequest* request);
+void StowCallback(OrthancPluginRestOutput* output,
+                  const char* url,
+                  const OrthancPluginHttpRequest* request);
--- a/Plugin/Wado.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,291 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Wado.h"
-#include "Plugin.h"
-
-#include "../Orthanc/Core/OrthancException.h"
-#include "Configuration.h"
-
-#include <string>
-
-static bool MapWadoToOrthancIdentifier(std::string& orthanc,
-                                       char* (*func) (OrthancPluginContext*, const char*),
-                                       const std::string& dicom)
-{
-  char* tmp = func(context_, dicom.c_str());
-
-  if (tmp)
-  {
-    orthanc = tmp;
-    OrthancPluginFreeString(context_, tmp);
-    return true;
-  }
-  else
-  {
-    return false;
-  }
-}
-
-
-static bool LocateInstance(std::string& instance,
-                           std::string& contentType,
-                           const OrthancPluginHttpRequest* request)
-{
-  std::string requestType, studyUid, seriesUid, objectUid;
-
-  for (uint32_t i = 0; i < request->getCount; i++)
-  {
-    std::string key(request->getKeys[i]);
-    std::string value(request->getValues[i]);
-
-    if (key == "studyUID")
-    {
-      studyUid = value;
-    }
-    else if (key == "seriesUID")
-    {
-      seriesUid = value;
-    }
-    else if (key == "objectUID")  // In WADO, "objectUID" corresponds to "SOPInstanceUID"
-    {
-      objectUid = value;
-    }
-    else if (key == "requestType")
-    {
-      requestType = value;
-    }
-    else if (key == "contentType")
-    {
-      contentType = value;
-    }
-  }
-
-  if (requestType != "WADO")
-  {
-    std::string msg = "WADO: Invalid requestType: \"" + requestType + "\"";
-    OrthancPluginLogError(context_, msg.c_str());
-    return false;
-  }
-
-  if (objectUid.empty())
-  {
-    OrthancPluginLogError(context_, "WADO: No SOPInstanceUID provided");
-    return false;
-  }
-
-  if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
-  {
-    std::string msg = "WADO: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"";
-    OrthancPluginLogError(context_, msg.c_str());
-    return false;
-  }
-
-  /**
-   * Below are only sanity checks to ensure that the possibly provided
-   * "seriesUID" and "studyUID" match that of the provided instance.
-   **/
-
-  if (!seriesUid.empty())
-  {
-    std::string series;
-    if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
-    {
-      std::string msg = "WADO: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"";
-      OrthancPluginLogError(context_, msg.c_str());
-      return false;
-    }
-    else
-    {
-      Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/series") ||
-          info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
-      {
-        std::string msg = "WADO: Instance " + objectUid + " does not belong to series " + seriesUid;
-        OrthancPluginLogError(context_, msg.c_str());
-        return false;
-      }
-    }
-  }
-  
-  if (!studyUid.empty())
-  {
-    std::string study;
-    if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
-    {
-      std::string msg = "WADO: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"";
-      OrthancPluginLogError(context_, msg.c_str());
-      return false;
-    }
-    else
-    {
-      Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/study") ||
-          info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
-      {
-        std::string msg = "WADO: Instance " + objectUid + " does not belong to study " + studyUid;
-        OrthancPluginLogError(context_, msg.c_str());
-        return false;
-      }
-    }
-  }
-  
-  return true;
-}
-
-
-static OrthancPluginErrorCode AnswerDicom(OrthancPluginRestOutput* output,
-                                          const std::string& instance)
-{
-  std::string uri = "/instances/" + instance + "/file";
-
-  std::string dicom;
-  if (OrthancPlugins::RestApiGetString(dicom, context_, uri))
-  {
-    OrthancPluginAnswerBuffer(context_, output, dicom.c_str(), dicom.size(), "application/dicom");
-    return OrthancPluginErrorCode_Success;
-  }
-  else
-  {
-    std::string msg = "WADO: Unable to retrieve DICOM file from " + uri;
-    OrthancPluginLogError(context_, msg.c_str());
-    return OrthancPluginErrorCode_Plugin;
-  }
-}
-
-
-static bool RetrievePngPreview(std::string& png,
-                               const std::string& instance)
-{
-  std::string uri = "/instances/" + instance + "/preview";
-
-  if (OrthancPlugins::RestApiGetString(png, context_, uri, true))
-  {
-    return true;
-  }
-  else
-  {
-    std::string msg = "WADO: Unable to generate a preview image for " + uri;
-    OrthancPluginLogError(context_, msg.c_str());
-    return false;
-  }
-}
-
-
-static OrthancPluginErrorCode AnswerPngPreview(OrthancPluginRestOutput* output,
-                                               const std::string& instance)
-{
-  std::string png;
-  if (!RetrievePngPreview(png, instance))
-  {
-    return OrthancPluginErrorCode_Plugin;
-  }
-
-  OrthancPluginAnswerBuffer(context_, output, png.c_str(), png.size(), "image/png");
-  return OrthancPluginErrorCode_Success;
-}
-
-
-static OrthancPluginErrorCode AnswerJpegPreview(OrthancPluginRestOutput* output,
-                                                const std::string& instance)
-{
-  // Retrieve the preview in the PNG format
-  std::string png;
-  if (!RetrievePngPreview(png, instance))
-  {
-    return OrthancPluginErrorCode_Plugin;
-  }
-
-  // Decode the PNG file
-  OrthancPluginImage* image = OrthancPluginUncompressImage(
-    context_, png.c_str(), png.size(), OrthancPluginImageFormat_Png);
-
-  // Convert to JPEG
-  OrthancPluginCompressAndAnswerJpegImage(
-    context_, output, 
-    OrthancPluginGetImagePixelFormat(context_, image),
-    OrthancPluginGetImageWidth(context_, image),
-    OrthancPluginGetImageHeight(context_, image),
-    OrthancPluginGetImagePitch(context_, image),
-    OrthancPluginGetImageBuffer(context_, image), 
-    90 /*quality*/);
-
-  OrthancPluginFreeImage(context_, image);
-
-  return OrthancPluginErrorCode_Success;
-}
-
-
-OrthancPluginErrorCode WadoCallback(OrthancPluginRestOutput* output,
-                                    const char* url,
-                                    const OrthancPluginHttpRequest* request)
-{
-  try
-  {
-    if (request->method != OrthancPluginHttpMethod_Get)
-    {
-      OrthancPluginSendMethodNotAllowed(context_, output, "GET");
-      return OrthancPluginErrorCode_Plugin;
-    }
-
-    std::string instance;
-    std::string contentType = "image/jpg";  // By default, JPEG image will be returned
-    if (!LocateInstance(instance, contentType, request))
-    {
-#if HAS_ERROR_CODE == 1
-      return OrthancPluginErrorCode_UnknownResource;
-#else
-      return OrthancPluginErrorCode_Plugin;
-#endif
-    }
-
-    if (contentType == "application/dicom")
-    {
-      return AnswerDicom(output, instance);
-    }
-    else if (contentType == "image/png")
-    {
-      return AnswerPngPreview(output, instance);
-    }
-    else if (contentType == "image/jpeg" ||
-             contentType == "image/jpg")
-    {
-      return AnswerJpegPreview(output, instance);
-    }
-    else
-    {
-      std::string msg = "WADO: Unsupported content type: \"" + contentType + "\"";
-      OrthancPluginLogError(context_, msg.c_str());
-      return OrthancPluginErrorCode_Plugin;
-    }
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
-  }
-}
--- a/Plugin/Wado.h	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 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 "Configuration.h"
-
-OrthancPluginErrorCode WadoCallback(OrthancPluginRestOutput* output,
-                                    const char* url,
-                                    const OrthancPluginHttpRequest* request);
--- a/Plugin/WadoRs.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/WadoRs.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -18,15 +19,36 @@
  **/
 
 
-#include "Plugin.h"
+#include "Configuration.h"
+#include "DicomWebFormatter.h"
 
-#include <boost/lexical_cast.hpp>
+#include <Core/ChunkedBuffer.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <memory>
+
 
-#include "Configuration.h"
-#include "Dicom.h"
-#include "DicomResults.h"
-#include "../Orthanc/Core/Toolbox.h"
-#include "../Orthanc/Core/OrthancException.h"
+static std::string GetResourceUri(Orthanc::ResourceType level,
+                                  const std::string& publicId)
+{
+  switch (level)
+  {
+    case Orthanc::ResourceType_Study:
+      return "/studies/" + publicId;
+      
+    case Orthanc::ResourceType_Series:
+      return "/series/" + publicId;
+      
+    case Orthanc::ResourceType_Instance:
+      return "/instances/" + publicId;
+      
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
+
+
 
 static bool AcceptMultipartDicom(const OrthancPluginHttpRequest* request)
 {
@@ -44,9 +66,8 @@
   if (application != "multipart/related" &&
       application != "*/*")
   {
-    std::string s = "This WADO-RS plugin cannot generate the following content type: " + accept;
-    OrthancPluginLogError(context_, s.c_str());
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot generate the following content type: " + accept);
   }
 
   if (attributes.find("type") != attributes.end())
@@ -55,17 +76,17 @@
     Orthanc::Toolbox::ToLowerCase(s);
     if (s != "application/dicom")
     {
-      std::string s = "This WADO-RS plugin only supports application/dicom return type for DICOM retrieval (" + accept + ")";
-      OrthancPluginLogError(context_, s.c_str());
-      return false;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                      "This WADO-RS plugin only supports application/dicom "
+                                      "return type for DICOM retrieval (" + accept + ")");
     }
   }
 
   if (attributes.find("transfer-syntax") != attributes.end())
   {
-    std::string s = "This WADO-RS plugin cannot change the transfer syntax to " + attributes["transfer-syntax"];
-    OrthancPluginLogError(context_, s.c_str());
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot change the transfer syntax to " + 
+                                    attributes["transfer-syntax"]);
   }
 
   return true;
@@ -76,13 +97,11 @@
 static bool AcceptMetadata(const OrthancPluginHttpRequest* request,
                            bool& isXml)
 {
-  isXml = true;
+  isXml = false;    // By default, return application/dicom+json
 
   std::string accept;
-
   if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
   {
-    // By default, return "multipart/related; type=application/dicom+xml;"
     return true;
   }
 
@@ -90,37 +109,45 @@
   std::map<std::string, std::string> attributes;
   OrthancPlugins::ParseContentType(application, attributes, accept);
 
-  if (application == "application/json")
+  if (application == "application/json" ||
+      application == "application/dicom+json" ||
+      application == "*/*")
   {
-    isXml = false;
     return true;
   }
 
-  if (application != "multipart/related" &&
-      application != "*/*")
+  if (application != "multipart/related")
   {
-    std::string s = "This WADO-RS plugin cannot generate the following content type: " + accept;
-    OrthancPluginLogError(context_, s.c_str());
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot generate the following content type: " + accept);
   }
 
   if (attributes.find("type") != attributes.end())
   {
     std::string s = attributes["type"];
     Orthanc::Toolbox::ToLowerCase(s);
-    if (s != "application/dicom+xml")
+    if (s == "application/dicom+xml")
+    {
+      isXml = true;
+    }
+    else
     {
-      std::string s = "This WADO-RS plugin only supports application/json or application/dicom+xml return types for metadata (" + accept + ")";
-      OrthancPluginLogError(context_, s.c_str());
-      return false;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                      "This WADO-RS plugin only supports application/dicom+xml "
+                                      "type for multipart/related accept (" + accept + ")");
     }
   }
+  else
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "Missing \"type\" in multipart/related accept type (" + accept + ")");
+  }
 
   if (attributes.find("transfer-syntax") != attributes.end())
   {
-    std::string s = "This WADO-RS plugin cannot change the transfer syntax to " + attributes["transfer-syntax"];
-    OrthancPluginLogError(context_, s.c_str());
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot change the transfer syntax to " + 
+                                    attributes["transfer-syntax"]);
   }
 
   return true;
@@ -144,9 +171,9 @@
   if (application != "multipart/related" &&
       application != "*/*")
   {
-    std::string s = "This WADO-RS plugin cannot generate the following bulk data type: " + accept;
-    OrthancPluginLogError(context_, s.c_str());
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot generate the following "
+                                    "bulk data type: " + accept);
   }
 
   if (attributes.find("type") != attributes.end())
@@ -155,560 +182,506 @@
     Orthanc::Toolbox::ToLowerCase(s);
     if (s != "application/octet-stream")
     {
-      std::string s = "This WADO-RS plugin only supports application/octet-stream return type for bulk data retrieval (" + accept + ")";
-      OrthancPluginLogError(context_, s.c_str());
-      return false;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                      "This WADO-RS plugin only supports application/octet-stream "
+                                      "return type for bulk data retrieval (" + accept + ")");
     }
   }
 
   if (attributes.find("ra,ge") != attributes.end())
   {
-    std::string s = "This WADO-RS plugin does not support Range retrieval, it can only return entire bulk data object";
-    OrthancPluginLogError(context_, s.c_str());
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin does not support Range retrieval, "
+                                    "it can only return entire bulk data object");
   }
 
   return true;
 }
 
 
-static OrthancPluginErrorCode AnswerListOfDicomInstances(OrthancPluginRestOutput* output,
-                                                         const std::string& resource)
+static void AnswerListOfDicomInstances(OrthancPluginRestOutput* output,
+                                       Orthanc::ResourceType level,
+                                       const std::string& publicId)
 {
+  if (level != Orthanc::ResourceType_Study &&
+      level != Orthanc::ResourceType_Series)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
   Json::Value instances;
-  if (!OrthancPlugins::RestApiGetJson(instances, context_, resource + "/instances"))
+  if (!OrthancPlugins::RestApiGet(instances, GetResourceUri(level, publicId) + "/instances", false))
   {
     // Internal error
-    OrthancPluginSendHttpStatusCode(context_, output, 400);
-    return OrthancPluginErrorCode_Success;
+    OrthancPluginSendHttpStatusCode(context, output, 400);
+    return;
   }
 
-  if (OrthancPluginStartMultipartAnswer(context_, output, "related", "application/dicom"))
+  if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
   {
-    return OrthancPluginErrorCode_Plugin;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
   
   for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
   {
     std::string uri = "/instances/" + instances[i]["ID"].asString() + "/file";
-    std::string dicom;
-    if (OrthancPlugins::RestApiGetString(dicom, context_, uri) &&
-        OrthancPluginSendMultipartItem(context_, output, dicom.c_str(), dicom.size()) != 0)
+
+    OrthancPlugins::MemoryBuffer dicom;
+    if (dicom.RestApiGet(uri, false) &&
+        OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
     {
-      return OrthancPluginErrorCode_Plugin;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
   }
+}
 
-  return OrthancPluginErrorCode_Success;
+
+static bool GetDicomIdentifiers(std::string& studyInstanceUid,
+                                std::string& seriesInstanceUid,
+                                std::string& sopInstanceUid,
+                                const std::string& orthancId)
+{
+  // TODO - Any way of speeding this up?
+
+  static const char* const STUDY_INSTANCE_UID = "0020,000d";
+  static const char* const SERIES_INSTANCE_UID = "0020,000e";
+  static const char* const SOP_INSTANCE_UID = "0008,0018";
+  
+  Json::Value dicom;
+  if (OrthancPlugins::RestApiGet(dicom, "/instances/" + orthancId + "/tags?short", false) &&
+      dicom.isMember(STUDY_INSTANCE_UID) &&
+      dicom.isMember(SERIES_INSTANCE_UID) &&
+      dicom.isMember(SOP_INSTANCE_UID) &&
+      dicom[STUDY_INSTANCE_UID].type() == Json::stringValue &&
+      dicom[SERIES_INSTANCE_UID].type() == Json::stringValue &&
+      dicom[SOP_INSTANCE_UID].type() == Json::stringValue)
+  {
+    studyInstanceUid = dicom[STUDY_INSTANCE_UID].asString();
+    seriesInstanceUid = dicom[SERIES_INSTANCE_UID].asString();
+    sopInstanceUid = dicom[SOP_INSTANCE_UID].asString();
+    return true;
+  }
+  else
+  {
+    return false;
+  }
 }
 
 
 
 static void AnswerMetadata(OrthancPluginRestOutput* output,
                            const OrthancPluginHttpRequest* request,
+                           Orthanc::ResourceType level,
                            const std::string& resource,
                            bool isInstance,
                            bool isXml)
 {
-  std::list<std::string> files;
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  std::list<std::string> instances;
   if (isInstance)
   {
-    files.push_back(resource + "/file");
+    instances.push_back(resource);
   }
   else
   {
-    Json::Value instances;
-    if (!OrthancPlugins::RestApiGetJson(instances, context_, resource + "/instances"))
+    Json::Value children;
+    if (!OrthancPlugins::RestApiGet(children, GetResourceUri(level, resource) + "/instances", false))
     {
       // Internal error
-      OrthancPluginSendHttpStatusCode(context_, output, 400);
+      OrthancPluginSendHttpStatusCode(context, output, 400);
       return;
     }
 
-    for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+    for (Json::Value::ArrayIndex i = 0; i < children.size(); i++)
     {
-      files.push_back("/instances/" + instances[i]["ID"].asString() + "/file");
+      instances.push_back(children[i]["ID"].asString());
     }
   }
 
-  const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(configuration_, request);
-  OrthancPlugins::DicomResults results(context_, output, wadoBase, *dictionary_, isXml, true);
-  
+  const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
+
+  OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+
   for (std::list<std::string>::const_iterator
-         it = files.begin(); it != files.end(); ++it)
+         it = instances.begin(); it != instances.end(); ++it)
   {
-    std::string content; 
-    if (OrthancPlugins::RestApiGetString(content, context_, *it))
+    std::string studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (GetDicomIdentifiers(studyInstanceUid, seriesInstanceUid, sopInstanceUid, *it))
     {
-      OrthancPlugins::ParsedDicomFile dicom(content);
-      results.Add(dicom.GetFile());
+      const std::string bulkRoot = (wadoBase +
+                                    "studies/" + studyInstanceUid +
+                                    "/series/" + seriesInstanceUid + 
+                                    "/instances/" + sopInstanceUid + "/bulk");
+      
+      OrthancPlugins::MemoryBuffer dicom;
+      if (dicom.RestApiGet("/instances/" + *it + "/file", false))
+      {
+        writer.AddDicom(dicom.GetData(), dicom.GetSize(), bulkRoot);
+      }
     }
   }
 
-  results.Answer();
+  writer.Send();
 }
 
 
 
-
 static bool LocateStudy(OrthancPluginRestOutput* output,
-                        std::string& uri,
+                        std::string& publicId,
                         const OrthancPluginHttpRequest* request)
 {
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
   if (request->method != OrthancPluginHttpMethod_Get)
   {
-    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
     return false;
   }
 
-  std::string id;
-
+  try
   {
-    char* tmp = OrthancPluginLookupStudy(context_, request->groups[0]);
-    if (tmp == NULL)
-    {
-      std::string s = "Accessing an inexistent study with WADO-RS: " + std::string(request->groups[0]);
-      OrthancPluginLogError(context_, s.c_str());
-      OrthancPluginSendHttpStatusCode(context_, output, 404);
-      return false;
-    }
-
-    id.assign(tmp);
-    OrthancPluginFreeString(context_, tmp);
+    OrthancPlugins::OrthancString tmp;
+    tmp.Assign(OrthancPluginLookupStudy(context, request->groups[0]));
+    tmp.ToString(publicId);
+  }
+  catch (Orthanc::OrthancException&)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
+                                    "Accessing an inexistent study with WADO-RS: " +
+                                    std::string(request->groups[0]));
   }
   
-  uri = "/studies/" + id;
   return true;
 }
 
 
 static bool LocateSeries(OrthancPluginRestOutput* output,
-                         std::string& uri,
+                         std::string& publicId,
                          const OrthancPluginHttpRequest* request)
 {
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
   if (request->method != OrthancPluginHttpMethod_Get)
   {
-    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
-    return false;
-  }
-
-  std::string id;
-
-  {
-    char* tmp = OrthancPluginLookupSeries(context_, request->groups[1]);
-    if (tmp == NULL)
-    {
-      std::string s = "Accessing an inexistent series with WADO-RS: " + std::string(request->groups[1]);
-      OrthancPluginLogError(context_, s.c_str());
-      OrthancPluginSendHttpStatusCode(context_, output, 404);
-      return false;
-    }
-
-    id.assign(tmp);
-    OrthancPluginFreeString(context_, tmp);
-  }
-  
-  Json::Value study;
-  if (!OrthancPlugins::RestApiGetJson(study, context_, "/series/" + id + "/study"))
-  {
-    OrthancPluginSendHttpStatusCode(context_, output, 404);
-    return false;
-  }
-
-  if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]))
-  {
-    std::string s = "No series " + std::string(request->groups[1]) + " in study " + std::string(request->groups[0]);
-    OrthancPluginLogError(context_, s.c_str());
-    OrthancPluginSendHttpStatusCode(context_, output, 404);
-    return false;
-  }
-  
-  uri = "/series/" + id;
-  return true;
-}
-
-
-static bool LocateInstance(OrthancPluginRestOutput* output,
-                           std::string& uri,
-                           const OrthancPluginHttpRequest* request)
-{
-  if (request->method != OrthancPluginHttpMethod_Get)
-  {
-    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
     return false;
   }
 
-  std::string id;
-
+  try
   {
-    char* tmp = OrthancPluginLookupInstance(context_, request->groups[2]);
-    if (tmp == NULL)
-    {
-      std::string s = "Accessing an inexistent instance with WADO-RS: " + std::string(request->groups[2]);
-      OrthancPluginLogError(context_, s.c_str());
-      OrthancPluginSendHttpStatusCode(context_, output, 404);
-      return false;
-    }
-
-    id.assign(tmp);
-    OrthancPluginFreeString(context_, tmp);
+    OrthancPlugins::OrthancString tmp;
+    tmp.Assign(OrthancPluginLookupSeries(context, request->groups[1]));
+    tmp.ToString(publicId);
+  }
+  catch (Orthanc::OrthancException&)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
+                                    "Accessing an inexistent series with WADO-RS: " +
+                                    std::string(request->groups[1]));
   }
   
-  Json::Value study, series;
-  if (!OrthancPlugins::RestApiGetJson(series, context_, "/instances/" + id + "/series") ||
-      !OrthancPlugins::RestApiGetJson(study, context_, "/instances/" + id + "/study"))
+  Json::Value study;
+  if (!OrthancPlugins::RestApiGet(study, "/series/" + publicId + "/study", false))
   {
-    OrthancPluginSendHttpStatusCode(context_, output, 404);
-    return false;
-  }
-
-  if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]) ||
-      series["MainDicomTags"]["SeriesInstanceUID"].asString() != std::string(request->groups[1]))
-  {
-    std::string s = ("No instance " + std::string(request->groups[2]) + 
-                     " in study " + std::string(request->groups[0]) + " or " +
-                     " in series " + std::string(request->groups[1]));
-    OrthancPluginLogError(context_, s.c_str());
-    OrthancPluginSendHttpStatusCode(context_, output, 404);
+    OrthancPluginSendHttpStatusCode(context, output, 404);
     return false;
   }
-
-  uri = "/instances/" + id;
-  return true;
-}
-
-
-OrthancPluginErrorCode RetrieveDicomStudy(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request)
-{
-  try
+  else if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]))
   {
-    if (!AcceptMultipartDicom(request))
-    {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
-    }
-
-    std::string uri;
-    if (LocateStudy(output, uri, request))
-    {
-      AnswerListOfDicomInstances(output, uri);
-    }
-
-    return OrthancPluginErrorCode_Success;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
+                                    "No series " + std::string(request->groups[1]) + 
+                                    " in study " + std::string(request->groups[0]));
   }
-  catch (Orthanc::OrthancException& e)
+  else
   {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
+    return true;
   }
 }
 
 
-OrthancPluginErrorCode RetrieveDicomSeries(OrthancPluginRestOutput* output,
-                                           const char* url,
-                                           const OrthancPluginHttpRequest* request)
+bool LocateInstance(OrthancPluginRestOutput* output,
+                    std::string& publicId,
+                    const OrthancPluginHttpRequest* request)
 {
-  try
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+    return false;
+  }
+
   {
-    if (!AcceptMultipartDicom(request))
+    OrthancPlugins::OrthancString tmp;
+    tmp.Assign(OrthancPluginLookupInstance(context, request->groups[2]));
+
+    if (tmp.GetContent() == NULL)
     {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
+                                      "Accessing an inexistent instance with WADO-RS: " + 
+                                      std::string(request->groups[2]));
     }
 
-    std::string uri;
-    if (LocateSeries(output, uri, request))
-    {
-      AnswerListOfDicomInstances(output, uri);
-    }
-
-    return OrthancPluginErrorCode_Success;
+    tmp.ToString(publicId);
+  }
+  
+  Json::Value study, series;
+  if (!OrthancPlugins::RestApiGet(series, "/instances/" + publicId + "/series", false) ||
+      !OrthancPlugins::RestApiGet(study, "/instances/" + publicId + "/study", false))
+  {
+    OrthancPluginSendHttpStatusCode(context, output, 404);
+    return false;
+  }
+  else if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]) ||
+           series["MainDicomTags"]["SeriesInstanceUID"].asString() != std::string(request->groups[1]))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
+                                    "No instance " + std::string(request->groups[2]) + 
+                                    " in study " + std::string(request->groups[0]) +
+                                    " or in series " + std::string(request->groups[1]));
+  }
+  else
+  {
+    return true;
   }
-  catch (Orthanc::OrthancException& e)
+}
+
+
+void RetrieveDicomStudy(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request)
+{
+  if (!AcceptMultipartDicom(request))
   {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
+  }
+  else
+  {
+    std::string publicId;
+    if (LocateStudy(output, publicId, request))
+    {
+      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Study, publicId);
+    }
   }
-  catch (std::runtime_error& e)
+}
+
+
+void RetrieveDicomSeries(OrthancPluginRestOutput* output,
+                         const char* url,
+                         const OrthancPluginHttpRequest* request)
+{
+  if (!AcceptMultipartDicom(request))
   {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
+  }
+  else
+  {
+    std::string publicId;
+    if (LocateSeries(output, publicId, request))
+    {
+      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Series, publicId);
+    }
   }
 }
 
 
 
-OrthancPluginErrorCode RetrieveDicomInstance(OrthancPluginRestOutput* output,
-                                             const char* url,
-                                             const OrthancPluginHttpRequest* request)
+void RetrieveDicomInstance(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request)
 {
-  try
-  {
-    if (!AcceptMultipartDicom(request))
-    {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
-    }
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
-    std::string uri;
-    if (LocateInstance(output, uri, request))
+  if (!AcceptMultipartDicom(request))
+  {
+    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
+  }
+  else
+  {
+    std::string publicId;
+    if (LocateInstance(output, publicId, request))
     {
-      if (OrthancPluginStartMultipartAnswer(context_, output, "related", "application/dicom"))
+      if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
       {
-        return OrthancPluginErrorCode_Plugin;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
-  
-      std::string dicom;
-      if (OrthancPlugins::RestApiGetString(dicom, context_, uri + "/file") &&
-          OrthancPluginSendMultipartItem(context_, output, dicom.c_str(), dicom.size()) != 0)
+
+      OrthancPlugins::MemoryBuffer dicom;
+      if (dicom.RestApiGet("/instances/" + publicId + "/file", false) &&
+          OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
       {
-        return OrthancPluginErrorCode_Plugin;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
     }
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
   }
 }
 
 
 
-OrthancPluginErrorCode RetrieveStudyMetadata(OrthancPluginRestOutput* output,
-                                             const char* url,
-                                             const OrthancPluginHttpRequest* request)
+void RetrieveStudyMetadata(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request)
 {
-  try
+  bool isXml;
+  if (!AcceptMetadata(request, isXml))
   {
-    bool isXml;
-    if (!AcceptMetadata(request, isXml))
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
+  }
+  else
+  {
+    std::string publicId;
+    if (LocateStudy(output, publicId, request))
     {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
+      AnswerMetadata(output, request, Orthanc::ResourceType_Study, publicId, false, isXml);
     }
-
-    std::string uri;
-    if (LocateStudy(output, uri, request))
-    {
-      AnswerMetadata(output, request, uri, false, isXml);
-    }
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
   }
 }
 
 
-OrthancPluginErrorCode RetrieveSeriesMetadata(OrthancPluginRestOutput* output,
-                                              const char* url,
-                                              const OrthancPluginHttpRequest* request)
+void RetrieveSeriesMetadata(OrthancPluginRestOutput* output,
+                            const char* url,
+                            const OrthancPluginHttpRequest* request)
 {
-  try
+  bool isXml;
+  if (!AcceptMetadata(request, isXml))
   {
-    bool isXml;
-    if (!AcceptMetadata(request, isXml))
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
+  }
+  else
+  {
+    std::string publicId;
+    if (LocateSeries(output, publicId, request))
     {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
+      AnswerMetadata(output, request, Orthanc::ResourceType_Series, publicId, false, isXml);
     }
-
-    std::string uri;
-    if (LocateSeries(output, uri, request))
-    {
-      AnswerMetadata(output, request, uri, false, isXml);
-    }
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
   }
 }
 
 
-OrthancPluginErrorCode RetrieveInstanceMetadata(OrthancPluginRestOutput* output,
-                                                const char* url,
-                                                const OrthancPluginHttpRequest* request)
+void RetrieveInstanceMetadata(OrthancPluginRestOutput* output,
+                              const char* url,
+                              const OrthancPluginHttpRequest* request)
 {
-  try
+  bool isXml;
+  if (!AcceptMetadata(request, isXml))
   {
-    bool isXml;
-    if (!AcceptMetadata(request, isXml))
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
+  }
+  else
+  {
+    std::string publicId;
+    if (LocateInstance(output, publicId, request))
     {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
+      AnswerMetadata(output, request, Orthanc::ResourceType_Instance, publicId, true, isXml);
     }
-
-    std::string uri;
-    if (LocateInstance(output, uri, request))
-    {
-      AnswerMetadata(output, request, uri, true, isXml);
-    }
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
   }
 }
 
 
-
-static uint32_t Hex2Dec(char c)
-{
-  return (c >= '0' && c <= '9') ? c - '0' : c - 'a' + 10;
-}
-
-
-static bool ParseBulkTag(gdcm::Tag& tag,
-                         const std::string& s)
+void RetrieveBulkData(OrthancPluginRestOutput* output,
+                      const char* url,
+                      const OrthancPluginHttpRequest* request)
 {
-  if (s.size() != 8)
-  {
-    return false;
-  }
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
-  for (size_t i = 0; i < 8; i++)
+  if (!AcceptBulkData(request))
   {
-    if ((s[i] < '0' || s[i] > '9') &&
-        (s[i] < 'a' || s[i] > 'f'))
-    {
-      return false;
-    }
+    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
+    return;
   }
 
-  uint32_t g = ((Hex2Dec(s[0]) << 12) +
-                (Hex2Dec(s[1]) << 8) +
-                (Hex2Dec(s[2]) << 4) +
-                Hex2Dec(s[3]));
-
-  uint32_t e = ((Hex2Dec(s[4]) << 12) +
-                (Hex2Dec(s[5]) << 8) +
-                (Hex2Dec(s[6]) << 4) +
-                Hex2Dec(s[7]));
-
-  tag = gdcm::Tag(g, e);
-  return true;
-}
-
+  std::string publicId;
+  OrthancPlugins::MemoryBuffer content;
+  if (LocateInstance(output, publicId, request) &&
+      content.RestApiGet("/instances/" + publicId + "/file", false))
+  {
+    std::string bulk(request->groups[3]);
 
-static bool ExploreBulkData(std::string& content,
-                            const std::vector<std::string>& path,
-                            size_t position,
-                            const gdcm::DataSet& dataset)
-{
-  gdcm::Tag tag;
-  if (!ParseBulkTag(tag, path[position]) ||
-      !dataset.FindDataElement(tag))
-  {
-    return false;
-  }
+    std::vector<std::string> path;
+    Orthanc::Toolbox::TokenizeString(path, bulk, '/');
+
+    // Map the bulk data URI to the Orthanc "/instances/.../content/..." built-in URI
+    std::string orthanc = "/instances/" + publicId + "/content";
 
-  const gdcm::DataElement& element = dataset.GetDataElement(tag);
-
-  if (position + 1 == path.size())
-  {
-    const gdcm::ByteValue* data = element.GetByteValue();
-    if (!data)
-    {
-      content.clear();
-    }
-    else
+    Orthanc::DicomTag tmp(0, 0);
+    
+    if (path.size() == 1 &&
+        Orthanc::DicomTag::ParseHexadecimal(tmp, path[0].c_str()) &&
+        tmp == Orthanc::DICOM_TAG_PIXEL_DATA)
     {
-      content.assign(data->GetPointer(), data->GetLength());
-    }
-
-    return true;
-  }
-
-  return false;
-}
+      // Accessing pixel data: Return the raw content of the fragments in a multipart stream.
+      // TODO - Is this how DICOMweb should work?
+      orthanc += "/" + Orthanc::DICOM_TAG_PIXEL_DATA.Format();
 
-OrthancPluginErrorCode RetrieveBulkData(OrthancPluginRestOutput* output,
-                                        const char* url,
-                                        const OrthancPluginHttpRequest* request)
-{
-  try
-  {
-    if (!AcceptBulkData(request))
-    {
-      OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
-      return OrthancPluginErrorCode_Success;
-    }
+      Json::Value frames;
+      if (OrthancPlugins::RestApiGet(frames, orthanc, false))
+      {
+        if (frames.type() != Json::arrayValue ||
+            OrthancPluginStartMultipartAnswer(context, output, "related", "application/octet-stream") != 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
 
-    std::string uri, content;
-    if (LocateInstance(output, uri, request) &&
-        OrthancPlugins::RestApiGetString(content, context_, uri + "/file"))
-    {
-      OrthancPlugins::ParsedDicomFile dicom(content);
-
-      std::vector<std::string> path;
-      Orthanc::Toolbox::TokenizeString(path, request->groups[3], '/');
-      
-      std::string result;
-      if (path.size() % 2 == 1 &&
-          ExploreBulkData(result, path, 0, dicom.GetDataSet()))
-      {
-        if (OrthancPluginStartMultipartAnswer(context_, output, "related", "application/octet-stream") != 0 ||
-            OrthancPluginSendMultipartItem(context_, output, result.c_str(), result.size()) != 0)
+        for (Json::Value::ArrayIndex i = 0; i < frames.size(); i++)
         {
-          return OrthancPluginErrorCode_Plugin;
+          std::string frame;
+          
+          if (frames[i].type() != Json::stringValue ||
+              !OrthancPlugins::RestApiGetString(frame, orthanc + "/" + frames[i].asString(), false) ||
+              OrthancPluginSendMultipartItem(context, output, frame.c_str(), frame.size()) != 0)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+          }
         }
       }
       else
       {
-        OrthancPluginSendHttpStatusCode(context_, output, 400 /* Bad request */);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
       }      
     }
+    else
+    {
+      if (path.size() % 2 != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                        "Bulk data URI in WADO-RS should have an odd number of items: " + bulk);
+      }
 
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    OrthancPluginLogError(context_, e.What());
-    return OrthancPluginErrorCode_Plugin;
-  }
-  catch (std::runtime_error& e)
-  {
-    OrthancPluginLogError(context_, e.what());
-    return OrthancPluginErrorCode_Plugin;
+      for (size_t i = 0; i < path.size() / 2; i++)
+      {
+        int index;
+
+        try
+        {
+          index = boost::lexical_cast<int>(path[2 * i + 1]);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                          "Bad sequence index in bulk data URI: " + bulk);
+        }
+
+        orthanc += "/" + path[2 * i] + "/" + boost::lexical_cast<std::string>(index - 1);
+      }
+
+      orthanc += "/" + path.back();
+
+      std::string result; 
+      if (OrthancPlugins::RestApiGetString(result, orthanc, false))
+      {
+        if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/octet-stream") != 0 ||
+            OrthancPluginSendMultipartItem(context, output, result.c_str(), result.size()) != 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+      }
+    }
   }
 }
--- a/Plugin/WadoRs.h	Tue Dec 08 17:24:44 2015 +0100
+++ b/Plugin/WadoRs.h	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -23,30 +24,38 @@
 #include "Configuration.h"
 
 
-OrthancPluginErrorCode RetrieveDicomStudy(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request);
+bool LocateInstance(OrthancPluginRestOutput* output,
+                    std::string& uri,
+                    const OrthancPluginHttpRequest* request);
+
+void RetrieveDicomStudy(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode RetrieveDicomSeries(OrthancPluginRestOutput* output,
-                                           const char* url,
-                                           const OrthancPluginHttpRequest* request);
+void RetrieveDicomSeries(OrthancPluginRestOutput* output,
+                         const char* url,
+                         const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode RetrieveDicomInstance(OrthancPluginRestOutput* output,
-                                             const char* url,
-                                             const OrthancPluginHttpRequest* request);
+void RetrieveDicomInstance(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode RetrieveStudyMetadata(OrthancPluginRestOutput* output,
-                                             const char* url,
-                                             const OrthancPluginHttpRequest* request);
+void RetrieveStudyMetadata(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode RetrieveSeriesMetadata(OrthancPluginRestOutput* output,
-                                              const char* url,
-                                              const OrthancPluginHttpRequest* request);
+void RetrieveSeriesMetadata(OrthancPluginRestOutput* output,
+                            const char* url,
+                            const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode RetrieveInstanceMetadata(OrthancPluginRestOutput* output,
-                                                const char* url,
-                                                const OrthancPluginHttpRequest* request);
+void RetrieveInstanceMetadata(OrthancPluginRestOutput* output,
+                              const char* url,
+                              const OrthancPluginHttpRequest* request);
 
-OrthancPluginErrorCode RetrieveBulkData(OrthancPluginRestOutput* output,
-                                        const char* url,
-                                        const OrthancPluginHttpRequest* request);
+void RetrieveBulkData(OrthancPluginRestOutput* output,
+                      const char* url,
+                      const OrthancPluginHttpRequest* request);
+
+void RetrieveFrames(OrthancPluginRestOutput* output,
+                    const char* url,
+                    const OrthancPluginHttpRequest* request);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/WadoRsRetrieveFrames.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,587 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WadoRs.h"
+
+#include "GdcmParsedDicomFile.h"
+
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <memory>
+#include <list>
+#include <gdcmImageReader.h>
+#include <gdcmImageWriter.h>
+#include <gdcmImageChangeTransferSyntax.h>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/lexical_cast.hpp>
+
+
+
+static void TokenizeAndNormalize(std::vector<std::string>& tokens,
+                                 const std::string& source,
+                                 char separator)
+{
+  Orthanc::Toolbox::TokenizeString(tokens, source, separator);
+
+  for (size_t i = 0; i < tokens.size(); i++)
+  {
+    tokens[i] = Orthanc::Toolbox::StripSpaces(tokens[i]);
+    Orthanc::Toolbox::ToLowerCase(tokens[i]);
+  }
+}
+
+
+static void RemoveSurroundingQuotes(std::string& value)
+{
+  if (!value.empty() &&
+      value[0] == '\"' &&
+      value[value.size() - 1] == '\"')
+  {
+    value = value.substr(1, value.size() - 2);
+  }  
+}
+
+
+
+static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest* request)
+{
+  for (uint32_t i = 0; i < request->headersCount; i++)
+  {
+    std::string key(request->headersKeys[i]);
+    Orthanc::Toolbox::ToLowerCase(key);
+
+    if (key == "accept")
+    {
+      std::vector<std::string> tokens;
+      TokenizeAndNormalize(tokens, request->headersValues[i], ';');
+
+      if (tokens.size() == 0 ||
+          tokens[0] == "*/*")
+      {
+        return gdcm::TransferSyntax::ImplicitVRLittleEndian;
+      }
+
+      if (tokens[0] != "multipart/related")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      std::string type("application/octet-stream");
+      std::string transferSyntax;
+      
+      for (size_t j = 1; j < tokens.size(); j++)
+      {
+        std::vector<std::string> parsed;
+        TokenizeAndNormalize(parsed, tokens[j], '=');
+
+        if (parsed.size() != 2)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
+        }
+
+        if (parsed[0] == "type")
+        {
+          type = parsed[1];
+          RemoveSurroundingQuotes(type);
+        }
+
+        if (parsed[0] == "transfer-syntax")
+        {
+          transferSyntax = parsed[1];
+          RemoveSurroundingQuotes(transferSyntax);
+        }
+      }
+
+      if (type == "application/octet-stream")
+      {
+        if (transferSyntax.empty())
+        {
+          return gdcm::TransferSyntax(gdcm::TransferSyntax::ImplicitVRLittleEndian);
+        }
+        else
+        {
+          throw Orthanc::OrthancException(
+            Orthanc::ErrorCode_BadRequest,
+            "DICOMweb RetrieveFrames: Cannot specify a transfer syntax (" + 
+            transferSyntax + ") for default Little Endian uncompressed pixel data");
+        }
+      }
+      else
+      {
+        /**
+         * DICOM 2017c
+         * http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.1.1.8-3b
+         **/
+        if (type == "image/jpeg" && (transferSyntax.empty() ||  // Default
+                                     transferSyntax == "1.2.840.10008.1.2.4.70"))
+        {
+          return gdcm::TransferSyntax::JPEGLosslessProcess14_1;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50")
+        {
+          return gdcm::TransferSyntax::JPEGBaselineProcess1;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51")
+        {
+          return gdcm::TransferSyntax::JPEGExtendedProcess2_4;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57")
+        {
+          return gdcm::TransferSyntax::JPEGLosslessProcess14;
+        }
+        else if (type == "image/x-dicom-rle" && (transferSyntax.empty() ||  // Default
+                                                 transferSyntax == "1.2.840.10008.1.2.5"))
+        {
+          return gdcm::TransferSyntax::RLELossless;
+        }
+        else if (type == "image/x-jls" && (transferSyntax.empty() ||  // Default
+                                           transferSyntax == "1.2.840.10008.1.2.4.80"))
+        {
+          return gdcm::TransferSyntax::JPEGLSLossless;
+        }
+        else if (type == "image/x-jls" && transferSyntax == "1.2.840.10008.1.2.4.81")
+        {
+          return gdcm::TransferSyntax::JPEGLSNearLossless;
+        }
+        else if (type == "image/jp2" && (transferSyntax.empty() ||  // Default
+                                         transferSyntax == "1.2.840.10008.1.2.4.90"))
+        {
+          return gdcm::TransferSyntax::JPEG2000Lossless;
+        }
+        else if (type == "image/jp2" && transferSyntax == "1.2.840.10008.1.2.4.91")
+        {
+          return gdcm::TransferSyntax::JPEG2000;
+        }
+        else if (type == "image/jpx" && (transferSyntax.empty() ||  // Default
+                                         transferSyntax == "1.2.840.10008.1.2.4.92"))
+        {
+          return gdcm::TransferSyntax::JPEG2000Part2Lossless;
+        }
+        else if (type == "image/jpx" && transferSyntax == "1.2.840.10008.1.2.4.93")
+        {
+          return gdcm::TransferSyntax::JPEG2000Part2;
+        }
+
+
+        /**
+         * Backward compatibility with DICOM 2014a
+         * http://dicom.nema.org/medical/dicom/2014a/output/html/part18.html#table_6.5-1
+         **/
+        if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50")
+        {
+          return gdcm::TransferSyntax::JPEGBaselineProcess1;
+        }
+        else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51")
+        {
+          return gdcm::TransferSyntax::JPEGExtendedProcess2_4;
+        }
+        else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57")
+        {
+          return gdcm::TransferSyntax::JPEGLosslessProcess14;
+        }
+        else if (type == "image/dicom+jpeg" && (transferSyntax.empty() ||
+                                                transferSyntax == "1.2.840.10008.1.2.4.70"))
+        {
+          return gdcm::TransferSyntax::JPEGLosslessProcess14_1;
+        }
+        else if (type == "image/dicom+rle" && (transferSyntax.empty() ||
+                                               transferSyntax == "1.2.840.10008.1.2.5"))
+        {
+          return gdcm::TransferSyntax::RLELossless;
+        }
+        else if (type == "image/dicom+jpeg-ls" && (transferSyntax.empty() ||
+                                                   transferSyntax == "1.2.840.10008.1.2.4.80"))
+        {
+          return gdcm::TransferSyntax::JPEGLSLossless;
+        }
+        else if (type == "image/dicom+jpeg-ls" && transferSyntax == "1.2.840.10008.1.2.4.81")
+        {
+          return gdcm::TransferSyntax::JPEGLSNearLossless;
+        }
+        else if (type == "image/dicom+jp2" && (transferSyntax.empty() ||
+                                               transferSyntax == "1.2.840.10008.1.2.4.90"))
+        {
+          return gdcm::TransferSyntax::JPEG2000Lossless;
+        }
+        else if (type == "image/dicom+jp2" && transferSyntax == "1.2.840.10008.1.2.4.91")
+        {
+          return gdcm::TransferSyntax::JPEG2000;
+        }
+        else if (type == "image/dicom+jpx" && (transferSyntax.empty() ||
+                                               transferSyntax == "1.2.840.10008.1.2.4.92"))
+        {
+          return gdcm::TransferSyntax::JPEG2000Part2Lossless;
+        }
+        else if (type == "image/dicom+jpx" && transferSyntax == "1.2.840.10008.1.2.4.93")
+        {
+          return gdcm::TransferSyntax::JPEG2000Part2;
+        }
+
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_BadRequest,
+          "DICOMweb RetrieveFrames: Transfer syntax \"" + 
+          transferSyntax + "\" is incompatible with media type \"" + type + "\"");
+      }
+    }
+  }
+
+  // By default, DICOMweb expectes Little Endian uncompressed pixel data
+  return gdcm::TransferSyntax::ImplicitVRLittleEndian;
+}
+
+
+static void ParseFrameList(std::list<unsigned int>& frames,
+                           const OrthancPluginHttpRequest* request)
+{
+  frames.clear();
+
+  if (request->groupsCount <= 3 ||
+      request->groups[3] == NULL)
+  {
+    return;
+  }
+
+  std::string source(request->groups[3]);
+  Orthanc::Toolbox::ToLowerCase(source);
+  boost::replace_all(source, "%2c", ",");
+
+  std::vector<std::string> tokens;
+  Orthanc::Toolbox::TokenizeString(tokens, source, ',');
+
+  for (size_t i = 0; i < tokens.size(); i++)
+  {
+    int frame = boost::lexical_cast<int>(tokens[i]);
+    if (frame <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Invalid frame number (must be > 0): " + tokens[i]);
+    }
+
+    frames.push_back(static_cast<unsigned int>(frame - 1));
+  }
+}                           
+
+
+
+static const char* GetMimeType(const gdcm::TransferSyntax& syntax)
+{
+  // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.1.1.8-3b
+  
+  switch (syntax)
+  {
+    case gdcm::TransferSyntax::ImplicitVRLittleEndian:
+      return "application/octet-stream";
+
+    case gdcm::TransferSyntax::JPEGBaselineProcess1:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.50";
+
+    case gdcm::TransferSyntax::JPEGExtendedProcess2_4:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.51";
+    
+    case gdcm::TransferSyntax::JPEGLosslessProcess14:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.57";
+
+    case gdcm::TransferSyntax::JPEGLosslessProcess14_1:
+      return "image/jpeg; transferSyntax=1.2.840.10008.1.2.4.70";
+    
+    case gdcm::TransferSyntax::RLELossless:
+      return "image/x-dicom-rle; transferSyntax=1.2.840.10008.1.2.5";
+
+    case gdcm::TransferSyntax::JPEGLSLossless:
+      return "image/x-jls; transferSyntax=1.2.840.10008.1.2.4.80";
+
+    case gdcm::TransferSyntax::JPEGLSNearLossless:
+      return "image/x-jls; transfer-syntax=1.2.840.10008.1.2.4.81";
+
+    case gdcm::TransferSyntax::JPEG2000Lossless:
+      return "image/jp2; transferSyntax=1.2.840.10008.1.2.4.90";
+
+    case gdcm::TransferSyntax::JPEG2000:
+      return "image/jp2; transfer-syntax=1.2.840.10008.1.2.4.91";
+
+    case gdcm::TransferSyntax::JPEG2000Part2Lossless:
+      return "image/jpx; transferSyntax=1.2.840.10008.1.2.4.92";
+
+    case gdcm::TransferSyntax::JPEG2000Part2:
+      return "image/jpx; transfer-syntax=1.2.840.10008.1.2.4.93";
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
+
+
+
+static void AnswerSingleFrame(OrthancPluginRestOutput* output,
+                              const OrthancPluginHttpRequest* request,
+                              const OrthancPlugins::GdcmParsedDicomFile& dicom,
+                              const char* frame,
+                              size_t size,
+                              unsigned int frameIndex)
+{
+  OrthancPluginErrorCode error;
+
+#if HAS_SEND_MULTIPART_ITEM_2 == 1
+  std::string location = dicom.GetWadoUrl(request) + "frames/" + boost::lexical_cast<std::string>(frameIndex + 1);
+  const char *keys[] = { "Content-Location" };
+  const char *values[] = { location.c_str() };
+  error = OrthancPluginSendMultipartItem2(OrthancPlugins::GetGlobalContext(), output, frame, size, 1, keys, values);
+#else
+  error = OrthancPluginSendMultipartItem(OrthancPlugins::GetGlobalContext(), output, frame, size);
+#endif
+
+  if (error != OrthancPluginErrorCode_Success)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);      
+  }
+}
+
+
+
+static bool AnswerFrames(OrthancPluginRestOutput* output,
+                         const OrthancPluginHttpRequest* request,
+                         const OrthancPlugins::GdcmParsedDicomFile& dicom,
+                         const gdcm::TransferSyntax& syntax,
+                         std::list<unsigned int>& frames)
+{
+  static const gdcm::Tag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
+  static const gdcm::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const gdcm::Tag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
+  static const gdcm::Tag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const gdcm::Tag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+
+  if (!dicom.GetDataSet().FindDataElement(DICOM_TAG_PIXEL_DATA))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+  }
+
+  const gdcm::DataElement& pixelData = dicom.GetDataSet().GetDataElement(DICOM_TAG_PIXEL_DATA);
+  const gdcm::SequenceOfFragments* fragments = pixelData.GetSequenceOfFragments();
+
+  if (OrthancPluginStartMultipartAnswer(OrthancPlugins::GetGlobalContext(), 
+                                        output, "related", GetMimeType(syntax)) != OrthancPluginErrorCode_Success)
+  {
+    return false;
+  }
+
+  if (fragments == NULL)
+  {
+    // Single-fragment image
+
+    if (pixelData.GetByteValue() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                      "Image was not properly decoded");
+    }
+
+    int width, height, bits, samplesPerPixel;
+
+    if (!dicom.GetIntegerTag(height, DICOM_TAG_ROWS) ||
+        !dicom.GetIntegerTag(width, DICOM_TAG_COLUMNS) ||
+        !dicom.GetIntegerTag(bits, DICOM_TAG_BITS_ALLOCATED) || 
+        !dicom.GetIntegerTag(samplesPerPixel, DICOM_TAG_SAMPLES_PER_PIXEL))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    size_t frameSize = height * width * bits * samplesPerPixel / 8;
+    
+    if (pixelData.GetByteValue()->GetLength() % frameSize != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
+    }
+
+    size_t framesCount = pixelData.GetByteValue()->GetLength() / frameSize;
+
+    if (frames.empty())
+    {
+      // If no frame is provided, return all the frames (this is an extension)
+      for (size_t i = 0; i < framesCount; i++)
+      {
+        frames.push_back(i);
+      }
+    }
+
+    const char* buffer = pixelData.GetByteValue()->GetPointer();
+    assert(sizeof(char) == 1);
+
+    for (std::list<unsigned int>::const_iterator 
+           frame = frames.begin(); frame != frames.end(); ++frame)
+    {
+      if (*frame >= framesCount)
+      {
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_ParameterOutOfRange,
+          "Trying to access frame number " + boost::lexical_cast<std::string>(*frame + 1) + 
+          " of an image with " + boost::lexical_cast<std::string>(framesCount) + " frames");
+      }
+      else
+      {
+        const char* p = buffer + (*frame) * frameSize;
+        AnswerSingleFrame(output, request, dicom, p, frameSize, *frame);
+      }
+    }
+  }
+  else
+  {
+    // Multi-fragment image, we assume that each fragment corresponds to one frame
+
+    if (frames.empty())
+    {
+      // If no frame is provided, return all the frames (this is an extension)
+      for (size_t i = 0; i < fragments->GetNumberOfFragments(); i++)
+      {
+        frames.push_back(i);
+      }
+    }
+
+    for (std::list<unsigned int>::const_iterator 
+           frame = frames.begin(); frame != frames.end(); ++frame)
+    {
+      if (*frame >= fragments->GetNumberOfFragments())
+      {
+        // TODO A frame is not a fragment, looks like a bug
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_ParameterOutOfRange,
+          "Trying to access frame number " + 
+          boost::lexical_cast<std::string>(*frame + 1) + 
+          " of an image with " + 
+          boost::lexical_cast<std::string>(fragments->GetNumberOfFragments()) + 
+          " frames");
+      }
+      else
+      {
+        AnswerSingleFrame(output, request, dicom,
+                          fragments->GetFragment(*frame).GetByteValue()->GetPointer(),
+                          fragments->GetFragment(*frame).GetByteValue()->GetLength(), *frame);
+      }
+    }
+  }
+
+  return true;
+}
+
+
+
+void RetrieveFrames(OrthancPluginRestOutput* output,
+                    const char* url,
+                    const OrthancPluginHttpRequest* request)
+{
+  gdcm::TransferSyntax targetSyntax(ParseTransferSyntax(request));
+
+  std::list<unsigned int> frames;
+  ParseFrameList(frames, request);
+
+  Json::Value header;
+  std::string publicId;
+  OrthancPlugins::MemoryBuffer content;
+  if (LocateInstance(output, publicId, request) &&
+      content.RestApiGet("/instances/" + publicId + "/file", false) &&
+      OrthancPlugins::RestApiGet(header, "/instances/" + publicId + "/header?simplify", false))
+  {
+    {
+      std::string s = "DICOMweb RetrieveFrames on " + publicId + ", frames: ";
+      for (std::list<unsigned int>::const_iterator 
+             frame = frames.begin(); frame != frames.end(); ++frame)
+      {
+        s += boost::lexical_cast<std::string>(*frame + 1) + " ";
+      }
+
+      OrthancPlugins::LogInfo(s);
+    }
+
+    std::auto_ptr<OrthancPlugins::GdcmParsedDicomFile> source;
+
+    gdcm::TransferSyntax sourceSyntax;
+
+    if (header.type() == Json::objectValue &&
+        header.isMember("TransferSyntaxUID"))
+    {
+      sourceSyntax = gdcm::TransferSyntax::GetTSType(header["TransferSyntaxUID"].asCString());
+    }
+    else
+    {
+      source.reset(new OrthancPlugins::GdcmParsedDicomFile(content));
+      sourceSyntax = source->GetFile().GetHeader().GetDataSetTransferSyntax();
+    }
+
+    if (sourceSyntax == targetSyntax ||
+        (targetSyntax == gdcm::TransferSyntax::ImplicitVRLittleEndian &&
+         sourceSyntax == gdcm::TransferSyntax::ExplicitVRLittleEndian))
+    {
+      // No need to change the transfer syntax
+
+      if (source.get() == NULL)
+      {
+        source.reset(new OrthancPlugins::GdcmParsedDicomFile(content));
+      }
+
+      AnswerFrames(output, request, *source, targetSyntax, frames);
+    }
+    else
+    {
+      // Need to convert the transfer syntax
+
+      {
+        OrthancPlugins::LogInfo("DICOMweb RetrieveFrames: Transcoding instance " + publicId + 
+                                " from transfer syntax " + std::string(sourceSyntax.GetString()) + 
+                                " to " + std::string(targetSyntax.GetString()));
+      }
+
+      gdcm::ImageChangeTransferSyntax change;
+      change.SetTransferSyntax(targetSyntax);
+
+      // TODO Avoid this unnecessary memcpy by defining a stream over the MemoryBuffer
+      std::string dicom(content.GetData(), content.GetData() + content.GetSize());
+      std::stringstream stream(dicom);
+
+      gdcm::ImageReader reader;
+      reader.SetStream(stream);
+      if (!reader.Read())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Cannot decode the image");
+      }
+
+      change.SetInput(reader.GetImage());
+      if (!change.Change())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                        "Cannot change the transfer syntax of the image");
+      }
+
+      gdcm::ImageWriter writer;
+      writer.SetImage(change.GetOutput());
+      writer.SetFile(reader.GetFile());
+      
+      std::stringstream ss;
+      writer.SetStream(ss);
+      if (!writer.Write())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+      }
+
+      OrthancPlugins::GdcmParsedDicomFile transcoded(ss.str());
+      AnswerFrames(output, request, transcoded, targetSyntax, frames);
+    }
+  }    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/WadoUri.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,260 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WadoUri.h"
+
+#include "Configuration.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <string>
+
+
+static bool MapWadoToOrthancIdentifier(std::string& orthanc,
+                                       char* (*func) (OrthancPluginContext*, const char*),
+                                       const std::string& dicom)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  char* tmp = func(context, dicom.c_str());
+
+  if (tmp)
+  {
+    orthanc = tmp;
+    OrthancPluginFreeString(context, tmp);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+static bool LocateInstanceWadoUri(std::string& instance,
+                                  std::string& contentType,
+                                  const OrthancPluginHttpRequest* request)
+{
+  std::string requestType, studyUid, seriesUid, objectUid;
+
+  for (uint32_t i = 0; i < request->getCount; i++)
+  {
+    std::string key(request->getKeys[i]);
+    std::string value(request->getValues[i]);
+
+    if (key == "studyUID")
+    {
+      studyUid = value;
+    }
+    else if (key == "seriesUID")
+    {
+      seriesUid = value;
+    }
+    else if (key == "objectUID")  // In WADO-URI, "objectUID" corresponds to "SOPInstanceUID"
+    {
+      objectUid = value;
+    }
+    else if (key == "requestType")
+    {
+      requestType = value;
+    }
+    else if (key == "contentType")
+    {
+      contentType = value;
+    }
+  }
+
+  if (requestType != "WADO")
+  {
+    OrthancPlugins::LogError("WADO-URI: Invalid requestType: \"" + requestType + "\"");
+    return false;
+  }
+
+  if (objectUid.empty())
+  {
+    OrthancPlugins::LogError("WADO-URI: No SOPInstanceUID provided");
+    return false;
+  }
+
+  if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
+  {
+    OrthancPlugins::LogError("WADO-URI: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"");
+    return false;
+  }
+
+  /**
+   * Below are only sanity checks to ensure that the possibly provided
+   * "seriesUID" and "studyUID" match that of the provided instance.
+   **/
+
+  if (!seriesUid.empty())
+  {
+    std::string series;
+    if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
+    {
+      OrthancPlugins::LogError("WADO-URI: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"");
+      return false;
+    }
+    else
+    {
+      Json::Value info;
+      if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/series", false) ||
+          info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
+      {
+        OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid);
+        return false;
+      }
+    }
+  }
+  
+  if (!studyUid.empty())
+  {
+    std::string study;
+    if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
+    {
+      OrthancPlugins::LogError("WADO-URI: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"");
+      return false;
+    }
+    else
+    {
+      Json::Value info;
+      if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/study", false) ||
+          info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
+      {
+        OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid);
+        return false;
+      }
+    }
+  }
+  
+  return true;
+}
+
+
+static void AnswerDicom(OrthancPluginRestOutput* output,
+                        const std::string& instance)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  std::string uri = "/instances/" + instance + "/file";
+
+  OrthancPlugins::MemoryBuffer dicom;
+  if (dicom.RestApiGet(uri, false))
+  {
+    OrthancPluginAnswerBuffer(context, output, 
+                              dicom.GetData(), dicom.GetSize(), "application/dicom");
+  }
+  else
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin,
+                                    "WADO-URI: Unable to retrieve DICOM file from " + uri);
+  }
+}
+
+
+static bool RetrievePngPreview(OrthancPlugins::MemoryBuffer& png,
+                               const std::string& instance)
+{
+  std::string uri = "/instances/" + instance + "/preview";
+
+  if (png.RestApiGet(uri, true))
+  {
+    return true;
+  }
+  else
+  {
+    OrthancPlugins::LogError("WADO-URI: Unable to generate a preview image for " + uri);
+    return false;
+  }
+}
+
+
+static void AnswerPngPreview(OrthancPluginRestOutput* output,
+                             const std::string& instance)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  OrthancPlugins::MemoryBuffer png;
+  if (RetrievePngPreview(png, instance))
+  {
+    OrthancPluginAnswerBuffer(context, output, 
+                              png.GetData(), png.GetSize(), "image/png");
+  }
+  else
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+  }
+}
+
+
+static void AnswerJpegPreview(OrthancPluginRestOutput* output,
+                              const std::string& instance)
+{
+  // Retrieve the preview in the PNG format
+  OrthancPlugins::MemoryBuffer png;
+  if (!RetrievePngPreview(png, instance))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+  }
+  
+  OrthancPlugins::OrthancImage image;
+  image.UncompressPngImage(png.GetData(), png.GetSize());
+  image.AnswerJpegImage(output, 90 /* quality */);
+}
+
+
+void WadoUriCallback(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+    return;
+  }
+
+  std::string instance;
+  std::string contentType = "image/jpg";  // By default, JPEG image will be returned
+  if (!LocateInstanceWadoUri(instance, contentType, request))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+  }
+
+  if (contentType == "application/dicom")
+  {
+    AnswerDicom(output, instance);
+  }
+  else if (contentType == "image/png")
+  {
+    AnswerPngPreview(output, instance);
+  }
+  else if (contentType == "image/jpeg" ||
+           contentType == "image/jpg")
+  {
+    AnswerJpegPreview(output, instance);
+  }
+  else
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_BadRequest,
+      "WADO-URI: Unsupported content type: \"" + contentType + "\"");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/WadoUri.h	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,28 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Configuration.h"
+
+void WadoUriCallback(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request);
--- a/README	Tue Dec 08 17:24:44 2015 +0100
+++ b/README	Fri Jun 07 16:03:50 2019 +0200
@@ -1,5 +1,5 @@
-DICOM Web plugin for Orthanc
-============================
+DICOMweb plugin for Orthanc
+===========================
 
 
 General Information
@@ -10,8 +10,8 @@
 extends the RESTful API of Orthanc with WADO and DICOMweb support.
 
 
-DICOM Web Support
------------------
+DICOMweb Support
+----------------
 
 Currently, a basic support of the following protocols is provided:
 
@@ -32,23 +32,17 @@
 the "./Status.txt" file.
 
 
-Install
--------
-
-Build instructions can be found in "./Resources/BuildInstructions.txt".
-
+Installation and usage
+----------------------
 
-Samples
--------
-
-Python samples to call the DICOM Web services can be found in the
-"./Samples" folder.
+Build and usage instructions are available in the Orthanc Book:
+http://book.orthanc-server.com/plugins/dicomweb.html
 
 
 Licensing: AGPL
 ---------------
 
-The DICOM Web plugin for Orthanc is licensed under the Affero General
+The DICOMweb plugin for Orthanc is licensed under the Affero General
 Public License (AGPL) license. Pay attention to the fact that this
 license is more restrictive than the license of the Orthanc core.
 
@@ -58,14 +52,17 @@
 use of Orthanc to warn us about this use. You can cite our work
 using the following BibTeX entry:
 
-@inproceedings{Jodogne:ISBI2013,
-  author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.},
-  title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research},
-  booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, 
-  year={2013}, 
-  pages={190-193}, 
-  ISSN={1945-7928},
-  month=apr,
-  url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444},
-  address={San Francisco, {CA}, {USA}}
+@Article{Jodogne2018,
+  author="Jodogne, S{\'e}bastien",
+  title="The {O}rthanc Ecosystem for Medical Imaging",
+  journal="Journal of Digital Imaging",
+  year="2018",
+  month="Jun",
+  day="01",
+  volume="31",
+  number="3",
+  pages="341--352",
+  issn="1618-727X",
+  doi="10.1007/s10278-018-0082-y",
+  url="https://doi.org/10.1007/s10278-018-0082-y"
 }
--- a/Resources/BuildInstructions.txt	Tue Dec 08 17:24:44 2015 +0100
+++ b/Resources/BuildInstructions.txt	Fri Jun 07 16:03:50 2019 +0200
@@ -1,9 +1,9 @@
-Generic Linux (static linking)
-==============================
+Generic GNU/Linux (static linking)
+==================================
 
 # mkdir Build
 # cd Build
-# cmake .. -DCMAKE_BUILD_TYPE=Debug -DALLOW_DOWNLOADS=ON -DSTATIC_BUILD=ON
+# cmake .. -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON
 # make
 
 
@@ -12,7 +12,7 @@
 
 # mkdir Build
 # cd Build
-# cmake .. -DCMAKE_BUILD_TYPE=Debug -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON
+# cmake .. -DCMAKE_BUILD_TYPE=Debug -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON -DUSE_SYSTEM_ORTHANC_SDK=OFF
 # make
 
 
@@ -25,12 +25,12 @@
   -DALLOW_DOWNLOADS=ON \
   -DUSE_SYSTEM_JSONCPP=OFF \
   -DUSE_SYSTEM_PUGIXML=OFF \
-  -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON
+  -DUSE_GOOGLE_TEST_DEBIAN_SOURCE_PACKAGE=ON
 # make
 
 
-Cross-compiling for Windows from Linux using MinGW
-==================================================
+Cross-compiling for Windows from GNU/Linux using MinGW
+======================================================
 
 # mkdir Build
 # cd Build
@@ -41,6 +41,7 @@
 Notes
 =====
 
-List the public symbols exported by the shared library under Linux:
+List the public symbols exported by the shared library under
+GNU/Linux:
 
 # nm -C -D --defined-only ./libOrthancDicomWeb.so
--- a/Resources/CMake/GdcmConfiguration.cmake	Tue Dec 08 17:24:44 2015 +0100
+++ b/Resources/CMake/GdcmConfiguration.cmake	Fri Jun 07 16:03:50 2019 +0200
@@ -1,6 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2019 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Affero General Public License
@@ -17,12 +18,16 @@
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM)
-  # If using gcc, build GDCM with the "-fPIC" argument to allow its
-  # embedding into the shared library containing the Orthanc plugin
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
+    # If using gcc, build GDCM with the "-fPIC" argument to allow its
+    # embedding into the shared library containing the Orthanc plugin
     set(AdditionalFlags "-fPIC")
+  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # This definition is necessary to compile
+    # "Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx"
+    set(AdditionalFlags "-Doff64_t=off_t") 
   endif()
   
   set(Flags
@@ -39,15 +44,42 @@
     )
 
   if (CMAKE_TOOLCHAIN_FILE)
-    list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
+    # Take absolute path to the toolchain
+    get_filename_component(TMP ${CMAKE_TOOLCHAIN_FILE} REALPATH BASE ${CMAKE_SOURCE_DIR})
+    list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${TMP})
+  endif()
+
+  # Don't build manpages (since gdcm 2.8.4)
+  list(APPEND Flags -DGDCM_BUILD_DOCBOOK_MANPAGES=OFF)
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    # Trick to disable the compilation of socket++ by gdcm, which is
+    # incompatible with LSB, but fortunately only required for DICOM
+    # Networking
+    list(APPEND Flags -DGDCM_USE_SYSTEM_SOCKETXX=ON)
+
+    # Detect the number of CPU cores to run "make" with as much
+    # parallelism as possible
+    include(ProcessorCount)
+    ProcessorCount(N)
+    if (NOT N EQUAL 0)
+      set(MAKE_PARALLEL -j${N})
+    endif()
+      
+    # For Linux Standard Base, avoid building incompatible target gdcmMEXD (*)
+    set(BUILD_COMMAND BUILD_COMMAND
+      ${CMAKE_MAKE_PROGRAM} ${MAKE_PARALLEL}
+      gdcmMSFF gdcmcharls gdcmDICT gdcmDSED gdcmIOD gdcmjpeg8
+      gdcmjpeg12 gdcmjpeg16 gdcmopenjp2 gdcmzlib gdcmCommon gdcmexpat gdcmuuid)
   endif()
 
   include(ExternalProject)
   externalproject_add(GDCM
-    URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gdcm-2.6.0.tar.gz"
-    URL_MD5 "978afe57af448b1c97c9f116790aca9c"
+    URL "http://orthanc.osimis.io/ThirdPartyDownloads/gdcm-2.8.8.tar.gz"
+    URL_MD5 "740c22b787a8e47e4d748c167e450d8f"
+    TIMEOUT 60
     CMAKE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ${Flags}
-    #-DLIBRARY_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
+    ${BUILD_COMMAND}    # Customize "make", only for Linux Standard Base (*)
     INSTALL_COMMAND ""  # Skip the install step
     )
 
@@ -59,7 +91,8 @@
     list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
   endif()
 
-  set(GDCM_LIBRARIES 
+  set(GDCM_LIBRARIES
+    # WARNING: The order of the libraries below *is* important!
     ${Prefix}gdcmMSFF${Suffix}
     ${Prefix}gdcmcharls${Suffix}
     ${Prefix}gdcmDICT${Suffix}
@@ -68,17 +101,26 @@
     ${Prefix}gdcmjpeg8${Suffix}
     ${Prefix}gdcmjpeg12${Suffix}
     ${Prefix}gdcmjpeg16${Suffix}
-    ${Prefix}gdcmMEXD${Suffix}
-    ${Prefix}gdcmopenjpeg${Suffix}
+    ${Prefix}gdcmopenjp2${Suffix}
     ${Prefix}gdcmzlib${Suffix}
-    ${Prefix}socketxx${Suffix}
     ${Prefix}gdcmCommon${Suffix}
     ${Prefix}gdcmexpat${Suffix}
 
+    #${Prefix}socketxx${Suffix}
+    #${Prefix}gdcmMEXD${Suffix}  # DICOM Networking, unneeded by Orthanc plugins
     #${Prefix}gdcmgetopt${Suffix}
-    #${Prefix}gdcmuuid${Suffix}
     )
 
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(APPEND GDCM_LIBRARIES
+      rpcrt4   # For UUID stuff
+      )
+  else()
+    list(APPEND GDCM_LIBRARIES
+      ${Prefix}gdcmuuid${Suffix}
+      )
+  endif()
+
   ExternalProject_Get_Property(GDCM binary_dir)
   include_directories(${binary_dir}/Source/Common)
   link_directories(${binary_dir}/bin)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/DownloadOrthancFramework.cmake	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,342 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2019 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU 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/>.
+
+
+
+##
+## Check whether the parent script sets the mandatory variables
+##
+
+if (NOT DEFINED ORTHANC_FRAMEWORK_SOURCE OR
+    (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "web" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "path"))
+  message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_SOURCE must be set to \"hg\", \"web\", \"archive\" or \"path\"")
+endif()
+
+
+##
+## Detection of the requested version
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_VERSION)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_VERSION must be set")
+  endif()
+
+  if (DEFINED ORTHANC_FRAMEWORK_MAJOR OR
+      DEFINED ORTHANC_FRAMEWORK_MINOR OR
+      DEFINED ORTHANC_FRAMEWORK_REVISION OR
+      DEFINED ORTHANC_FRAMEWORK_MD5)
+    message(FATAL_ERROR "Some internal variable has been set")
+  endif()
+
+  set(ORTHANC_FRAMEWORK_MD5 "")
+
+  if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH)
+    if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline")
+      set(ORTHANC_FRAMEWORK_BRANCH "default")
+
+    else()
+      set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
+
+      set(RE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
+      string(REGEX REPLACE ${RE} "\\1" ORTHANC_FRAMEWORK_MAJOR ${ORTHANC_FRAMEWORK_VERSION})
+      string(REGEX REPLACE ${RE} "\\2" ORTHANC_FRAMEWORK_MINOR ${ORTHANC_FRAMEWORK_VERSION})
+      string(REGEX REPLACE ${RE} "\\3" ORTHANC_FRAMEWORK_REVISION ${ORTHANC_FRAMEWORK_VERSION})
+
+      if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR
+          NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR
+          NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$")
+        message("Bad version of the Orthanc framework: ${ORTHANC_FRAMEWORK_VERSION}")
+      endif()
+
+      if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1")
+        set(ORTHANC_FRAMEWORK_MD5 "dac95bd6cf86fb19deaf4e612961f378")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.2")
+        set(ORTHANC_FRAMEWORK_MD5 "d0ccdf68e855d8224331f13774992750")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.0")
+        set(ORTHANC_FRAMEWORK_MD5 "81e15f34d97ac32bbd7d26e85698835a")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.1")
+        set(ORTHANC_FRAMEWORK_MD5 "9b6f6114264b17ed421b574cd6476127")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.2")
+        set(ORTHANC_FRAMEWORK_MD5 "d1ee84927dcf668e60eb5868d24b9394")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.0")
+        set(ORTHANC_FRAMEWORK_MD5 "4429d8d9dea4ff6648df80ec3c64d79e")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.1")
+        set(ORTHANC_FRAMEWORK_MD5 "099671538865e5da96208b37494d6718")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.2")
+        set(ORTHANC_FRAMEWORK_MD5 "8867050f3e9a1ce6157c1ea7a9433b1b")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.3")
+        set(ORTHANC_FRAMEWORK_MD5 "bf2f5ed1adb8b0fc5f10d278e68e1dfe")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.4")
+        set(ORTHANC_FRAMEWORK_MD5 "404baef5d4c43e7c5d9410edda8ef5a5")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.5")
+        set(ORTHANC_FRAMEWORK_MD5 "cfc437e0687ae4bd725fd93dc1f08bc4")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.6")
+        set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0")
+      endif()
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Detection of the third-party software
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg")
+  find_program(ORTHANC_FRAMEWORK_HG hg)
+  
+  if (${ORTHANC_FRAMEWORK_HG} MATCHES "ORTHANC_FRAMEWORK_HG-NOTFOUND")
+    message(FATAL_ERROR "Please install Mercurial")
+  endif()
+endif()
+
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    find_program(ORTHANC_FRAMEWORK_7ZIP 7z 
+      PATHS 
+      "$ENV{ProgramFiles}/7-Zip"
+      "$ENV{ProgramW6432}/7-Zip"
+      )
+
+    if (${ORTHANC_FRAMEWORK_7ZIP} MATCHES "ORTHANC_FRAMEWORK_7ZIP-NOTFOUND")
+      message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+    endif()
+
+  else()
+    find_program(ORTHANC_FRAMEWORK_TAR tar)
+    if (${ORTHANC_FRAMEWORK_TAR} MATCHES "ORTHANC_FRAMEWORK_TAR-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'tar' package")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework specified as a path on the filesystem
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ROOT)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ROOT must provide the path to the sources of Orthanc")
+  endif()
+  
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT})
+    message(FATAL_ERROR "Non-existing directory: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+  
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+    message(FATAL_ERROR "Directory not containing the source code of Orthanc: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+  
+  set(ORTHANC_ROOT ${ORTHANC_FRAMEWORK_ROOT})
+endif()
+
+
+
+##
+## Case of the Orthanc framework cloned using Mercurial
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg")
+  if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+    message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+  endif()
+
+  set(ORTHANC_ROOT ${CMAKE_BINARY_DIR}/orthanc)
+
+  if (EXISTS ${ORTHANC_ROOT})
+    message("Updating the Orthanc source repository using Mercurial")
+    execute_process(
+      COMMAND ${ORTHANC_FRAMEWORK_HG} pull
+      WORKING_DIRECTORY ${ORTHANC_ROOT}
+      RESULT_VARIABLE Failure
+      )    
+  else()
+    message("Forking the Orthanc source repository using Mercurial")
+    execute_process(
+      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://bitbucket.org/sjodogne/orthanc"
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )    
+  endif()
+
+  if (Failure OR NOT EXISTS ${ORTHANC_ROOT})
+    message(FATAL_ERROR "Cannot fork the Orthanc repository")
+  endif()
+
+  message("Setting branch of the Orthanc repository to: ${ORTHANC_FRAMEWORK_BRANCH}")
+
+  execute_process(
+    COMMAND ${ORTHANC_FRAMEWORK_HG} update -c ${ORTHANC_FRAMEWORK_BRANCH}
+    WORKING_DIRECTORY ${ORTHANC_ROOT}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while running Mercurial")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework provided as a source archive on the
+## filesystem
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ARCHIVE must provide the path to the sources of Orthanc")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework downloaded from the Web
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (DEFINED ORTHANC_FRAMEWORK_URL)
+    string(REGEX REPLACE "^.*/" "" ORTHANC_FRAMEMORK_FILENAME "${ORTHANC_FRAMEWORK_URL}")
+  else()
+    # Default case: Download from the official Web site
+    set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz)
+    set(ORTHANC_FRAMEWORK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
+  endif()
+
+  set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}")
+
+  if (NOT EXISTS "${ORTHANC_FRAMEWORK_ARCHIVE}")
+    if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+      message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+    endif()
+
+    message("Downloading: ${ORTHANC_FRAMEWORK_URL}")
+
+    file(DOWNLOAD
+      "${ORTHANC_FRAMEWORK_URL}" "${ORTHANC_FRAMEWORK_ARCHIVE}" 
+      SHOW_PROGRESS EXPECTED_MD5 "${ORTHANC_FRAMEWORK_MD5}"
+      TIMEOUT 60
+      INACTIVITY_TIMEOUT 60
+      )
+  else()
+    message("Using local copy of: ${ORTHANC_FRAMEWORK_URL}")
+  endif()  
+endif()
+
+
+
+
+##
+## Uncompressing the Orthanc framework, if it was retrieved from a
+## source archive on the filesystem, or from the official Web site
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE OR
+      NOT DEFINED ORTHANC_FRAMEWORK_VERSION OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MD5)
+    message(FATAL_ERROR "Internal error")
+  endif()
+
+  if (ORTHANC_FRAMEWORK_MD5 STREQUAL "")
+    message(FATAL_ERROR "Unknown release of Orthanc: ${ORTHANC_FRAMEWORK_VERSION}")
+  endif()
+
+  file(MD5 ${ORTHANC_FRAMEWORK_ARCHIVE} ActualMD5)
+
+  if (NOT "${ActualMD5}" STREQUAL "${ORTHANC_FRAMEWORK_MD5}")
+    message(FATAL_ERROR "The MD5 hash of the Orthanc archive is invalid: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+  endif()
+
+  set(ORTHANC_ROOT "${CMAKE_BINARY_DIR}/Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
+
+  if (NOT IS_DIRECTORY "${ORTHANC_ROOT}")
+    if (NOT ORTHANC_FRAMEWORK_ARCHIVE MATCHES ".tar.gz$")
+      message(FATAL_ERROR "Archive should have the \".tar.gz\" extension: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+    endif()
+    
+    message("Uncompressing: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      execute_process(
+        COMMAND ${ORTHANC_FRAMEWORK_7ZIP} e -y ${ORTHANC_FRAMEWORK_ARCHIVE}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        OUTPUT_QUIET
+        )
+      
+      if (Failure)
+        message(FATAL_ERROR "Error while running the uncompression tool")
+      endif()
+
+      get_filename_component(TMP_FILENAME "${ORTHANC_FRAMEWORK_ARCHIVE}" NAME)
+      string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+
+      execute_process(
+        COMMAND ${ORTHANC_FRAMEWORK_7ZIP} x -y ${TMP_FILENAME2}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        OUTPUT_QUIET
+        )
+
+    else()
+      execute_process(
+        COMMAND sh -c "${ORTHANC_FRAMEWORK_TAR} xfz ${ORTHANC_FRAMEWORK_ARCHIVE}"
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        )
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${ORTHANC_ROOT}")
+      message(FATAL_ERROR "The Orthanc framework was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/LinuxStandardBaseToolchain.cmake	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,66 @@
+# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu
+
+INCLUDE(CMakeForceCompiler)
+
+SET(LSB_PATH $ENV{LSB_PATH})
+SET(LSB_CC $ENV{LSB_CC})
+SET(LSB_CXX $ENV{LSB_CXX})
+SET(LSB_TARGET_VERSION "4.0")
+
+IF ("${LSB_PATH}" STREQUAL "")
+  SET(LSB_PATH "/opt/lsb")
+ENDIF()
+
+IF (EXISTS ${LSB_PATH}/lib64)
+  SET(LSB_TARGET_PROCESSOR "x86_64")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib64-${LSB_TARGET_VERSION})
+ELSEIF (EXISTS ${LSB_PATH}/lib)
+  SET(LSB_TARGET_PROCESSOR "x86")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION})
+ELSE()
+  MESSAGE(FATAL_ERROR "Unable to detect the target processor architecture. Check the LSB_PATH environment variable.")
+ENDIF()
+
+SET(LSB_CPPPATH ${LSB_PATH}/include)
+SET(PKG_CONFIG_PATH ${LSB_LIBPATH}/pkgconfig/)
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_VERSION LinuxStandardBase)
+SET(CMAKE_SYSTEM_PROCESSOR ${LSB_TARGET_PROCESSOR})
+
+# which compilers to use for C and C++
+SET(CMAKE_C_COMPILER ${LSB_PATH}/bin/lsbcc)
+CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
+
+SET(CMAKE_CROSSCOMPILING OFF)
+
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include" CACHE INTERNAL "" FORCE)
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward" CACHE INTERNAL "" FORCE)
+SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+
+if (NOT "${LSB_CXX}" STREQUAL "")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+endif()
+
+if (NOT "${LSB_CC}" STREQUAL "")
+  SET(CMAKE_C_FLAGS "${CMAKE_CC_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/MinGW-W64-Toolchain32.cmake	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/MinGW-W64-Toolchain64.cmake	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/MinGWToolchain.cmake	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,20 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
+set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
+set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/README.txt	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,3 @@
+This folder contains an excerpt of the source code of Orthanc. It is
+automatically generated using the "../Resources/SyncOrthancFolder.py"
+script.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Sdk-1.5.4/orthanc/OrthancCPlugin.h	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,6802 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ *    - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2().
+ *    - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer().
+ *    - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup DicomCallbacks DicomCallbacks
+ * @brief Functions to register and manage DICOM callbacks (worklists, C-Find, C-MOVE).
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU 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 <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     5
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  4
+
+
+#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
+#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \
+  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||               \
+   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&             \
+    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||             \
+     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&           \
+      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
+#endif
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+/**
+ * For Microsoft Visual Studio, a compatibility "stdint.h" can be
+ * downloaded at the following URL:
+ * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h
+ **/
+#include <stdint.h>
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * The various error codes that can be returned by the Orthanc core.
+   **/
+  typedef enum
+  {
+    OrthancPluginErrorCode_InternalError = -1    /*!< Internal error */,
+    OrthancPluginErrorCode_Success = 0    /*!< Success */,
+    OrthancPluginErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    OrthancPluginErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    OrthancPluginErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    OrthancPluginErrorCode_NotEnoughMemory = 4    /*!< The server hosting Orthanc is running out of memory */,
+    OrthancPluginErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    OrthancPluginErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    OrthancPluginErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    OrthancPluginErrorCode_BadRequest = 8    /*!< Bad request */,
+    OrthancPluginErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    OrthancPluginErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    OrthancPluginErrorCode_Database = 11    /*!< Error with the database engine */,
+    OrthancPluginErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    OrthancPluginErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    OrthancPluginErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    OrthancPluginErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    OrthancPluginErrorCode_Timeout = 16    /*!< Timeout */,
+    OrthancPluginErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    OrthancPluginErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    OrthancPluginErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    OrthancPluginErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    OrthancPluginErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    OrthancPluginErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    OrthancPluginErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    OrthancPluginErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    OrthancPluginErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    OrthancPluginErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    OrthancPluginErrorCode_NullPointer = 35    /*!< Cannot handle a NULL pointer */,
+    OrthancPluginErrorCode_DatabaseUnavailable = 36    /*!< The database is currently not available (probably a transient situation) */,
+    OrthancPluginErrorCode_CanceledJob = 37    /*!< This job was canceled */,
+    OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    OrthancPluginErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    OrthancPluginErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    OrthancPluginErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    OrthancPluginErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    OrthancPluginErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    OrthancPluginErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    OrthancPluginErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    OrthancPluginErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    OrthancPluginErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    OrthancPluginErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    OrthancPluginErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    OrthancPluginErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+    OrthancPluginErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+    _OrthancPluginService_CallHttpClient2 = 27,
+    _OrthancPluginService_GenerateUuid = 28,
+    _OrthancPluginService_RegisterPrivateDictionaryTag = 29,
+    _OrthancPluginService_AutodetectMimeType = 30,
+    _OrthancPluginService_SetMetricsValue = 31,
+    _OrthancPluginService_EncodeDicomWebJson = 32,
+    _OrthancPluginService_EncodeDicomWebXml = 33,
+    
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+    _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007,
+    _OrthancPluginService_RegisterFindCallback = 1008,
+    _OrthancPluginService_RegisterMoveCallback = 1009,
+    _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010,
+    _OrthancPluginService_RegisterRefreshMetricsCallback = 1011,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _OrthancPluginService_SendMultipartItem2 = 2012,
+    _OrthancPluginService_SetHttpErrorDetails = 2013,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004,
+    _OrthancPluginService_LookupPatient = 3005,
+    _OrthancPluginService_LookupStudy = 3006,
+    _OrthancPluginService_LookupSeries = 3007,
+    _OrthancPluginService_LookupInstance = 3008,
+    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
+    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
+    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
+    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
+    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
+    _OrthancPluginService_RestApiGet2 = 3015,
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
+    _OrthancPluginService_HasInstanceMetadata = 4005,
+    _OrthancPluginService_GetInstanceMetadata = 4006,
+    _OrthancPluginService_GetInstanceOrigin = 4007,
+
+    /* Services for plugins implementing a database back-end */
+    _OrthancPluginService_RegisterDatabaseBackend = 5000,
+    _OrthancPluginService_DatabaseAnswer = 5001,
+    _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
+    _OrthancPluginService_StorageAreaCreate = 5003,
+    _OrthancPluginService_StorageAreaRead = 5004,
+    _OrthancPluginService_StorageAreaRemove = 5005,
+
+    /* Primitives for handling images */
+    _OrthancPluginService_GetImagePixelFormat = 6000,
+    _OrthancPluginService_GetImageWidth = 6001,
+    _OrthancPluginService_GetImageHeight = 6002,
+    _OrthancPluginService_GetImagePitch = 6003,
+    _OrthancPluginService_GetImageBuffer = 6004,
+    _OrthancPluginService_UncompressImage = 6005,
+    _OrthancPluginService_FreeImage = 6006,
+    _OrthancPluginService_CompressImage = 6007,
+    _OrthancPluginService_ConvertPixelFormat = 6008,
+    _OrthancPluginService_GetFontsCount = 6009,
+    _OrthancPluginService_GetFontInfo = 6010,
+    _OrthancPluginService_DrawText = 6011,
+    _OrthancPluginService_CreateImage = 6012,
+    _OrthancPluginService_CreateImageAccessor = 6013,
+    _OrthancPluginService_DecodeDicomImage = 6014,
+
+    /* Primitives for handling C-Find, C-Move and worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+    _OrthancPluginService_FindAddAnswer = 7004,
+    _OrthancPluginService_FindMarkIncomplete = 7005,
+    _OrthancPluginService_GetFindQuerySize = 7006,
+    _OrthancPluginService_GetFindQueryTag = 7007,
+    _OrthancPluginService_GetFindQueryTagName = 7008,
+    _OrthancPluginService_GetFindQueryValue = 7009,
+    _OrthancPluginService_CreateFindMatcher = 7010,
+    _OrthancPluginService_FreeFindMatcher = 7011,
+    _OrthancPluginService_FindMatcherIsMatch = 7012,
+
+    /* Primitives for accessing Orthanc Peers (new in 1.4.2) */
+    _OrthancPluginService_GetPeers = 8000,
+    _OrthancPluginService_FreePeers = 8001,
+    _OrthancPluginService_GetPeersCount = 8003,
+    _OrthancPluginService_GetPeerName = 8004,
+    _OrthancPluginService_GetPeerUrl = 8005,
+    _OrthancPluginService_CallPeerApi = 8006,
+    _OrthancPluginService_GetPeerUserProperty = 8007,
+
+    /* Primitives for handling jobs (new in 1.4.2) */
+    _OrthancPluginService_CreateJob = 9000,
+    _OrthancPluginService_FreeJob = 9001,
+    _OrthancPluginService_SubmitJob = 9002,
+    _OrthancPluginService_RegisterJobsUnserializer = 9003,
+    
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    /**
+     * @brief Color image in RGB48 format.
+     *
+     * This format describes a color image. The pixels are stored in 6
+     * consecutive bytes. The memory layout is RRGGBB.
+     **/
+    OrthancPluginPixelFormat_RGB48 = 7,
+
+    /**
+     * @brief Graylevel, unsigned 32bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * four bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale32 = 8,
+
+    /**
+     * @brief Graylevel, floating-point 32bpp image.
+     *
+     * The image is graylevel. Each pixel is floating-point and stored
+     * in four bytes.
+     **/
+    OrthancPluginPixelFormat_Float32 = 9,
+
+    /**
+     * @brief Color image in BGRA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is BGRA.
+     **/
+    OrthancPluginPixelFormat_BGRA32 = 10,
+
+    /**
+     * @brief Graylevel, unsigned 64bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * eight bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale64 = 11,
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+    OrthancPluginChangeType_UpdatedPeers = 14,      /*!< The list of Orthanc peers has changed */
+    OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_None                  = 0,
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_None                  = 0,
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   * @deprecated Plugins using OrthancPluginConstraintType will be faster
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The constraints on the tags (main DICOM tags and identifier tags)
+   * that must be supported by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginConstraintType_Equal = 1,           /*!< Equal */
+    OrthancPluginConstraintType_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginConstraintType_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginConstraintType_Wildcard = 4,        /*!< Wildcard matching */
+    OrthancPluginConstraintType_List = 5,            /*!< List of values */
+
+    _OrthancPluginConstraintType_INTERNAL = 0x7fffffff
+  } OrthancPluginConstraintType;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * The possible status for one single step of a job.
+   **/
+  typedef enum
+  {
+    OrthancPluginJobStepStatus_Success = 1,   /*!< The job has successfully executed all its steps */
+    OrthancPluginJobStepStatus_Failure = 2,   /*!< The job has failed while executing this step */
+    OrthancPluginJobStepStatus_Continue = 3   /*!< The job has still data to process after this step */
+  } OrthancPluginJobStepStatus;
+
+
+  /**
+   * Explains why the job should stop and release the resources it has
+   * allocated. This is especially important to disambiguate between
+   * the "paused" condition and the "final" conditions (success,
+   * failure, or canceled).
+   **/
+  typedef enum
+  {
+    OrthancPluginJobStopReason_Success = 1,  /*!< The job has succeeded */
+    OrthancPluginJobStopReason_Paused = 2,   /*!< The job was paused, and will be resumed later */
+    OrthancPluginJobStopReason_Failure = 3,  /*!< The job has failed, and might be resubmitted later */
+    OrthancPluginJobStopReason_Canceled = 4  /*!< The job was canceled, and might be resubmitted later */
+  } OrthancPluginJobStopReason;
+
+
+  /**
+   * The available types of metrics.
+   **/
+  typedef enum
+  {
+    OrthancPluginMetricsType_Default,   /*!< Default metrics */
+
+    /**
+     * This metrics represents a time duration. Orthanc will keep the
+     * maximum value of the metrics over a sliding window of ten
+     * seconds, which is useful if the metrics is sampled frequently.
+     **/
+    OrthancPluginMetricsType_Timer
+  } OrthancPluginMetricsType;
+  
+
+  /**
+   * The available modes to export a binary DICOM tag into a DICOMweb
+   * JSON or XML document.
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomWebBinaryMode_Ignore,        /*!< Don't include binary tags */
+    OrthancPluginDicomWebBinaryMode_InlineBinary,  /*!< Inline encoding using Base64 */
+    OrthancPluginDicomWebBinaryMode_BulkDataUri    /*!< Use a bulk data URI field */
+  } OrthancPluginDicomWebBinaryMode;
+
+  
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers;
+
+
+
+  /**
+   * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindMatcher;
+
+
+  
+  /**
+   * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginPeers_t OrthancPluginPeers;
+
+
+
+  /**
+   * @brief Opaque structure to a job to be executed by Orthanc.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginJob_t OrthancPluginJob;  
+
+
+
+  /**
+   * @brief Opaque structure that represents a node in a JSON or XML
+   * document used in DICOMweb.
+   * @ingroup Toolbox
+   **/
+  typedef struct _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode;
+
+  
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Signature of a function to set the content of a node
+   * encoding a binary DICOM tag, into a JSON or XML document
+   * generated for DICOMweb.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginDicomWebSetBinaryNode) (
+    OrthancPluginDicomWebNode*       node,
+    OrthancPluginDicomWebBinaryMode  mode,
+    const char*                      bulkDataUri);
+    
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests for worklists.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       issuerAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Callback to filter incoming HTTP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives an HTTP/REST request, and that answers whether
+   * this request should be allowed. If the callback returns "0"
+   * ("false"), the server answers with HTTP status code 403
+   * (Forbidden).
+   *
+   * @param method The HTTP method used by the request.
+   * @param uri The URI of interest.
+   * @param ip The IP address of the HTTP client.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys The keys of the HTTP headers (always converted to low-case).
+   * @param headersValues The values of the HTTP headers.
+   * @return 0 if forbidden access, 1 if allowed access, -1 if error.
+   * @ingroup Callback
+   * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2()
+   **/
+  typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) (
+    OrthancPluginHttpMethod  method,
+    const char*              uri,
+    const char*              ip,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues);
+
+
+
+  /**
+   * @brief Callback to filter incoming HTTP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives an HTTP/REST request, and that answers whether
+   * this request should be allowed. If the callback returns "0"
+   * ("false"), the server answers with HTTP status code 403
+   * (Forbidden).
+   *
+   * @param method The HTTP method used by the request.
+   * @param uri The URI of interest.
+   * @param ip The IP address of the HTTP client.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys The keys of the HTTP headers (always converted to low-case).
+   * @param headersValues The values of the HTTP headers.
+   * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method).
+   * @param getArgumentsKeys The keys of the GET arguments (only for the GET HTTP method).
+   * @param getArgumentsValues The values of the GET arguments (only for the GET HTTP method).
+   * @return 0 if forbidden access, 1 if allowed access, -1 if error.
+   * @ingroup Callback
+   **/
+  typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) (
+    OrthancPluginHttpMethod  method,
+    const char*              uri,
+    const char*              ip,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues,
+    uint32_t                 getArgumentsCount,
+    const char* const*       getArgumentsKeys,
+    const char* const*       getArgumentsValues);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Find SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Find SCP request not concerning modality
+   * worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
+    OrthancPluginFindAnswers*     answers,
+    const OrthancPluginFindQuery* query,
+    const char*                   issuerAet,
+    const char*                   calledAet);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Move SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Move SCP request. The callback receives the
+   * type of the resource of interest (study, series, instance...)
+   * together with the DICOM tags containing its identifiers. In turn,
+   * the plugin must create a driver object that will be responsible
+   * for driving the successive move suboperations.
+   *
+   * @param resourceType The type of the resource of interest. Note
+   * that this might be set to ResourceType_None if the
+   * QueryRetrieveLevel (0008,0052) tag was not provided by the
+   * issuer (i.e. the originator modality).
+   * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL.
+   * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL.
+   * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL.
+   * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL.
+   * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL.
+   * @param originatorAet The Application Entity Title (AET) of the
+   * modality from which the request originates.
+   * @param sourceAet The Application Entity Title (AET) of the
+   * modality that should send its DICOM files to another modality.
+   * @param targetAet The Application Entity Title (AET) of the
+   * modality that should receive the DICOM files.
+   * @param originatorId The Message ID issued by the originator modality,
+   * as found in tag (0000,0110) of the DICOM query emitted by the issuer.
+   *
+   * @return The NULL value if the plugin cannot deal with this query,
+   * or a pointer to the driver object that is responsible for
+   * handling the successive move suboperations.
+   * 
+   * @note If targetAet equals sourceAet, this is actually a query/retrieve operation.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void* (*OrthancPluginMoveCallback) (
+    OrthancPluginResourceType  resourceType,
+    const char*                patientId,
+    const char*                accessionNumber,
+    const char*                studyInstanceUid,
+    const char*                seriesInstanceUid,
+    const char*                sopInstanceUid,
+    const char*                originatorAet,
+    const char*                sourceAet,
+    const char*                targetAet,
+    uint16_t                   originatorId);
+    
+
+  /**
+   * @brief Callback to read the size of a C-Move driver.
+   * 
+   * Signature of a callback function that returns the number of
+   * C-Move suboperations that are to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return The number of suboperations. 
+   * @ingroup DicomCallbacks
+   **/
+  typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to apply one C-Move suboperation.
+   * 
+   * Signature of a callback function that applies the next C-Move
+   * suboperation that os to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to free one C-Move driver.
+   * 
+   * Signature of a callback function that releases the resources
+   * allocated by the given C-Move driver. This driver is the return
+   * value of a previous call to the OrthancPluginMoveCallback()
+   * callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void (*OrthancPluginFreeMove) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to finalize one custom job.
+   * 
+   * Signature of a callback function that releases all the resources
+   * allocated by the given job. This job is the argument provided to
+   * OrthancPluginCreateJob().
+   *
+   * @param job The job of interest.
+   * @ingroup Toolbox
+   **/  
+  typedef void (*OrthancPluginJobFinalize) (void* job);
+
+
+  /**
+   * @brief Callback to check the progress of one custom job.
+   * 
+   * Signature of a callback function that returns the progress of the
+   * job.
+   *
+   * @param job The job of interest.
+   * @return The progress, as a floating-point number ranging from 0 to 1.
+   * @ingroup Toolbox
+   **/  
+  typedef float (*OrthancPluginJobGetProgress) (void* job);
+
+  
+  /**
+   * @brief Callback to retrieve the content of one custom job.
+   * 
+   * Signature of a callback function that returns human-readable
+   * statistics about the job. This statistics must be formatted as a
+   * JSON object. This information is notably displayed in the "Jobs"
+   * tab of "Orthanc Explorer".
+   *
+   * @param job The job of interest.
+   * @return The statistics, as a JSON object encoded as a string.
+   * @ingroup Toolbox
+   **/  
+  typedef const char* (*OrthancPluginJobGetContent) (void* job);
+
+
+  /**
+   * @brief Callback to serialize one custom job.
+   * 
+   * Signature of a callback function that returns a serialized
+   * version of the job, formatted as a JSON object. This
+   * serialization is stored in the Orthanc database, and is used to
+   * reload the job on the restart of Orthanc. The "unserialization"
+   * callback (with OrthancPluginJobsUnserializer signature) will
+   * receive this serialized object.
+   *
+   * @param job The job of interest.
+   * @return The serialized job, as a JSON object encoded as a string.
+   * @see OrthancPluginRegisterJobsUnserializer()
+   * @ingroup Toolbox
+   **/  
+  typedef const char* (*OrthancPluginJobGetSerialized) (void* job);
+
+
+  /**
+   * @brief Callback to execute one step of a custom job.
+   * 
+   * Signature of a callback function that executes one step in the
+   * job. The jobs engine of Orthanc will make successive calls to
+   * this method, as long as it returns
+   * OrthancPluginJobStepStatus_Continue.
+   *
+   * @param job The job of interest.
+   * @return The status of execution.
+   * @ingroup Toolbox
+   **/  
+  typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job);
+
+
+  /**
+   * @brief Callback executed once one custom job leaves the "running" state.
+   * 
+   * Signature of a callback function that is invoked once a job
+   * leaves the "running" state. This can happen if the previous call
+   * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc
+   * server is being stopped, or if the user manually tags the job as
+   * paused/canceled. This callback allows the plugin to free
+   * resources allocated for running this custom job (e.g. to stop
+   * threads, or to remove temporary files).
+   * 
+   * Note that handling pauses might involves a specific treatment
+   * (such a stopping threads, but keeping temporary files on the
+   * disk). This "paused" situation can be checked by looking at the
+   * "reason" parameter.
+   *
+   * @param job The job of interest.
+   * @param reason The reason for leaving the "running" state. 
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/    
+  typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job, 
+                                                          OrthancPluginJobStopReason reason);
+
+
+  /**
+   * @brief Callback executed once one stopped custom job is started again.
+   * 
+   * Signature of a callback function that is invoked once a job
+   * leaves the "failure/canceled" state, to be started again. This
+   * function will typically reset the progress to zero. Note that
+   * before being actually executed, the job would first be tagged as
+   * "pending" in the Orthanc jobs engine.
+   *
+   * @param job The job of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/    
+  typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job);
+
+
+  /**
+   * @brief Callback executed to unserialize a custom job.
+   * 
+   * Signature of a callback function that unserializes a job that was
+   * saved in the Orthanc database.
+   *
+   * @param jobType The type of the job, as provided to OrthancPluginCreateJob().
+   * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized.
+   * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL
+   * if this unserializer cannot handle this job type.
+   * @see OrthancPluginRegisterJobsUnserializer()
+   * @ingroup Callbacks
+   **/    
+  typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType,
+                                                              const char* serialized);
+  
+
+
+  /**
+   * @brief Callback executed to update the metrics of the plugin.
+   * 
+   * Signature of a callback function that is called by Orthanc
+   * whenever a monitoring tool (such as Prometheus) asks the current
+   * values of the metrics. This callback gives the plugin a chance to
+   * update its metrics, by calling OrthancPluginSetMetricsValue().
+   * This is typically useful for metrics that are expensive to
+   * acquire.
+   * 
+   * @see OrthancPluginRegisterRefreshMetrics()
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginRefreshMetricsCallback) ();
+
+  
+
+  /**
+   * @brief Callback executed to encode a binary tag in DICOMweb.
+   * 
+   * Signature of a callback function that is called by Orthanc
+   * whenever a DICOM tag that contains a binary value must be written
+   * to a JSON or XML node, while a DICOMweb document is being
+   * generated. The value representation (VR) of the DICOM tag can be
+   * OB, OD, OF, OL, OW, or UN.
+   * 
+   * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml()
+   * @param node The node being generated, as provided by Orthanc.
+   * @param setter The setter to be used to encode the content of the node. If
+   * the setter is not called, the binary tag is not written to the output document.
+   * @param levelDepth The depth of the node in the DICOM hierarchy of sequences.
+   * This parameter gives the number of elements in the "levelTagGroup", 
+   * "levelTagElement", and "levelIndex" arrays.
+   * @param levelTagGroup The group of the parent DICOM tags in the hierarchy.
+   * @param levelTagElement The element of the parent DICOM tags in the hierarchy.
+   * @param levelIndex The index of the node in the parent sequences of the hiearchy.
+   * @param tagGroup The group of the DICOM tag of interest.
+   * @param tagElement The element of the DICOM tag of interest.
+   * @param vr The value representation of the binary DICOM node.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginDicomWebBinaryCallback) (
+    OrthancPluginDicomWebNode*          node,
+    OrthancPluginDicomWebSetBinaryNode  setter,
+    uint32_t                            levelDepth,
+    const uint16_t*                     levelTagGroup,
+    const uint16_t*                     levelTagElement,
+    const uint32_t*                     levelIndex,
+    uint16_t                            tagGroup,
+    uint16_t                            tagElement,
+    OrthancPluginValueRepresentation    vr);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check that the version of the hosting Orthanc is above a given version.
+   * 
+   * This function checks whether the version of the Orthanc server
+   * running this plugin, is above the given version. Contrarily to
+   * OrthancPluginCheckVersion(), it is up to the developer of the
+   * plugin to make sure that all the Orthanc SDK services called by
+   * the plugin are actually implemented in the given version of
+   * Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param expectedMajor Expected major version.
+   * @param expectedMinor Expected minor version.
+   * @param expectedRevision Expected revision.
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @see OrthancPluginCheckVersion
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersionAdvanced(
+    OrthancPluginContext* context,
+    int expectedMajor,
+    int expectedMinor,
+    int expectedRevision)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
+        sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
+        sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > expectedMajor)
+    {
+      return 1;
+    }
+
+    if (major < expectedMajor)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > expectedMinor)
+    {
+      return 1;
+    }
+
+    if (minor < expectedMinor)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= expectedRevision)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of the Orthanc server
+   * running this plugin, is above the version of the current Orthanc
+   * SDK header. This guarantees that the plugin is compatible with
+   * the hosting Orthanc (i.e. it will not call unavailable services).
+   * The result of this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @see OrthancPluginCheckVersionAdvanced
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    return OrthancPluginCheckVersionAdvanced(
+      context,
+      ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+      ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups. 
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   *
+   * @note
+   * The regular expression is case sensitive and must follow the
+   * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
+   *
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, if using this function,
+   * it is up to the plugin to implement the required locking
+   * mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   *
+   * @note
+   * The regular expression is case sensitive and must follow the
+   * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
+   *
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * scripts.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              subType,
+    const char*              contentType)
+  {
+    _OrthancPluginStartMultipartAnswer params;
+    params.output = output;
+    params.subType = subType;
+    params.contentType = contentType;
+    return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
+  }
+
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = NULL;
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*    target;
+    const void*                   source;
+    uint32_t                      size;
+    OrthancPluginCompressionType  compression;
+    uint8_t                       uncompress;
+  } _OrthancPluginBufferCompression;
+
+
+  /**
+   * @brief Compress or decompress a buffer.
+   *
+   * This function compresses or decompresses a buffer, using the
+   * version of the zlib library that is used by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param source The source buffer.
+   * @param size The size in bytes of the source buffer.
+   * @param compression The compression algorithm.
+   * @param uncompress If set to "0", the buffer must be compressed. 
+   * If set to "1", the buffer must be uncompressed.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    const void*                   source,
+    uint32_t                      size,
+    OrthancPluginCompressionType  compression,
+    uint8_t                       uncompress)
+  {
+    _OrthancPluginBufferCompression params;
+    params.target = target;
+    params.source = source;
+    params.size = size;
+    params.compression = compression;
+    params.uncompress = uncompress;
+
+    return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 path;
+  } _OrthancPluginReadFile;
+
+  /**
+   * @brief Read a file.
+   * 
+   * Read the content of a file on the filesystem, and returns it into
+   * a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param path The path of the file to be read.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReadFile(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 path)
+  {
+    _OrthancPluginReadFile params;
+    params.target = target;
+    params.path = path;
+    return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char*  path;
+    const void*  data;
+    uint32_t     size;
+  } _OrthancPluginWriteFile;
+
+  /**
+   * @brief Write a file.
+   * 
+   * Write the content of a memory buffer to the filesystem.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path The path of the file to be written.
+   * @param data The content of the memory buffer.
+   * @param size The size of the memory buffer.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWriteFile(
+    OrthancPluginContext*  context,
+    const char*            path,
+    const void*            data,
+    uint32_t               size)
+  {
+    _OrthancPluginWriteFile params;
+    params.path = path;
+    params.data = data;
+    params.size = size;
+    return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char**            target;
+    OrthancPluginErrorCode  error;
+  } _OrthancPluginGetErrorDescription;
+
+  /**
+   * @brief Get the description of a given error code.
+   *
+   * This function returns the description of a given error code.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param error The error code of interest.
+   * @return The error description. This is a statically-allocated
+   * string, do not free it.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
+    OrthancPluginContext*    context,
+    OrthancPluginErrorCode   error)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetErrorDescription params;
+    params.target = &result;
+    params.error = error;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
+        result == NULL)
+    {
+      return "Unknown error code";
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+    const char*              body;
+    uint32_t                 bodySize;
+  } _OrthancPluginSendHttpStatus;
+
+  /**
+   * @brief Send a HTTP status, with a custom body.
+   *
+   * This function answers to a HTTP request by sending a HTTP status
+   * code (such as "400 - Bad Request"), together with a body
+   * describing the error. The body will only be returned if the
+   * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
+   * 
+   * Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @param body The body of the answer.
+   * @param bodySize The size of the body.
+   * @see OrthancPluginSendHttpStatusCode()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status,
+    const char*              body,
+    uint32_t                 bodySize)
+  {
+    _OrthancPluginSendHttpStatus params;
+    params.output = output;
+    params.status = status;
+    params.body = body;
+    params.bodySize = bodySize;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const OrthancPluginImage*  image;
+    uint32_t*                  resultUint32;
+    OrthancPluginPixelFormat*  resultPixelFormat;
+    void**                     resultBuffer;
+  } _OrthancPluginGetImageInfo;
+
+
+  /**
+   * @brief Return the pixel format of an image.
+   *
+   * This function returns the type of memory layout for the pixels of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pixel format.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat  OrthancPluginGetImagePixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    OrthancPluginPixelFormat target;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultPixelFormat = &target;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginPixelFormat_Unknown;
+    }
+    else
+    {
+      return (OrthancPluginPixelFormat) target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the width of an image.
+   *
+   * This function returns the width of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The width.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageWidth(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t width;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &width;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return width;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the height of an image.
+   *
+   * This function returns the height of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The height.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageHeight(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t height;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &height;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return height;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the pitch of an image.
+   *
+   * This function returns the pitch of the given image. The pitch is
+   * defined as the number of bytes between 2 successive lines of the
+   * image in the memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pitch.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImagePitch(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t pitch;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &pitch;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return pitch;
+    }
+  }
+
+
+
+  /**
+   * @brief Return a pointer to the content of an image.
+   *
+   * This function returns a pointer to the memory buffer that
+   * contains the pixels of the image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pointer.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    void* target = NULL;
+
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.resultBuffer = &target;
+    params.image = image;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const void*                data;
+    uint32_t                   size;
+    OrthancPluginImageFormat   format;
+  } _OrthancPluginUncompressImage;
+
+
+  /**
+   * @brief Decode a compressed image.
+   *
+   * This function decodes a compressed image from a memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param data Pointer to a memory buffer containing the compressed image.
+   * @param size Size of the memory buffer containing the compressed image.
+   * @param format The file format of the compressed image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
+    OrthancPluginContext*      context,
+    const void*                data,
+    uint32_t                   size,
+    OrthancPluginImageFormat   format)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginUncompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.data = data;
+    params.size = size;
+    params.format = format;
+
+    if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+  } _OrthancPluginFreeImage;
+
+  /**
+   * @brief Free an image.
+   *
+   * This function frees an image that was decoded with OrthancPluginUncompressImage().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeImage(
+    OrthancPluginContext* context, 
+    OrthancPluginImage*   image)
+  {
+    _OrthancPluginFreeImage params;
+    params.image = image;
+
+    context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer* target;
+    OrthancPluginImageFormat   imageFormat;
+    OrthancPluginPixelFormat   pixelFormat;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    const void*                buffer;
+    uint8_t                    quality;
+  } _OrthancPluginCompressImage;
+
+
+  /**
+   * @brief Encode a PNG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the PNG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCompressAndAnswerPngImage()
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* Unused for PNG */
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+  /**
+   * @brief Encode a JPEG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the JPEG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer,
+    uint8_t                       quality)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+
+  /**
+   * @brief Answer to a REST request with a JPEG image.
+   *
+   * This function answers to a REST request with a JPEG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a JPEG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer,
+    uint8_t                   quality)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    const char*                 username;
+    const char*                 password;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginCallHttpClient;
+
+
+  /**
+   * @brief Issue a HTTP GET call.
+   * 
+   * Make a HTTP GET call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiGet() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Get;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP POST call.
+   * 
+   * Make a HTTP POST call to the given URL. The result to the query
+   * is stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPost() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Post;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP PUT call.
+   * 
+   * Make a HTTP PUT call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPut() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Put;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP DELETE call.
+   * 
+   * Make a HTTP DELETE call to the given URL. Favor
+   * OrthancPluginRestApiDelete() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpDelete(
+    OrthancPluginContext*       context,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.method = OrthancPluginHttpMethod_Delete;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const OrthancPluginImage*  source;
+    OrthancPluginPixelFormat   targetFormat;
+  } _OrthancPluginConvertPixelFormat;
+
+
+  /**
+   * @brief Change the pixel format of an image.
+   *
+   * This function creates a new image, changing the memory layout of the pixels.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param source The source image.
+   * @param targetFormat The target pixel format.
+   * @return The resulting image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  source,
+    OrthancPluginPixelFormat   targetFormat)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginConvertPixelFormat params;
+    params.target = &target;
+    params.source = source;
+    params.targetFormat = targetFormat;
+
+    if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the number of available fonts.
+   *
+   * This function returns the number of fonts that are built in the
+   * Orthanc core. These fonts can be used to draw texts on images
+   * through OrthancPluginDrawText().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of fonts.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    uint32_t      fontIndex; /* in */
+    const char**  name; /* out */
+    uint32_t*     size; /* out */
+  } _OrthancPluginGetFontInfo;
+
+  /**
+   * @brief Return the name of a font.
+   *
+   * This function returns the name of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font name. This is a statically-allocated string, do not free it.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.name = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the size of a font.
+   *
+   * This function returns the size of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font size.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    uint32_t result;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.size = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+    uint32_t              fontIndex;
+    const char*           utf8Text;
+    int32_t               x;
+    int32_t               y;
+    uint8_t               r;
+    uint8_t               g;
+    uint8_t               b;
+  } _OrthancPluginDrawText;
+
+
+  /**
+   * @brief Draw text on an image.
+   *
+   * This function draws some text on some image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image upon which to draw the text.
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
+   * @param x The X position of the text over the image.
+   * @param y The Y position of the text over the image.
+   * @param r The value of the red color channel of the text.
+   * @param g The value of the green color channel of the text.
+   * @param b The value of the blue color channel of the text.
+   * @return 0 if success, other value if error.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginDrawText(
+    OrthancPluginContext*  context,
+    OrthancPluginImage*    image,
+    uint32_t               fontIndex,
+    const char*            utf8Text,
+    int32_t                x,
+    int32_t                y,
+    uint8_t                r,
+    uint8_t                g,
+    uint8_t                b)
+  {
+    _OrthancPluginDrawText params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.fontIndex = fontIndex;
+    params.utf8Text = utf8Text;
+    params.x = x;
+    params.y = y;
+    params.r = r;
+    params.g = g;
+    params.b = b;
+
+    return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    const void*                 content;
+    uint64_t                    size;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaCreate;
+
+
+  /**
+   * @brief Create a file inside the storage area.
+   *
+   * This function creates a new file inside the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be created.
+   * @param content The content to store in the newly created file.
+   * @param size The size of the content.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    const void*                 content,
+    uint64_t                    size,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaCreate params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.content = content;
+    params.size = size;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRead;
+
+
+  /**
+   * @brief Read a file from the storage area.
+   *
+   * This function reads the content of a given file from the storage
+   * area that is currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be read.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRead params;
+    params.target = target;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRemove;
+
+  /**
+   * @brief Remove a file from the storage area.
+   *
+   * This function removes a given file from the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be removed.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRemove params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginErrorCode*  target;
+    int32_t                  code;
+    uint16_t                 httpStatus;
+    const char*              message;
+  } _OrthancPluginRegisterErrorCode;
+  
+  /**
+   * @brief Declare a custom error code for this plugin.
+   *
+   * This function declares a custom error code that can be generated
+   * by this plugin. This declaration is used to enrich the body of
+   * the HTTP answer in the case of an error, and to set the proper
+   * HTTP status code.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param code The error code that is internal to this plugin.
+   * @param httpStatus The HTTP status corresponding to this error.
+   * @param message The description of the error.
+   * @return The error code that has been assigned inside the Orthanc core.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
+    OrthancPluginContext*    context,
+    int32_t                  code,
+    uint16_t                 httpStatus,
+    const char*              message)
+  {
+    OrthancPluginErrorCode target;
+
+    _OrthancPluginRegisterErrorCode params;
+    params.target = &target;
+    params.code = code;
+    params.httpStatus = httpStatus;
+    params.message = message;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
+    {
+      return target;
+    }
+    else
+    {
+      /* There was an error while assigned the error. Use a generic code. */
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+  } _OrthancPluginRegisterDictionaryTag;
+  
+  /**
+   * @brief Register a new tag into the DICOM dictionary.
+   *
+   * This function declares a new public tag in the dictionary of
+   * DICOM tags that are known to Orthanc. This function should be
+   * used in the OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @return 0 if success, other value if error.
+   * @see OrthancPluginRegisterPrivateDictionaryTag()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity)
+  {
+    _OrthancPluginRegisterDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+    const char*                       privateCreator;
+  } _OrthancPluginRegisterPrivateDictionaryTag;
+  
+  /**
+   * @brief Register a new private tag into the DICOM dictionary.
+   *
+   * This function declares a new private tag in the dictionary of
+   * DICOM tags that are known to Orthanc. This function should be
+   * used in the OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @param privateCreator The private creator of this private tag.
+   * @return 0 if success, other value if error.
+   * @see OrthancPluginRegisterDictionaryTag()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterPrivateDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity,
+    const char*                       privateCreator)
+  {
+    _OrthancPluginRegisterPrivateDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+    params.privateCreator = privateCreator;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*  storageArea;
+    OrthancPluginResourceType  level;
+  } _OrthancPluginReconstructMainDicomTags;
+
+  /**
+   * @brief Reconstruct the main DICOM tags.
+   *
+   * This function requests the Orthanc core to reconstruct the main
+   * DICOM tags of all the resources of the given type. This function
+   * can only be used as a part of the upgrade of a custom database
+   * back-end. A database transaction will be automatically setup.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param level The type of the resources of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
+    OrthancPluginContext*      context,
+    OrthancPluginStorageArea*  storageArea,
+    OrthancPluginResourceType  level)
+  {
+    _OrthancPluginReconstructMainDicomTags params;
+    params.level = level;
+    params.storageArea = storageArea;
+
+    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                          result;
+    const char*                     instanceId;
+    const void*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const void*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    uint32_t                 headersCount;
+    const char* const*       headersKeys;
+    const char* const*       headersValues;
+  } _OrthancPluginSendMultipartItem2;
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer, with custom headers.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to
+   * OrthancPluginSendMultipartItem(), this function will set HTTP header associated
+   * with the item.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues)
+  {
+    _OrthancPluginSendMultipartItem2 params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;    
+
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingHttpRequestFilter callback;
+  } _OrthancPluginIncomingHttpRequestFilter;
+
+  /**
+   * @brief Register a callback to filter incoming HTTP requests.
+   *
+   * This function registers a custom callback to filter incoming HTTP/REST
+   * requests received by the HTTP server of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter(
+    OrthancPluginContext*                   context,
+    OrthancPluginIncomingHttpRequestFilter  callback)
+  {
+    _OrthancPluginIncomingHttpRequestFilter params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    const char*                 body;
+    uint32_t                    bodySize;
+    const char*                 username;
+    const char*                 password;
+    uint32_t                    timeout;
+    const char*                 certificateFile;
+    const char*                 certificateKeyFile;
+    const char*                 certificateKeyPassword;
+    uint8_t                     pkcs11;
+  } _OrthancPluginCallHttpClient2;
+
+
+
+  /**
+   * @brief Issue a HTTP call with full flexibility.
+   * 
+   * Make a HTTP call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. The HTTP request
+   * will be done accordingly to the global configuration of Orthanc
+   * (in particular, the options "HttpProxy", "HttpTimeout",
+   * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be
+   * taken into account).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param url The URL of interest.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @param body The HTTP body for a POST or PUT request.
+   * @param bodySize The size of the body.
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @param certificateFile Path to the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyPassword Password to unlock the key of the client certificate 
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCallPeerApi()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpClient(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    OrthancPluginHttpMethod     method,
+    const char*                 url,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password,
+    uint32_t                    timeout,
+    const char*                 certificateFile,
+    const char*                 certificateKeyFile,
+    const char*                 certificateKeyPassword,
+    uint8_t                     pkcs11)
+  {
+    _OrthancPluginCallHttpClient2 params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+    params.certificateFile = certificateFile;
+    params.certificateKeyFile = certificateKeyFile;
+    params.certificateKeyPassword = certificateKeyPassword;
+    params.pkcs11 = pkcs11;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, &params);
+  }
+
+
+  /**
+   * @brief Generate an UUID.
+   *
+   * Generate a random GUID/UUID (globally unique identifier).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the UUID. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid(
+    OrthancPluginContext*  context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginFindCallback callback;
+  } _OrthancPluginFindCallback;
+
+  /**
+   * @brief Register a callback to handle C-Find requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * that are not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback(
+    OrthancPluginContext*      context,
+    OrthancPluginFindCallback  callback)
+  {
+    _OrthancPluginFindCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginFindAnswers      *answers;
+    const OrthancPluginFindQuery  *query;
+    const void                    *dicom;
+    uint32_t                       size;
+    uint32_t                       index;
+    uint32_t                      *resultUint32;
+    uint16_t                      *resultGroup;
+    uint16_t                      *resultElement;
+    char                         **resultString;
+  } _OrthancPluginFindOperation;
+
+  /**
+   * @brief Add one answer to some C-Find request.
+   *
+   * This function adds one answer (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request that is
+   * not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param dicom The answer to be added, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindAddAnswer(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers,
+    const void*                dicom,
+    uint32_t                   size)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of C-Find answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request that is not related to
+   * modality worklists. This must be used if canceling the handling
+   * of a request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindMarkIncomplete(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+
+    return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, &params);
+  }
+
+
+
+  /**
+   * @brief Get the number of tags in a C-Find query.
+   *
+   * This function returns the number of tags that are contained in
+   * the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @return The number of tags.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetFindQuerySize(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  /**
+   * @brief Get one tag in a C-Find query.
+   *
+   * This function returns the group and the element of one DICOM tag
+   * in the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag (output).
+   * @param element The element of the tag (output).
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetFindQueryTag(
+    OrthancPluginContext*          context,
+    uint16_t*                      group,
+    uint16_t*                      element,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultGroup = group;
+    params.resultElement = element;
+
+    return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, &params);
+  }
+
+
+  /**
+   * @brief Get the symbolic name of one tag in a C-Find query.
+   *
+   * This function returns the symbolic name of one DICOM tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the name of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryTagName(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the value associated with one tag in a C-Find query.
+   *
+   * This function returns the value associated with one tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the value of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryValue(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+ 
+
+
+
+  typedef struct
+  {
+    OrthancPluginMoveCallback   callback;
+    OrthancPluginGetMoveSize    getMoveSize;
+    OrthancPluginApplyMove      applyMove;
+    OrthancPluginFreeMove       freeMove;
+  } _OrthancPluginMoveCallback;
+
+  /**
+   * @brief Register a callback to handle C-Move requests.
+   *
+   * This function registers a callback to handle C-Move SCP requests.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The main callback.
+   * @param getMoveSize Callback to read the number of C-Move suboperations.
+   * @param applyMove Callback to apply one C-Move suboperations.
+   * @param freeMove Callback to free the C-Move driver.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback(
+    OrthancPluginContext*       context,
+    OrthancPluginMoveCallback   callback,
+    OrthancPluginGetMoveSize    getMoveSize,
+    OrthancPluginApplyMove      applyMove,
+    OrthancPluginFreeMove       freeMove)
+  {
+    _OrthancPluginMoveCallback params;
+    params.callback = callback;
+    params.getMoveSize = getMoveSize;
+    params.applyMove = applyMove;
+    params.freeMove = freeMove;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginFindMatcher** target;
+    const void*                query;
+    uint32_t                   size;
+  } _OrthancPluginCreateFindMatcher;
+
+
+  /**
+   * @brief Create a C-Find matcher.
+   *
+   * This function creates a "matcher" object that can be used to
+   * check whether a DICOM instance matches a C-Find query. The C-Find
+   * query must be expressed as a DICOM buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find DICOM query.
+   * @param size The size of the DICOM query.
+   * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher(
+    OrthancPluginContext*  context,
+    const void*            query,
+    uint32_t               size)
+  {
+    OrthancPluginFindMatcher* target = NULL;
+
+    _OrthancPluginCreateFindMatcher params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.query = query;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginFindMatcher*   matcher;
+  } _OrthancPluginFreeFindMatcher;
+
+  /**
+   * @brief Free a C-Find matcher.
+   *
+   * This function frees a matcher that was created using OrthancPluginCreateFindMatcher().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param matcher The matcher of interest.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeFindMatcher(
+    OrthancPluginContext*     context, 
+    OrthancPluginFindMatcher* matcher)
+  {
+    _OrthancPluginFreeFindMatcher params;
+    params.matcher = matcher;
+
+    context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginFindMatcher*  matcher;
+    const void*                      dicom;
+    uint32_t                         size;
+    int32_t*                         isMatch;
+  } _OrthancPluginFindMatcherIsMatch;
+
+  /**
+   * @brief Test whether a DICOM instance matches a C-Find query.
+   *
+   * This function checks whether one DICOM instance matches C-Find
+   * matcher that was previously allocated using
+   * OrthancPluginCreateFindMatcher().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param matcher The matcher of interest.
+   * @param dicom The DICOM instance to be matched.
+   * @param size The size of the DICOM instance.
+   * @return 1 if the DICOM instance matches the query, 0 otherwise.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginFindMatcherIsMatch(
+    OrthancPluginContext*            context,
+    const OrthancPluginFindMatcher*  matcher,
+    const void*                      dicom,
+    uint32_t                         size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginFindMatcherIsMatch params;
+    params.matcher = matcher;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+
+    if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingHttpRequestFilter2 callback;
+  } _OrthancPluginIncomingHttpRequestFilter2;
+
+  /**
+   * @brief Register a callback to filter incoming HTTP requests.
+   *
+   * This function registers a custom callback to filter incoming HTTP/REST
+   * requests received by the HTTP server of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2(
+    OrthancPluginContext*                   context,
+    OrthancPluginIncomingHttpRequestFilter2 callback)
+  {
+    _OrthancPluginIncomingHttpRequestFilter2 params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginPeers**  peers;
+  } _OrthancPluginGetPeers;
+
+  /**
+   * @brief Return the list of available Orthanc peers.
+   *
+   * This function returns the parameters of the Orthanc peers that are known to
+   * the Orthanc server hosting the plugin.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL if error, or a newly allocated opaque data structure containing the peers.
+   * This structure must be freed with OrthancPluginFreePeers().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers(
+    OrthancPluginContext*  context)
+  {
+    OrthancPluginPeers* peers = NULL;
+
+    _OrthancPluginGetPeers params;
+    memset(&params, 0, sizeof(params));
+    params.peers = &peers;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeers, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return peers;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginPeers*   peers;
+  } _OrthancPluginFreePeers;
+
+  /**
+   * @brief Free the list of available Orthanc peers.
+   *
+   * This function frees the data structure returned by OrthancPluginGetPeers().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreePeers(
+    OrthancPluginContext*     context, 
+    OrthancPluginPeers* peers)
+  {
+    _OrthancPluginFreePeers params;
+    params.peers = peers;
+
+    context->InvokeService(context, _OrthancPluginService_FreePeers, &params);
+  }
+
+
+  typedef struct
+  {
+    uint32_t*                  target;
+    const OrthancPluginPeers*  peers;
+  } _OrthancPluginGetPeersCount;
+
+  /**
+   * @brief Get the number of Orthanc peers.
+   *
+   * This function returns the number of Orthanc peers.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @result The number of peers. 
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers)
+  {
+    uint32_t target = 0;
+
+    _OrthancPluginGetPeersCount params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    const char**               target;
+    const OrthancPluginPeers*  peers;
+    uint32_t                   peerIndex;
+    const char*                userProperty;
+  } _OrthancPluginGetPeerProperty;
+
+  /**
+   * @brief Get the symbolic name of an Orthanc peer.
+   *
+   * This function returns the symbolic name of the Orthanc peer,
+   * which corresponds to the key of the "OrthancPeers" configuration
+   * option of Orthanc.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @result The symbolic name, or NULL in the case of an error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers,
+    uint32_t                   peerIndex)
+  {
+    const char* target = NULL;
+
+    _OrthancPluginGetPeerProperty params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.userProperty = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeerName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Get the base URL of an Orthanc peer.
+   *
+   * This function returns the base URL to the REST API of some Orthanc peer.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @result The URL, or NULL in the case of an error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers,
+    uint32_t                   peerIndex)
+  {
+    const char* target = NULL;
+
+    _OrthancPluginGetPeerProperty params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.userProperty = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Get some user-defined property of an Orthanc peer.
+   *
+   * This function returns some user-defined property of some Orthanc
+   * peer. An user-defined property is a property that is associated
+   * with the peer in the Orthanc configuration file, but that is not
+   * recognized by the Orthanc core.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @param userProperty The user property of interest.
+   * @result The value of the user property, or NULL if it is not defined.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty(
+    OrthancPluginContext*      context,
+    const OrthancPluginPeers*  peers,
+    uint32_t                   peerIndex,
+    const char*                userProperty)
+  {
+    const char* target = NULL;
+
+    _OrthancPluginGetPeerProperty params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.userProperty = userProperty;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* No such user property */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    const OrthancPluginPeers*   peers;
+    uint32_t                    peerIndex;
+    OrthancPluginHttpMethod     method;
+    const char*                 uri;
+    uint32_t                    additionalHeadersCount;
+    const char* const*          additionalHeadersKeys;
+    const char* const*          additionalHeadersValues;
+    const char*                 body;
+    uint32_t                    bodySize;
+    uint32_t                    timeout;
+  } _OrthancPluginCallPeerApi;
+
+  /**
+   * @brief Call the REST API of an Orthanc peer.
+   * 
+   * Make a REST call to the given URI in the REST API of a remote
+   * Orthanc peer. The result to the query is stored into a newly
+   * allocated memory buffer. The HTTP request will be done according
+   * to the "OrthancPeers" configuration option of Orthanc.
+   *
+   * This function is thread-safe: Several threads sharing the same
+   * OrthancPluginPeers object can simultaneously call this function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param peers The data structure describing the Orthanc peers.
+   * @param peerIndex The index of the peer of interest.
+   * This value must be lower than OrthancPluginGetPeersCount().
+   * @param method HTTP method to be used.
+   * @param uri The URI of interest in the REST API.
+   * @param additionalHeadersCount The number of HTTP headers to be added to the
+   * HTTP headers provided in the global configuration of Orthanc.
+   * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param additionalHeadersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param body The HTTP body for a POST or PUT request.
+   * @param bodySize The size of the body.
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginHttpClient()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginCallPeerApi(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    const OrthancPluginPeers*   peers,
+    uint32_t                    peerIndex,
+    OrthancPluginHttpMethod     method,
+    const char*                 uri,
+    uint32_t                    additionalHeadersCount,
+    const char* const*          additionalHeadersKeys,
+    const char* const*          additionalHeadersValues,
+    const char*                 body,
+    uint32_t                    bodySize,
+    uint32_t                    timeout)
+  {
+    _OrthancPluginCallPeerApi params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.peers = peers;
+    params.peerIndex = peerIndex;
+    params.method = method;
+    params.uri = uri;
+    params.additionalHeadersCount = additionalHeadersCount;
+    params.additionalHeadersKeys = additionalHeadersKeys;
+    params.additionalHeadersValues = additionalHeadersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.timeout = timeout;
+
+    return context->InvokeService(context, _OrthancPluginService_CallPeerApi, &params);
+  }
+
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginJob**              target;
+    void                           *job;
+    OrthancPluginJobFinalize        finalize;
+    const char                     *type;
+    OrthancPluginJobGetProgress     getProgress;
+    OrthancPluginJobGetContent      getContent;
+    OrthancPluginJobGetSerialized   getSerialized;
+    OrthancPluginJobStep            step;
+    OrthancPluginJobStop            stop;
+    OrthancPluginJobReset           reset;
+  } _OrthancPluginCreateJob;
+
+  /**
+   * @brief Create a custom job.
+   *
+   * This function creates a custom job to be run by the jobs engine
+   * of Orthanc.
+   * 
+   * Orthanc starts one dedicated thread per custom job that is
+   * running. It is guaranteed that all the callbacks will only be
+   * called from this single dedicated thread, in mutual exclusion: As
+   * a consequence, it is *not* mandatory to protect the various
+   * callbacks by mutexes.
+   * 
+   * The custom job can nonetheless launch its own processing threads
+   * on the first call to the "step()" callback, and stop them once
+   * the "stop()" callback is called.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job to be executed.
+   * @param finalize The finalization callback.
+   * @param type The type of the job, provided to the job unserializer. 
+   * See OrthancPluginRegisterJobsUnserializer().
+   * @param getProgress The progress callback.
+   * @param getContent The content callback.
+   * @param getSerialized The serialization callback.
+   * @param step The callback to execute the individual steps of the job.
+   * @param stop The callback that is invoked once the job leaves the "running" state.
+   * @param reset The callback that is invoked if a stopped job is started again.
+   * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(),
+   * as long as it is not submitted with OrthancPluginSubmitJob().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob(
+    OrthancPluginContext           *context,
+    void                           *job,
+    OrthancPluginJobFinalize        finalize,
+    const char                     *type,
+    OrthancPluginJobGetProgress     getProgress,
+    OrthancPluginJobGetContent      getContent,
+    OrthancPluginJobGetSerialized   getSerialized,
+    OrthancPluginJobStep            step,
+    OrthancPluginJobStop            stop,
+    OrthancPluginJobReset           reset)
+  {
+    OrthancPluginJob* target = NULL;
+
+    _OrthancPluginCreateJob params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = &target;
+    params.job = job;
+    params.finalize = finalize;
+    params.type = type;
+    params.getProgress = getProgress;
+    params.getContent = getContent;
+    params.getSerialized = getSerialized;
+    params.step = step;
+    params.stop = stop;
+    params.reset = reset;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateJob, &params) != OrthancPluginErrorCode_Success ||
+        target == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginJob*   job;
+  } _OrthancPluginFreeJob;
+
+  /**
+   * @brief Free a custom job.
+   *
+   * This function frees an image that was created with OrthancPluginCreateJob().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeJob(
+    OrthancPluginContext* context, 
+    OrthancPluginJob*     job)
+  {
+    _OrthancPluginFreeJob params;
+    params.job = job;
+
+    context->InvokeService(context, _OrthancPluginService_FreeJob, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    char**             resultId;
+    OrthancPluginJob  *job;
+    int                priority;
+  } _OrthancPluginSubmitJob;
+
+  /**
+   * @brief Submit a new job to the jobs engine of Orthanc.
+   *
+   * This function adds the given job to the pending jobs of
+   * Orthanc. Orthanc will take take of freeing it by invoking the
+   * finalization callback provided to OrthancPluginCreateJob().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job, as received by OrthancPluginCreateJob().
+   * @param priority The priority of the job.
+   * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob(
+    OrthancPluginContext   *context,
+    OrthancPluginJob       *job,
+    int                     priority)
+  {
+    char* resultId = NULL;
+
+    _OrthancPluginSubmitJob params;
+    memset(&params, 0, sizeof(params));
+
+    params.resultId = &resultId;
+    params.job = job;
+    params.priority = priority;
+
+    if (context->InvokeService(context, _OrthancPluginService_SubmitJob, &params) != OrthancPluginErrorCode_Success ||
+        resultId == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return resultId;
+    }
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginJobsUnserializer unserializer;
+  } _OrthancPluginJobsUnserializer;
+
+  /**
+   * @brief Register an unserializer for custom jobs.
+   *
+   * This function registers an unserializer that decodes custom jobs
+   * from a JSON string. This callback is invoked when the jobs engine
+   * of Orthanc is started (on Orthanc initialization), for each job
+   * that is stored in the Orthanc database.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param unserializer The job unserializer.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer(
+    OrthancPluginContext*          context,
+    OrthancPluginJobsUnserializer  unserializer)
+  {
+    _OrthancPluginJobsUnserializer params;
+    params.unserializer = unserializer;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              details;
+    uint8_t                  log;
+  } _OrthancPluginSetHttpErrorDetails;
+
+  /**
+   * @brief Provide a detailed description for an HTTP error.
+   *
+   * This function sets the detailed description associated with an
+   * HTTP error. This description will be displayed in the "Details"
+   * field of the JSON body of the HTTP answer. It is only taken into
+   * consideration if the REST callback returns an error code that is
+   * different from "OrthancPluginErrorCode_Success", and if the
+   * "HttpDescribeErrors" configuration option of Orthanc is set to
+   * "true".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param details The details of the error message.
+   * @param log Whether to also write the detailed error to the Orthanc logs.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              details,
+    uint8_t                  log)
+  {
+    _OrthancPluginSetHttpErrorDetails params;
+    params.output = output;
+    params.details = details;
+    params.log = log;
+    context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char** result;
+    const char*  argument;
+  } _OrthancPluginRetrieveStaticString;
+
+  /**
+   * @brief Detect the MIME type of a file.
+   *
+   * This function returns the MIME type of a file by inspecting its extension.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path Path to the file.
+   * @return The MIME type. This is a statically-allocated
+   * string, do not free it.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType(
+    OrthancPluginContext*  context,
+    const char*            path)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginRetrieveStaticString params;
+    params.result = &result;
+    params.argument = path;
+
+    if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    const char*               name;
+    float                     value;
+    OrthancPluginMetricsType  type;
+  } _OrthancPluginSetMetricsValue;
+
+  /**
+   * @brief Set the value of a metrics.
+   *
+   * This function sets the value of a metrics to monitor the behavior
+   * of the plugin through tools such as Prometheus. The values of all
+   * the metrics are stored within the Orthanc context.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param name The name of the metrics to be set.
+   * @param value The value of the metrics.
+   * @param type The type of the metrics. This parameter is only taken into consideration
+   * the first time this metrics is set.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue(
+    OrthancPluginContext*     context,
+    const char*               name,
+    float                     value,
+    OrthancPluginMetricsType  type)
+  {
+    _OrthancPluginSetMetricsValue params;
+    params.name = name;
+    params.value = value;
+    params.type = type;
+    context->InvokeService(context, _OrthancPluginService_SetMetricsValue, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRefreshMetricsCallback  callback;
+  } _OrthancPluginRegisterRefreshMetricsCallback;
+
+  /**
+   * @brief Register a callback to refresh the metrics.
+   *
+   * This function registers a callback to refresh the metrics. The
+   * callback must make calls to OrthancPluginSetMetricsValue().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function to handle the refresh.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRefreshMetricsCallback(
+    OrthancPluginContext*               context,
+    OrthancPluginRefreshMetricsCallback callback)
+  {
+    _OrthancPluginRegisterRefreshMetricsCallback params;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRefreshMetricsCallback, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    char**                               target;
+    const void*                          dicom;
+    uint32_t                             dicomSize;
+    OrthancPluginDicomWebBinaryCallback  callback;
+  } _OrthancPluginEncodeDicomWeb;
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb JSON.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb JSON representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @see OrthancPluginCreateDicom()
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson(
+    OrthancPluginContext*                context,
+    const void*                          dicom,
+    uint32_t                             dicomSize,
+    OrthancPluginDicomWebBinaryCallback  callback)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb XML.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb XML representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @see OrthancPluginCreateDicom()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml(
+    OrthancPluginContext*                context,
+    const void*                          dicom,
+    uint32_t                             dicomSize,
+    OrthancPluginDicomWebBinaryCallback  callback)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+  
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- a/Resources/Samples/JavaScript/index.html	Tue Dec 08 17:24:44 2015 +0100
+++ b/Resources/Samples/JavaScript/index.html	Fri Jun 07 16:03:50 2019 +0200
@@ -3,11 +3,11 @@
 <html lang="us">
   <head>
     <meta charset="utf-8" />
-    <title>Orthanc DICOM Web Demo</title>
+    <title>Orthanc DICOMweb Demo</title>
   </head>
 
   <body>
-    <h1>Orthanc DICOM Web Demo</h2>
+    <h1>Orthanc DICOMweb Demo</h2>
 
     <h2>STOW-RS - Upload DICOM file</h2>
     <form id="stow">
--- a/Resources/Samples/JavaScript/qido-rs.js	Tue Dec 08 17:24:44 2015 +0100
+++ b/Resources/Samples/JavaScript/qido-rs.js	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/Resources/Samples/JavaScript/stow-rs.js	Tue Dec 08 17:24:44 2015 +0100
+++ b/Resources/Samples/JavaScript/stow-rs.js	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Proxy/NOTES.txt	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,9 @@
+This is a sample configuration file for nginx to test the DICOMweb
+plugin behind a HTTP proxy. To start the proxy as a regular user:
+
+$ nginx -c ./nginx.local.conf -p $PWD
+
+
+References about "Forwarded" header in nginx:
+https://onefeed.xyz/posts/x-forwarded-for-vs-x-real-ip.html
+https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Proxy/nginx.local.conf	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,39 @@
+worker_processes 1;
+error_log stderr;
+daemon off;
+pid nginx.pid;
+
+# `events` section is mandatory
+events {
+  worker_connections 1024; # Default: 1024
+}
+
+http {
+  # prevent nginx sync issues on OSX
+  proxy_buffering off;
+  access_log off;
+
+  server {
+    listen 9977 default_server;
+    client_max_body_size 4G;
+    
+    # location may have to be adjusted depending on your OS and nginx install
+    include /etc/nginx/mime.types;
+
+    # if not in your system mime.types, add this line to support WASM:
+    # types {
+    #    application/wasm                      wasm; 
+    # }
+
+    # reverse proxy orthanc
+	location /orthanc/ {
+		rewrite /orthanc(.*) $1 break;
+		proxy_pass http://127.0.0.1:8042;
+		proxy_set_header Host $http_host;
+		proxy_set_header my-auth-header good-token;
+		#proxy_request_buffering off;
+		#proxy_max_temp_file_size 0;
+		#client_max_body_size 0;
+	}
+  } 
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Python/SendStow.py	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2019 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+# We do not use Python's "email" package, as it uses LF (\n) for line
+# endings instead of CRLF (\r\n) for binary messages, as required by
+# RFC 1341
+# http://stackoverflow.com/questions/3086860/how-do-i-generate-a-multipart-mime-message-with-correct-crlf-in-python
+# https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
+
+import requests
+import sys
+import json
+import uuid
+
+if len(sys.argv) < 2:
+    print('Usage: %s <StowUri> <file>...' % sys.argv[0])
+    print('')
+    print('Example: %s http://localhost:8042/dicom-web/studies hello.dcm world.dcm' % sys.argv[0])
+    sys.exit(-1)
+
+URL = sys.argv[1]
+
+# Create a multipart message whose body contains all the input DICOM files
+boundary = str(uuid.uuid4())  # The boundary is a random UUID
+body = bytearray()
+
+for i in range(2, len(sys.argv)):
+    try:
+        with open(sys.argv[i], 'rb') as f:
+            content = f.read()
+            body += bytearray('--%s\r\n' % boundary, 'ascii')
+            body += bytearray('Content-Length: %d\r\n' % len(content), 'ascii')
+            body += bytearray('Content-Type: application/dicom\r\n\r\n', 'ascii')
+            body += content
+            body += bytearray('\r\n', 'ascii')
+    except:
+        print('Ignoring directory %s' % sys.argv[i])
+
+# Closing boundary
+body += bytearray('--%s--' % boundary, 'ascii')
+
+# Do the HTTP POST request to the STOW-RS server
+r = requests.post(URL, data=body, headers= {
+    'Content-Type' : 'multipart/related; type=application/dicom; boundary=%s' % boundary,
+    'Accept' : 'application/json',
+})
+
+j = json.loads(r.text)
+
+# Loop over the successful instances
+print('\nWADO-RS URL of the uploaded instances:')
+for instance in j['00081199']['Value']:
+    if '00081190' in instance:  # This instance has not been discarded
+        url = instance['00081190']['Value'][0]
+        print(url)
+
+print('\nWADO-RS URL of the study:')
+try:
+    print(j['00081190']['Value'][0])
+except:
+    print('No instance was uploaded!')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Python/WadoRetrieveStudy.py	Fri Jun 07 16:03:50 2019 +0200
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2019 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import email
+import urllib2
+import sys
+
+if len(sys.argv) != 2:
+    print('Usage: %s <Uri>' % sys.argv[0])
+    print('')
+    print('Example: %s http://localhost:8042/dicom-web/studies/1.3.51.0.1.1.192.168.29.133.1681753.1681732' % sys.argv[0])
+    sys.exit(-1)
+
+answer = urllib2.urlopen(sys.argv[1])
+s = str(answer.info()) + "\n" + answer.read()
+
+msg = email.message_from_string(s)
+
+for i, part in enumerate(msg.walk(), 1):
+    filename = 'wado-%06d.dcm' % i
+    dicom = part.get_payload(decode = True)
+    if dicom != None:
+        print('Storing DICOM file: %s' % filename)
+        with open(filename, 'wb') as f:
+            f.write(str(dicom))
--- a/Resources/Samples/SendStow.py	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-#!/usr/bin/python
-
-# Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2015 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 email
-import requests
-import sys
-import json
-from email.mime.multipart import MIMEMultipart
-from email.mime.application import MIMEApplication
-
-if len(sys.argv) < 2:
-    print('Usage: %s <StowUri> <file>...' % sys.argv[0])
-    print('')
-    print('Example: %s http://localhost:8042/dicom-web/studies hello.dcm world.dcm' % sys.argv[0])
-    sys.exit(-1)
-
-URL = sys.argv[1]
-
-related = MIMEMultipart('related')
-related.set_boundary('hello')
-
-for i in range(2, len(sys.argv)):
-    try:
-        with open(sys.argv[i], 'rb') as f:
-            dicom = MIMEApplication(f.read(), 'dicom', email.encoders.encode_noop)
-            related.attach(dicom)
-    except:
-        print('Ignoring directory %s' % sys.argv[i])
-
-headers = dict(related.items())
-body = related.as_string()
-
-# Discard the header
-body = body.split('\n\n', 1)[1]
-
-headers['Content-Type'] = 'multipart/related; type=application/dicom; boundary=%s' % related.get_boundary()
-headers['Accept'] = 'application/json'
-
-r = requests.post(URL, data=body, headers=headers)
-j = json.loads(r.text)
-
-# Loop over the successful instances
-print('\nWADO-RS URL of the uploaded instances:')
-for instance in j['00081199']['Value']:
-    if '00081190' in instance:  # This instance has not been discarded
-        url = instance['00081190']['Value'][0]
-        print(url)
-
-print('\nWADO-RS URL of the study:')
-print(j['00081190']['Value'][0])
--- a/Resources/Samples/WadoRetrieveStudy.py	Tue Dec 08 17:24:44 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-#!/usr/bin/python
-
-# Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2015 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 email
-import urllib2
-import sys
-
-if len(sys.argv) != 2:
-    print('Usage: %s <Uri>' % sys.argv[0])
-    print('')
-    print('Example: %s http://localhost:8042/dicom-web/studies/1.3.51.0.1.1.192.168.29.133.1681753.1681732' % sys.argv[0])
-    sys.exit(-1)
-
-answer = urllib2.urlopen(sys.argv[1])
-s = str(answer.info()) + "\n" + answer.read()
-
-msg = email.message_from_string(s)
-
-for i, part in enumerate(msg.walk(), 1):
-    filename = 'wado-%06d.dcm' % i
-    dicom = part.get_payload(decode = True)
-    if dicom != None:
-        print('Storing DICOM file: %s' % filename)
-        with open(filename, 'wb') as f:
-            f.write(str(dicom))
--- a/Resources/SyncOrthancFolder.py	Tue Dec 08 17:24:44 2015 +0100
+++ b/Resources/SyncOrthancFolder.py	Fri Jun 07 16:03:50 2019 +0200
@@ -10,48 +10,23 @@
 import stat
 import urllib2
 
-TARGET = os.path.join(os.path.dirname(__file__), '..', 'Orthanc')
-PLUGIN_SDK_VERSION = '0.9.5'
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+PLUGIN_SDK_VERSION = '1.5.4'
 REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
 
 FILES = [
-    'Core/ChunkedBuffer.cpp',
-    'Core/ChunkedBuffer.h',
-    'Core/Enumerations.cpp',
-    'Core/Enumerations.h',
-    'Core/Logging.h',
-    'Core/OrthancException.h',
-    'Core/PrecompiledHeaders.h',
-    'Core/Toolbox.cpp',
-    'Core/Toolbox.h',
-    'Plugins/Samples/Common/ExportedSymbols.list',
-    'Plugins/Samples/Common/VersionScript.map',
-    'Resources/CMake/BoostConfiguration.cmake',
-    'Resources/CMake/Compiler.cmake',
-    'Resources/CMake/DownloadPackage.cmake',
-    'Resources/CMake/GoogleTestConfiguration.cmake',
-    'Resources/CMake/JsonCppConfiguration.cmake',
-    'Resources/CMake/PugixmlConfiguration.cmake',
-    'Resources/CMake/ZlibConfiguration.cmake',
-    'Resources/MinGW-W64-Toolchain32.cmake',
-    'Resources/MinGW-W64-Toolchain64.cmake',
-    'Resources/MinGWToolchain.cmake',
-    'Resources/ThirdParty/VisualStudio/stdint.h',
-    'Resources/WindowsResources.py',
-    'Resources/WindowsResources.rc',
+    'DownloadOrthancFramework.cmake',
+    'LinuxStandardBaseToolchain.cmake',
+    'MinGW-W64-Toolchain32.cmake',
+    'MinGW-W64-Toolchain64.cmake',
+    'MinGWToolchain.cmake',
 ]
 
 SDK = [
     'orthanc/OrthancCPlugin.h',
-]   
-
-EXE = [ 
-    'Resources/WindowsResources.py',
 ]
 
 
-
-
 def Download(x):
     branch = x[0]
     source = x[1]
@@ -72,19 +47,17 @@
 commands = []
 
 for f in FILES:
-    commands.append([ 'default', f, f ])
+    commands.append([ 'default',
+                      os.path.join('Resources', f),
+                      f ])
 
 for f in SDK:
-    commands.append([ 'Orthanc-%s' % PLUGIN_SDK_VERSION, 
-                      'Plugins/Include/%s' % f,
-                      'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) ])
+    commands.append([
+        'Orthanc-%s' % PLUGIN_SDK_VERSION, 
+        'Plugins/Include/%s' % f,
+        'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) 
+    ])
 
 
 pool = multiprocessing.Pool(10)  # simultaneous downloads
 pool.map(Download, commands)
-
-
-for exe in EXE:
-    path = os.path.join(TARGET, exe)
-    st = os.stat(path)
-    os.chmod(path, st.st_mode | stat.S_IEXEC)
--- a/Status.txt	Tue Dec 08 17:24:44 2015 +0100
+++ b/Status.txt	Fri Jun 07 16:03:50 2019 +0200
@@ -50,7 +50,9 @@
 6.5.4 WADO-RS / RetrieveFrames
 ================================
 
-Not supported.
+Almost entirely supported.
+
+The only missing feature is returning multi-frame media types (cf. Table 6.5-1).
 
 
 
@@ -80,6 +82,14 @@
 
 
 
+===========================================
+6.5.8 WADO-RS / RetrieveRenderedTransaction
+===========================================
+
+Not supported yet.
+
+
+
 ===========
 6.6 STOW-RS
 ===========
@@ -118,3 +128,19 @@
 
 * Flag "fuzzymatching"
 * Header "Cache-control"
+
+
+
+==========================================================
+CP 1509 - Refactor media type description for web services
+==========================================================
+
+Not supported.
+
+"There are some significant changes described in CP 1509 to various
+parts of the PS3.18 standard that defines DICOMweb services. [...] The
+most important changes are cleaning up the bulk data media types,
+adding a rendered component to the URL for rendered resources,
+clarifying that compressed bulk data never contains the encapsulation
+item tags, and making JSON support required on the server side and the
+default for query responses." [David Clunie]
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Dec 08 17:24:44 2015 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Fri Jun 07 16:03:50 2019 +0200
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -20,14 +21,16 @@
 
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 #include "../Plugin/Configuration.h"
-#include "../Plugin/Plugin.h"
 
 using namespace OrthancPlugins;
 
+OrthancPluginContext* context_ = NULL;
 
 
+// TODO => Remove this test (now in Orthanc core)
 TEST(ContentType, Parse)
 {
   std::string c;
@@ -39,6 +42,15 @@
   ASSERT_EQ(a["type"], "Application/Dicom");
   ASSERT_EQ(a["boundary"], "heLLO");
 
+  // The WADO-RS client must support the case where the WADO-RS server
+  // escapes the "type" subfield in the Content-Type header
+  // cf. https://tools.ietf.org/html/rfc7231#section-3.1.1.1
+  ParseContentType(c, a, "Multipart/Related; TYPE=\"Application/Dicom\"  ;  Boundary=heLLO");
+  ASSERT_EQ(c, "multipart/related");
+  ASSERT_EQ(2u, a.size());
+  ASSERT_EQ(a["type"], "Application/Dicom");
+  ASSERT_EQ(a["boundary"], "heLLO");
+  
   ParseContentType(c, a, "");
   ASSERT_TRUE(c.empty());
   ASSERT_EQ(0u, a.size());