changeset 432:ad671caa2dcf

integration OrthancDicomWeb-0.3->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 26 May 2020 11:05:10 +0200
parents 906efffc68fd (diff) 1adc7c4d67e9 (current diff)
children 9a0457a3ca19 5449610fb02f
files
diffstat 89 files changed, 22428 insertions(+), 15679 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Fri Jul 15 12:01:19 2016 +0200
+++ b/AUTHORS	Tue May 26 11:05:10 2020 +0200
@@ -5,9 +5,16 @@
 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	Fri Jul 15 12:01:19 2016 +0200
+++ b/CMakeLists.txt	Tue May 26 11:05:10 2020 +0200
@@ -1,6 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 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,46 +21,66 @@
 
 project(OrthancDicomWeb)
 
-set(ORTHANC_DICOM_WEB_VERSION "0.3")
+set(ORTHANC_DICOM_WEB_VERSION "mainline")
+
+if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline")
+  # TODO - Switch to "mainline" after "transcoding" is made the new "default"
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.6.1")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.6.1")
+  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 framework (can be \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework")
+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.7" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"1.5.4\", \"1.5.7\", 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)
+
+set(BUILD_BOOTSTRAP_VUE OFF CACHE BOOL "Compile Bootstrap-Vue from sources")
+set(BUILD_BABEL_POLYFILL OFF CACHE BOOL "Retrieve babel-polyfill from npm")
+
+
 
-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)
+# Download and setup the Orthanc framework
+include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/DownloadOrthancFramework.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(ENABLE_MODULE_JOBS OFF)
+set(USE_BOOST_ICONV ON)
+
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include_directories(${ORTHANC_ROOT})
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/JavaScriptLibraries.cmake)
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
-  include_directories(${ORTHANC_ROOT}/Sdk-1.1.0)
+  if (ORTHANC_SDK_VERSION STREQUAL "1.5.4")
+    include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.4)
+  elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.7")
+    include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.7)
+  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,40 +119,50 @@
 endif()
 
 
-add_definitions(
-  -DORTHANC_ENABLE_MD5=0
-  -DORTHANC_ENABLE_BASE64=0
-  -DORTHANC_ENABLE_LOGGING=0
-  -DHAS_ORTHANC_EXCEPTION=1
+
+if (STANDALONE_BUILD)
+  add_definitions(-DORTHANC_STANDALONE=1)
+  set(ADDITIONAL_RESOURCES
+    ORTHANC_EXPLORER  ${CMAKE_SOURCE_DIR}/Plugin/OrthancExplorer.js
+    WEB_APPLICATION   ${CMAKE_SOURCE_DIR}/WebApplication/
+    )
+else()
+  add_definitions(-DORTHANC_STANDALONE=0)
+endif()
+
+EmbedResources(
+  --no-upcase-check
+  ${ADDITIONAL_RESOURCES}
+  JAVASCRIPT_LIBS   ${JAVASCRIPT_LIBS_DIR}
   )
 
+
 include_directories(${ORTHANC_ROOT}/Core)  # To access "OrthancException.h"
 
-set(CORE_SOURCES
-  ${BOOST_SOURCES}
-  ${JSONCPP_SOURCES}
-  ${ZLIB_SOURCES}
-  ${PUGIXML_SOURCES}
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  -DDICOMWEB_CLIENT_PATH="${CMAKE_SOURCE_DIR}/WebApplication/"
+  )
 
-  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
-  ${ORTHANC_ROOT}/Core/Enumerations.cpp
-  ${ORTHANC_ROOT}/Core/Toolbox.cpp
-  ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
+set(CORE_SOURCES
+  Plugin/Configuration.cpp
+  Plugin/GdcmParsedDicomFile.cpp
+
   ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
-
-  Plugin/Configuration.cpp
-  Plugin/Dicom.cpp
-  Plugin/DicomResults.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/WadoRs.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/WadoRsRetrieveFrames.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/WadoRsRetrieveRendered.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/WadoUri.cpp
   ${AUTOGENERATED_SOURCES}
   )
@@ -155,12 +186,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	Fri Jul 15 12:01:19 2016 +0200
+++ b/NEWS	Tue May 26 11:05:10 2020 +0200
@@ -2,7 +2,105 @@
 ===============================
 
 
-Version 0.3 (2016/06/28)
+Maintenance
+-----------
+
+* "QidoCaseSensitive" defaults to "CaseSensitivePN" of Orthanc configuration (instead of "true")
+
+
+Version 1.1 (2020-03-04)
+========================
+
+New features
+------------
+
+* Support of "window", "viewport" and "quality" parameters in "Retrieve Rendered Transaction"
+* Support of "/studies/.../rendered" and "/studies/.../series/.../rendered"
+* QIDO-RS: Allow to query against a list of multiple values separated by commas
+* WADO-RS "Retrieve Metadata": Configuration options "StudiesMetadata"
+  and "SeriesMetadata", whose value can be "Full" (read all DICOM
+  instances from the storage area), "MainDicomTags" (only report the
+  main DICOM tags from the Orthanc database), or "Extrapolate" (main
+  DICOM tags + user-specified tags extrapolated from a few random instances)
+
+Maintenance
+-----------
+
+* QIDO-RS: Optimization for large studies/series
+* QIDO-RS: Fix returned attributes if Study/Series Instance UIDs are not filtered
+* Fix handling of the "Forwarded" HTTP header
+* Fix support for client certificate authentication
+* Accept multiple MIME types in Accept header for WADO-RS "Retrieve Metadata"
+  https://groups.google.com/d/msg/orthanc-users/P3B6J9abZpE/syn5dnW2AwAJ
+* Added explicit "Accept" header to avoid uncompressing DICOM files by Google cloud,
+  can be turned off by setting "HasWadoRsUniversalTransferSyntax" to "false" (for
+  instance if contacting a remote Orthanc plugin with DICOMweb version <= 1.0)
+  https://groups.google.com/d/msg/orthanc-users/w1Ekrsc6-U8/T2a_DoQ5CwAJ
+* Fix issue #162 ("DICOMweb metadata resource reads all instances")
+* Fix issue #164 ("JPEG YBR_422 generates a 500 in the DicomWeb plugin")
+* Upgrade to GDCM 3.0.4 in static builds
+
+
+Version 1.0 (2019-06-26)
+========================
+
+=> Recommended SDK version: 1.5.7 <=
+=> Minimum SDK version: 1.5.4 <=
+
+* Web user interface to QIDO-RS, WADO-RS and STOW-RS client
+* First implementation of WADO-RS "Retrieve Rendered Transaction", for
+  ".../instances/.../rendered" and ".../instances/.../frames/.../rendered"
+* WADO-RS and STOW-RS client now create Orthanc jobs
+* Support "Transfer-Encoding: chunked" to reduce memory consumption in STOW-RS
+  (provided the SDK version is above 1.5.7)
+* New URI: /dicom-web/servers/.../qido
+* New URI: /dicom-web/servers/.../delete
+* Handling of the HTTP header "Forwarded" for WADO-RS
+* Full refactoring of multipart parsing
+
+
+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 <=
@@ -15,7 +113,7 @@
 * Fix issue #14 (Aggregate fields empty for QIDO-RS study/series-level queries)
 
 
-Version 0.2 (2015/12/10)
+Version 0.2 (2015-12-10)
 ========================
 
 => Minimum SDK version: 0.9.5 <=
@@ -25,7 +123,7 @@
 * 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 <=
@@ -45,7 +143,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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "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 void* chunkData,
-                               size_t chunkSize)
-  {
-    if (chunkSize == 0)
-    {
-      return;
-    }
-    else
-    {
-      assert(chunkData != NULL);
-      chunks_.push_back(new std::string(reinterpret_cast<const char*>(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();
-    numBytes_ = 0;
-  }
-}
--- a/Orthanc/Core/ChunkedBuffer.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#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 void* chunkData,
-                  size_t chunkSize);
-
-    void AddChunk(const std::string& chunk);
-
-    void Flatten(std::string& result);
-  };
-}
--- a/Orthanc/Core/Enumerations.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1392 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "Enumerations.h"
-
-#include "OrthancException.h"
-#include "Toolbox.h"
-#include "Logging.h"
-
-#include <string.h>
-#include <cassert>
-
-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 privileged or already in use";
-
-      case ErrorCode_DicomPortInUse:
-        return "The TCP port of the DICOM server is privileged or 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";
-
-      case ErrorCode_AlreadyExistingTag:
-        return "Cannot override the value of a tag that already exists";
-
-      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);
-    }
-  }
-
-
-  const char* EnumerationToString(PixelFormat format)
-  {
-    switch (format)
-    {
-      case PixelFormat_RGB24:
-        return "RGB24";
-
-      case PixelFormat_RGBA32:
-        return "RGBA32";
-
-      case PixelFormat_Grayscale8:
-        return "Grayscale (unsigned 8bpp)";
-
-      case PixelFormat_Grayscale16:
-        return "Grayscale (unsigned 16bpp)";
-
-      case PixelFormat_SignedGrayscale16:
-        return "Grayscale (signed 16bpp)";
-
-      case PixelFormat_Float32:
-        return "Grayscale (float 32bpp)";
-
-      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);
-    }
-  }
-
-
-  ValueRepresentation StringToValueRepresentation(const std::string& vr,
-                                                  bool throwIfUnsupported)
-  {
-    if (vr == "AE")
-    {
-      return ValueRepresentation_ApplicationEntity;
-    }
-    else if (vr == "AS")
-    {
-      return ValueRepresentation_AgeString;
-    }
-    else if (vr == "AT")
-    {
-      return ValueRepresentation_AttributeTag;
-    }
-    else if (vr == "CS")
-    {
-      return ValueRepresentation_CodeString;
-    }
-    else if (vr == "DA")
-    {
-      return ValueRepresentation_Date;
-    }
-    else if (vr == "DS")
-    {
-      return ValueRepresentation_DecimalString;
-    }
-    else if (vr == "DT")
-    {
-      return ValueRepresentation_DateTime;
-    }
-    else if (vr == "FL")
-    {
-      return ValueRepresentation_FloatingPointSingle;
-    }
-    else if (vr == "FD")
-    {
-      return ValueRepresentation_FloatingPointDouble;
-    }
-    else if (vr == "IS")
-    {
-      return ValueRepresentation_IntegerString;
-    }
-    else if (vr == "LO")
-    {
-      return ValueRepresentation_LongString;
-    }
-    else if (vr == "LT")
-    {
-      return ValueRepresentation_LongText;
-    }
-    else if (vr == "OB")
-    {
-      return ValueRepresentation_OtherByte;
-    }
-    else if (vr == "OD")
-    {
-      return ValueRepresentation_OtherDouble;
-    }
-    else if (vr == "OF")
-    {
-      return ValueRepresentation_OtherFloat;
-    }
-    else if (vr == "OL")
-    {
-      return ValueRepresentation_OtherLong;
-    }
-    else if (vr == "OW")
-    {
-      return ValueRepresentation_OtherWord;
-    }
-    else if (vr == "PN")
-    {
-      return ValueRepresentation_PersonName;
-    }
-    else if (vr == "SH")
-    {
-      return ValueRepresentation_ShortString;
-    }
-    else if (vr == "SL")
-    {
-      return ValueRepresentation_SignedLong;
-    }
-    else if (vr == "SQ")
-    {
-      return ValueRepresentation_Sequence;
-    }
-    else if (vr == "SS")
-    {
-      return ValueRepresentation_SignedShort;
-    }
-    else if (vr == "ST")
-    {
-      return ValueRepresentation_ShortText;
-    }
-    else if (vr == "TM")
-    {
-      return ValueRepresentation_Time;
-    }
-    else if (vr == "UC")
-    {
-      return ValueRepresentation_UnlimitedCharacters;
-    }
-    else if (vr == "UI")
-    {
-      return ValueRepresentation_UniqueIdentifier;
-    }
-    else if (vr == "UL")
-    {
-      return ValueRepresentation_UnsignedLong;
-    }
-    else if (vr == "UN")
-    {
-      return ValueRepresentation_Unknown;
-    }
-    else if (vr == "UR")
-    {
-      return ValueRepresentation_UniversalResource;
-    }
-    else if (vr == "US")
-    {
-      return ValueRepresentation_UnsignedShort;
-    }
-    else if (vr == "UT")
-    {
-      return ValueRepresentation_UnlimitedText;
-    }
-    else
-    {
-      std::string s = "Unsupported value representation encountered: " + vr;
-
-      if (throwIfUnsupported)
-      {
-        LOG(ERROR) << s;
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        LOG(INFO) << s;
-        return ValueRepresentation_NotSupported;
-      }
-    }
-  }
-
-
-  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;
-
-      case PixelFormat_Float32:
-        assert(sizeof(float) == 4);
-        return 4;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  bool GetDicomEncoding(Encoding& encoding,
-                        const char* specificCharacterSet)
-  {
-    std::string s = specificCharacterSet;
-    Toolbox::ToUpperCase(s);
-
-    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_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://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_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);
-  }
-
-
-  bool IsBinaryValueRepresentation(ValueRepresentation vr)
-  {
-    // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
-
-    switch (vr)
-    {
-      case ValueRepresentation_ApplicationEntity:     // AE
-      case ValueRepresentation_AgeString:             // AS
-      case ValueRepresentation_CodeString:            // CS
-      case ValueRepresentation_Date:                  // DA
-      case ValueRepresentation_DecimalString:         // DS
-      case ValueRepresentation_DateTime:              // DT
-      case ValueRepresentation_IntegerString:         // IS
-      case ValueRepresentation_LongString:            // LO
-      case ValueRepresentation_LongText:              // LT
-      case ValueRepresentation_PersonName:            // PN
-      case ValueRepresentation_ShortString:           // SH
-      case ValueRepresentation_ShortText:             // ST
-      case ValueRepresentation_Time:                  // TM
-      case ValueRepresentation_UnlimitedCharacters:   // UC
-      case ValueRepresentation_UniqueIdentifier:      // UI (UID)
-      case ValueRepresentation_UniversalResource:     // UR (URI or URL)
-      case ValueRepresentation_UnlimitedText:         // UT
-      {
-        return false;
-      }
-
-      /**
-       * Below are all the VR whose character repertoire is tagged as
-       * "not applicable"
-       **/
-      case ValueRepresentation_AttributeTag:          // AT (2 x uint16_t)
-      case ValueRepresentation_FloatingPointSingle:   // FL (float)
-      case ValueRepresentation_FloatingPointDouble:   // FD (double)
-      case ValueRepresentation_OtherByte:             // OB
-      case ValueRepresentation_OtherDouble:           // OD
-      case ValueRepresentation_OtherFloat:            // OF
-      case ValueRepresentation_OtherLong:             // OL
-      case ValueRepresentation_OtherWord:             // OW
-      case ValueRepresentation_SignedLong:            // SL (int32_t)
-      case ValueRepresentation_Sequence:              // SQ
-      case ValueRepresentation_SignedShort:           // SS (int16_t)
-      case ValueRepresentation_UnsignedLong:          // UL (uint32_t)
-      case ValueRepresentation_Unknown:               // UN
-      case ValueRepresentation_UnsignedShort:         // US (uint16_t)
-      {
-        return true;
-      }
-
-      case ValueRepresentation_NotSupported:
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-}
--- a/Orthanc/Core/Enumerations.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,540 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <string>
-
-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 privileged or already in use */,
-    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or 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_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
-    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}{Graylevel, floating-point image.}
-     * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.}
-     **/
-    PixelFormat_Float32 = 6
-  };
-
-
-  /**
-   * {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
-  };
-
-
-  // Specific Character Sets
-  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_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
-  };
-
-
-  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_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
-  };
-
-  enum ServerBarrierEvent
-  {
-    ServerBarrierEvent_Stop,
-    ServerBarrierEvent_Reload  // SIGHUP signal: reload configuration file
-  };
-
-  enum FileMode
-  {
-    FileMode_ReadBinary,
-    FileMode_WriteBinary
-  };
-
-  /**
-   * The value representations Orthanc knows about. They correspond to
-   * the DICOM 2016b version of the standard.
-   * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
-   **/
-  enum ValueRepresentation
-  {
-    ValueRepresentation_ApplicationEntity = 1,     // AE
-    ValueRepresentation_AgeString = 2,             // AS
-    ValueRepresentation_AttributeTag = 3,          // AT (2 x uint16_t)
-    ValueRepresentation_CodeString = 4,            // CS
-    ValueRepresentation_Date = 5,                  // DA
-    ValueRepresentation_DecimalString = 6,         // DS
-    ValueRepresentation_DateTime = 7,              // DT
-    ValueRepresentation_FloatingPointSingle = 8,   // FL (float)
-    ValueRepresentation_FloatingPointDouble = 9,   // FD (double)
-    ValueRepresentation_IntegerString = 10,        // IS
-    ValueRepresentation_LongString = 11,           // LO
-    ValueRepresentation_LongText = 12,             // LT
-    ValueRepresentation_OtherByte = 13,            // OB
-    ValueRepresentation_OtherDouble = 14,          // OD
-    ValueRepresentation_OtherFloat = 15,           // OF
-    ValueRepresentation_OtherLong = 16,            // OL
-    ValueRepresentation_OtherWord = 17,            // OW
-    ValueRepresentation_PersonName = 18,           // PN
-    ValueRepresentation_ShortString = 19,          // SH
-    ValueRepresentation_SignedLong = 20,           // SL (int32_t)
-    ValueRepresentation_Sequence = 21,             // SQ
-    ValueRepresentation_SignedShort = 22,          // SS (int16_t)
-    ValueRepresentation_ShortText = 23,            // ST
-    ValueRepresentation_Time = 24,                 // TM
-    ValueRepresentation_UnlimitedCharacters = 25,  // UC
-    ValueRepresentation_UniqueIdentifier = 26,     // UI (UID)
-    ValueRepresentation_UnsignedLong = 27,         // UL (uint32_t)
-    ValueRepresentation_Unknown = 28,              // UN
-    ValueRepresentation_UniversalResource = 29,    // UR (URI or URL)
-    ValueRepresentation_UnsignedShort = 30,        // US (uint16_t)
-    ValueRepresentation_UnlimitedText = 31,        // UT
-    ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
-  };
-
-
-  /**
-   * 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);
-
-  const char* EnumerationToString(PixelFormat format);
-
-  Encoding StringToEncoding(const char* encoding);
-
-  ResourceType StringToResourceType(const char* type);
-
-  ImageFormat StringToImageFormat(const char* format);
-
-  LogLevel StringToLogLevel(const char* level);
-
-  ValueRepresentation StringToValueRepresentation(const std::string& vr,
-                                                  bool throwIfUnsupported);
-
-  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);
-
-  bool IsBinaryValueRepresentation(ValueRepresentation vr);
-}
--- a/Orthanc/Core/Logging.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <iostream>
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    void Initialize();
-
-    void Finalize();
-
-    void Reset();
-
-    void Flush();
-
-    void EnableInfoLevel(bool enabled);
-
-    void EnableTraceLevel(bool enabled);
-
-    void SetTargetFile(const std::string& path);
-
-    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;
-      }
-
-      // This overload fixes build problems with Visual Studio 2015
-      std::ostream& operator<< (const char* 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 */
-
-#  include <boost/thread/mutex.hpp>
-#  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
-#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
-
-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  // ORTHANC_ENABLE_LOGGING
--- a/Orthanc/Core/OrthancException.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1568 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "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_;
-  static ServerBarrierEvent barrierEvent_;
-
-#if defined(_WIN32)
-  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
-  {
-    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
-    finish_ = true;
-    return true;
-  }
-#else
-  static void SignalHandler(int signal)
-  {
-    if (signal == SIGHUP)
-    {
-      barrierEvent_ = ServerBarrierEvent_Reload;
-    }
-
-    finish_ = true;
-  }
-#endif
-
-
-  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 ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
-  {
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, true);
-#else
-    signal(SIGINT, SignalHandler);
-    signal(SIGQUIT, SignalHandler);
-    signal(SIGTERM, SignalHandler);
-    signal(SIGHUP, SignalHandler);
-#endif
-  
-    // Active loop that awakens every 100ms
-    finish_ = false;
-    barrierEvent_ = ServerBarrierEvent_Stop;
-    while (!(*stopFlag || finish_))
-    {
-      Toolbox::USleep(100 * 1000);
-    }
-
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, false);
-#else
-    signal(SIGINT, NULL);
-    signal(SIGQUIT, NULL);
-    signal(SIGTERM, NULL);
-    signal(SIGHUP, NULL);
-#endif
-
-    return barrierEvent_;
-  }
-
-
-  ServerBarrierEvent Toolbox::ServerBarrier(const bool& stopFlag)
-  {
-    return ServerBarrierInternal(&stopFlag);
-  }
-
-  ServerBarrierEvent Toolbox::ServerBarrier()
-  {
-    const bool stopFlag = false;
-    return ServerBarrierInternal(&stopFlag);
-  }
-
-
-  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);
-  }
-
-
-  static std::streamsize GetStreamSize(std::istream& f)
-  {
-    // http://www.cplusplus.com/reference/iostream/istream/tellg/
-    f.seekg(0, std::ios::end);
-    std::streamsize size = f.tellg();
-    f.seekg(0, std::ios::beg);
-
-    return size;
-  }
-
-
-  void Toolbox::ReadFile(std::string& content,
-                         const std::string& path) 
-  {
-    if (!IsRegularFile(path))
-    {
-      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
-      throw OrthancException(ErrorCode_RegularFileExpected);
-    }
-
-    boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ifstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    std::streamsize size = GetStreamSize(f);
-    content.resize(size);
-    if (size != 0)
-    {
-      f.read(reinterpret_cast<char*>(&content[0]), size);
-    }
-
-    f.close();
-  }
-
-
-  bool Toolbox::ReadHeader(std::string& header,
-                           const std::string& path,
-                           size_t headerSize)
-  {
-    if (!IsRegularFile(path))
-    {
-      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
-      throw OrthancException(ErrorCode_RegularFileExpected);
-    }
-
-    boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ifstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    bool full = true;
-
-    {
-      std::streamsize size = GetStreamSize(f);
-      if (size <= 0)
-      {
-        headerSize = 0;
-        full = false;
-      }
-      else if (static_cast<size_t>(size) < headerSize)
-      {
-        headerSize = size;  // Truncate to the size of the file
-        full = false;
-      }
-    }
-
-    header.resize(headerSize);
-    if (headerSize != 0)
-    {
-      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
-    }
-
-    f.close();
-
-    return full;
-  }
-
-
-  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 (IsRegularFile(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)
-  {
-    for (size_t i = 0; i < data.length(); i++)
-    {
-      if (!isalnum(data[i]) &&
-          data[i] != '+' &&
-          data[i] != '/' &&
-          data[i] != '=')
-      {
-        // This is not a valid character for a Base64 string
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-
-    result = base64_decode(data);
-  }
-
-
-#  if BOOST_HAS_REGEX == 1
-  bool 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]);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-#  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
-  }
-
-
-  bool Toolbox::IsRegularFile(const std::string& path)
-  {
-    namespace fs = boost::filesystem;
-
-    try
-    {
-      if (fs::exists(path))
-      {
-        fs::file_status status = fs::status(path);
-        return (status.type() == boost::filesystem::regular_file ||
-                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
-      }
-    }
-    catch (fs::filesystem_error&)
-    {
-    }
-
-    return false;
-  }
-
-
-  FILE* Toolbox::OpenFile(const std::string& path,
-                          FileMode mode)
-  {
-#if defined(_WIN32)
-    // TODO Deal with special characters by converting to the current locale
-#endif
-
-    const char* m;
-    switch (mode)
-    {
-      case FileMode_ReadBinary:
-        m = "rb";
-        break;
-
-      case FileMode_WriteBinary:
-        m = "wb";
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    return fopen(path.c_str(), m);
-  }
-
-  
-
-  static bool IsUnreservedCharacter(char c)
-  {
-    // This function checks whether "c" is an unserved character
-    // wrt. an URI percent-encoding
-    // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI
-
-    return ((c >= 'A' && c <= 'Z') ||
-            (c >= 'a' && c <= 'z') ||
-            (c >= '0' && c <= '9') ||
-            c == '-' ||
-            c == '_' ||
-            c == '.' ||
-            c == '~');
-  }
-
-  void Toolbox::UriEncode(std::string& target,
-                          const std::string& source)
-  {
-    // Estimate the length of the percent-encoded URI
-    size_t length = 0;
-
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (IsUnreservedCharacter(source[i]))
-      {
-        length += 1;
-      }
-      else
-      {
-        // This character must be percent-encoded
-        length += 3;
-      }
-    }
-
-    target.clear();
-    target.reserve(length);
-
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (IsUnreservedCharacter(source[i]))
-      {
-        target.push_back(source[i]);
-      }
-      else
-      {
-        // This character must be percent-encoded
-        uint8_t byte = static_cast<uint8_t>(source[i]);
-        uint8_t a = byte >> 4;
-        uint8_t b = byte & 0x0f;
-
-        target.push_back('%');
-        target.push_back(a < 10 ? a + '0' : a - 10 + 'A');
-        target.push_back(b < 10 ? b + '0' : b - 10 + 'A');
-      }
-    }
-  }  
-}
--- a/Orthanc/Core/Toolbox.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#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
-  {
-    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
-
-    ServerBarrierEvent 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);
-
-    bool ReadHeader(std::string& header,
-                    const std::string& path,
-                    size_t headerSize);
-
-    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
-    bool 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();
-
-    bool IsRegularFile(const std::string& path);
-
-    FILE* OpenFile(const std::string& path,
-                   FileMode mode);
-
-    void UriEncode(std::string& target,
-                   const std::string& source);
-  }
-}
--- a/Orthanc/Core/WebServiceParameters.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,265 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "WebServiceParameters.h"
-
-#include "../Core/Logging.h"
-#include "../Core/Toolbox.h"
-#include "../Core/OrthancException.h"
-
-#include <cassert>
-
-namespace Orthanc
-{
-  WebServiceParameters::WebServiceParameters() : 
-    advancedFormat_(false),
-    url_("http://127.0.0.1:8042/"),
-    pkcs11Enabled_(false)
-  {
-  }
-
-
-  void WebServiceParameters::ClearClientCertificate()
-  {
-    certificateFile_.clear();
-    certificateKeyFile_.clear();
-    certificateKeyPassword_.clear();
-  }
-
-
-  void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
-                                                  const std::string& certificateKeyFile,
-                                                  const std::string& certificateKeyPassword)
-  {
-    if (certificateFile.empty())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (!Toolbox::IsRegularFile(certificateFile))
-    {
-      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    if (!certificateKeyFile.empty() && 
-        !Toolbox::IsRegularFile(certificateKeyFile))
-    {
-      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    advancedFormat_ = true;
-    certificateFile_ = certificateFile;
-    certificateKeyFile_ = certificateKeyFile;
-    certificateKeyPassword_ = certificateKeyPassword;
-  }
-
-
-  static void AddTrailingSlash(std::string& url)
-  {
-    if (url.size() != 0 && 
-        url[url.size() - 1] != '/')
-    {
-      url += '/';
-    }
-  }
-
-
-  void WebServiceParameters::FromJsonArray(const Json::Value& peer)
-  {
-    assert(peer.isArray());
-
-    advancedFormat_ = false;
-    pkcs11Enabled_ = false;
-
-    if (peer.size() != 1 && 
-        peer.size() != 3)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    std::string url = peer.get(0u, "").asString();
-    if (url.empty())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    AddTrailingSlash(url);
-    SetUrl(url);
-
-    if (peer.size() == 1)
-    {
-      SetUsername("");
-      SetPassword("");
-    }
-    else if (peer.size() == 3)
-    {
-      SetUsername(peer.get(1u, "").asString());
-      SetPassword(peer.get(2u, "").asString());
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  static std::string GetStringMember(const Json::Value& peer,
-                                     const std::string& key,
-                                     const std::string& defaultValue)
-  {
-    if (!peer.isMember(key))
-    {
-      return defaultValue;
-    }
-    else if (peer[key].type() != Json::stringValue)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      return peer[key].asString();
-    }
-  }
-
-
-  void WebServiceParameters::FromJsonObject(const Json::Value& peer)
-  {
-    assert(peer.isObject());
-    advancedFormat_ = true;
-
-    std::string url = GetStringMember(peer, "Url", "");
-    if (url.empty())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    AddTrailingSlash(url);
-    SetUrl(url);
-
-    SetUsername(GetStringMember(peer, "Username", ""));
-    SetPassword(GetStringMember(peer, "Password", ""));
-
-    if (peer.isMember("CertificateFile"))
-    {
-      SetClientCertificate(GetStringMember(peer, "CertificateFile", ""),
-                           GetStringMember(peer, "CertificateKeyFile", ""),
-                           GetStringMember(peer, "CertificateKeyPassword", ""));
-    }
-
-    if (peer.isMember("Pkcs11"))
-    {
-      if (peer["Pkcs11"].type() == Json::booleanValue)
-      {
-        pkcs11Enabled_ = peer["Pkcs11"].asBool();
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-  }
-
-
-  void WebServiceParameters::FromJson(const Json::Value& peer)
-  {
-    try
-    {
-      if (peer.isArray())
-      {
-        FromJsonArray(peer);
-      }
-      else if (peer.isObject())
-      {
-        FromJsonObject(peer);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-    catch (OrthancException&)
-    {
-      throw;
-    }
-    catch (...)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  void WebServiceParameters::ToJson(Json::Value& value) const
-  {
-    if (advancedFormat_)
-    {
-      value = Json::objectValue;
-      value["Url"] = url_;
-
-      if (!username_.empty() ||
-          !password_.empty())
-      {
-        value["Username"] = username_;
-        value["Password"] = password_;
-      }
-
-      if (!certificateFile_.empty())
-      {
-        value["CertificateFile"] = certificateFile_;
-      }
-
-      if (!certificateKeyFile_.empty())
-      {
-        value["CertificateKeyFile"] = certificateKeyFile_;
-      }
-
-      if (!certificateKeyPassword_.empty())
-      {
-        value["CertificateKeyPassword"] = certificateKeyPassword_;
-      }
-    }
-    else
-    {
-      value = Json::arrayValue;
-      value.append(url_);
-
-      if (!username_.empty() ||
-          !password_.empty())
-      {
-        value.append(username_);
-        value.append(password_);
-      }
-    }
-  }
-}
--- a/Orthanc/Core/WebServiceParameters.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <string>
-#include <json/json.h>
-
-namespace Orthanc
-{
-  class WebServiceParameters
-  {
-  private:
-    bool        advancedFormat_;
-    std::string url_;
-    std::string username_;
-    std::string password_;
-    std::string certificateFile_;
-    std::string certificateKeyFile_;
-    std::string certificateKeyPassword_;
-    bool        pkcs11Enabled_;
-
-    void FromJsonArray(const Json::Value& peer);
-
-    void FromJsonObject(const Json::Value& peer);
-
-  public:
-    WebServiceParameters();
-
-    const std::string& GetUrl() const
-    {
-      return url_;
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    const std::string& GetUsername() const
-    {
-      return username_;
-    }
-
-    void SetUsername(const std::string& username)
-    {
-      username_ = username;
-    }
-    
-    const std::string& GetPassword() const
-    {
-      return password_;
-    }
-
-    void SetPassword(const std::string& password)
-    {
-      password_ = password;
-    }
-
-    void ClearClientCertificate();
-
-    void SetClientCertificate(const std::string& certificateFile,
-                              const std::string& certificateKeyFile,
-                              const std::string& certificateKeyPassword);
-
-    const std::string& GetCertificateFile() const
-    {
-      return certificateFile_;
-    }
-
-    const std::string& GetCertificateKeyFile() const
-    {
-      return certificateKeyFile_;
-    }
-
-    const std::string& GetCertificateKeyPassword() const
-    {
-      return certificateKeyPassword_;
-    }
-
-    void SetPkcs11Enabled(bool pkcs11Enabled)
-    {
-      pkcs11Enabled_ = pkcs11Enabled;
-    }
-
-    bool IsPkcs11Enabled() const
-    {
-      return pkcs11Enabled_;
-    }
-
-    void FromJson(const Json::Value& peer);
-
-    void ToJson(Json::Value& value) const;
-  };
-}
--- a/Orthanc/Plugins/Samples/Common/ExportedSymbols.list	Fri Jul 15 12:01:19 2016 +0200
+++ /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/OrthancPluginCppWrapper.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,829 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrthancPluginCppWrapper.h"
-
-#include <json/reader.h>
-
-
-namespace OrthancPlugins
-{
-  const char* PluginException::GetErrorDescription(OrthancPluginContext* context) const
-  {
-    const char* description = OrthancPluginGetErrorDescription(context, code_);
-    if (description)
-    {
-      return description;
-    }
-    else
-    {
-      return "No description available";
-    }
-  }
-
-
-  MemoryBuffer::MemoryBuffer(OrthancPluginContext* context) : 
-    context_(context)
-  {
-    buffer_.data = NULL;
-    buffer_.size = 0;
-  }
-
-
-  void MemoryBuffer::Clear()
-  {
-    if (buffer_.data != NULL)
-    {
-      OrthancPluginFreeMemoryBuffer(context_, &buffer_);
-      buffer_.data = NULL;
-      buffer_.size = 0;
-    }
-  }
-
-
-  void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
-  {
-    Clear();
-
-    buffer_.data = other.data;
-    buffer_.size = other.size;
-
-    other.data = NULL;
-    other.size = 0;
-  }
-
-
-  void MemoryBuffer::ToString(std::string& target) const
-  {
-    if (buffer_.size == 0)
-    {
-      target.clear();
-    }
-    else
-    {
-      target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
-    }
-  }
-
-
-  void MemoryBuffer::ToJson(Json::Value& target) const
-  {
-    if (buffer_.data == NULL ||
-        buffer_.size == 0)
-    {
-      throw PluginException(OrthancPluginErrorCode_InternalError);
-    }
-
-    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
-
-    Json::Reader reader;
-    if (!reader.parse(tmp, tmp + buffer_.size, target))
-    {
-      OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    OrthancPluginErrorCode error;
-
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiGetAfterPlugins(context_, &buffer_, uri.c_str());
-    }
-    else
-    {
-      error = OrthancPluginRestApiGet(context_, &buffer_, uri.c_str());
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      throw PluginException(error);
-    }
-  }
-
-  
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const char* body,
-                                 size_t bodySize,
-                                 bool applyPlugins)
-  {
-    Clear();
-
-    OrthancPluginErrorCode error;
-
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiPostAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize);
-    }
-    else
-    {
-      error = OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body, bodySize);
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      throw PluginException(error);
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const char* body,
-                                size_t bodySize,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    OrthancPluginErrorCode error;
-
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiPutAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize);
-    }
-    else
-    {
-      error = OrthancPluginRestApiPut(context_, &buffer_, uri.c_str(), body, bodySize);
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      throw PluginException(error);
-    }
-  }
-
-
-  OrthancString::OrthancString(OrthancPluginContext* context,
-                               char* str) :
-    context_(context),
-    str_(str)
-  {
-  }
-
-
-  void OrthancString::Clear()
-  {
-    if (str_ != NULL)
-    {
-      OrthancPluginFreeString(context_, str_);
-      str_ = NULL;
-    }
-  }
-
-
-  void OrthancString::ToString(std::string& target) const
-  {
-    if (str_ == NULL)
-    {
-      target.clear();
-    }
-    else
-    {
-      target.assign(str_);
-    }
-  }
-
-
-  void OrthancString::ToJson(Json::Value& target) const
-  {
-    if (str_ == NULL)
-    {
-      OrthancPluginLogError(context_, "Cannot convert an empty memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
-    }
-
-    Json::Reader reader;
-    if (!reader.parse(str_, target))
-    {
-      OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-  }
-  
-
-  OrthancConfiguration::OrthancConfiguration(OrthancPluginContext* context) : 
-    context_(context)
-  {
-    OrthancString str(context, OrthancPluginGetConfiguration(context));
-
-    if (str.GetContent() == NULL)
-    {
-      OrthancPluginLogError(context, "Cannot access the Orthanc configuration");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
-    }
-
-    str.ToJson(configuration_);
-
-    if (configuration_.type() != Json::objectValue)
-    {
-      OrthancPluginLogError(context, "Unable to read the Orthanc configuration");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
-    }
-  }
-
-
-  OrthancPluginContext* OrthancConfiguration::GetContext() const
-  {
-    if (context_ == NULL)
-    {
-      throw PluginException(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      return context_;
-    }
-  }
-
-
-  std::string OrthancConfiguration::GetPath(const std::string& key) const
-  {
-    if (path_.empty())
-    {
-      return key;
-    }
-    else
-    {
-      return path_ + "." + key;
-    }
-  }
-
-
-  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
-                                        const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.context_ = context_;
-    target.path_ = GetPath(key);
-
-    if (!configuration_.isMember(key))
-    {
-      target.configuration_ = Json::objectValue;
-    }
-    else
-    {
-      if (configuration_[key].type() != Json::objectValue)
-      {
-        if (context_ != NULL)
-        {
-          std::string s = "The configuration section \"" + target.path_ + "\" is not an associative array as expected";
-          OrthancPluginLogError(context_, s.c_str());
-        }
-
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-      }
-
-      target.configuration_ = configuration_[key];
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupStringValue(std::string& target,
-                                               const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    if (configuration_[key].type() != Json::stringValue)
-    {
-      if (context_ != NULL)
-      {
-        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a string as expected";
-        OrthancPluginLogError(context_, s.c_str());
-      }
-
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-
-    target = configuration_[key].asString();
-    return true;
-  }
-
-
-  bool OrthancConfiguration::LookupIntegerValue(int& target,
-                                                const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-      case Json::intValue:
-        target = configuration_[key].asInt();
-        return true;
-        
-      case Json::uintValue:
-        target = configuration_[key].asUInt();
-        return true;
-        
-      default:
-        if (context_ != NULL)
-        {
-          std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected";
-          OrthancPluginLogError(context_, s.c_str());
-        }
-
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
-                                                        const std::string& key) const
-  {
-    int tmp;
-    if (!LookupIntegerValue(tmp, key))
-    {
-      return false;
-    }
-
-    if (tmp < 0)
-    {
-      if (context_ != NULL)
-      {
-        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a positive integer as expected";
-        OrthancPluginLogError(context_, s.c_str());
-      }
-
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      target = static_cast<unsigned int>(tmp);
-      return true;
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupBooleanValue(bool& target,
-                                                const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    if (configuration_[key].type() != Json::booleanValue)
-    {
-      if (context_ != NULL)
-      {
-        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a Boolean as expected";
-        OrthancPluginLogError(context_, s.c_str());
-      }
-
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-
-    target = configuration_[key].asBool();
-    return true;
-  }
-
-
-  bool OrthancConfiguration::LookupFloatValue(float& target,
-                                              const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-      case Json::realValue:
-        target = configuration_[key].asFloat();
-        return true;
-        
-      case Json::intValue:
-        target = configuration_[key].asInt();
-        return true;
-        
-      case Json::uintValue:
-        target = configuration_[key].asUInt();
-        return true;
-        
-      default:
-        if (context_ != NULL)
-        {
-          std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected";
-          OrthancPluginLogError(context_, s.c_str());
-        }
-
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-  }
-
-  
-  std::string OrthancConfiguration::GetStringValue(const std::string& key,
-                                                   const std::string& defaultValue) const
-  {
-    std::string tmp;
-    if (LookupStringValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  int OrthancConfiguration::GetIntegerValue(const std::string& key,
-                                            int defaultValue) const
-  {
-    int tmp;
-    if (LookupIntegerValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
-                                                             unsigned int defaultValue) const
-  {
-    unsigned int tmp;
-    if (LookupUnsignedIntegerValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  bool OrthancConfiguration::GetBooleanValue(const std::string& key,
-                                             bool defaultValue) const
-  {
-    bool tmp;
-    if (LookupBooleanValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  float OrthancConfiguration::GetFloatValue(const std::string& key,
-                                            float defaultValue) const
-  {
-    float tmp;
-    if (LookupFloatValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  void OrthancImage::Clear()
-  {
-    if (image_ != NULL)
-    {
-      OrthancPluginFreeImage(context_, image_);
-      image_ = NULL;
-    }
-  }
-
-
-  void OrthancImage::CheckImageAvailable()
-  {
-    if (image_ == NULL)
-    {
-      OrthancPluginLogError(context_, "Trying to access a NULL image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginContext*  context) :
-    context_(context),
-    image_(NULL)
-  {
-    if (context == NULL)
-    {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginContext*  context,
-                             OrthancPluginImage*    image) :
-    context_(context),
-    image_(image)
-  {
-    if (context == NULL)
-    {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-  }
-  
-
-  OrthancImage::OrthancImage(OrthancPluginContext*     context,
-                             OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height) :
-    context_(context)
-  {
-    if (context == NULL)
-    {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      image_ = OrthancPluginCreateImage(context, format, width, height);
-    }
-  }
-
-
-  void OrthancImage::UncompressPngImage(const void* data,
-                                        size_t size)
-  {
-    Clear();
-    image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Png);
-    if (image_ == NULL)
-    {
-      OrthancPluginLogError(context_, "Cannot uncompress a PNG image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::UncompressJpegImage(const void* data,
-                                         size_t size)
-  {
-    Clear();
-    image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Jpeg);
-    if (image_ == NULL)
-    {
-      OrthancPluginLogError(context_, "Cannot uncompress a JPEG image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::DecodeDicomImage(const void* data,
-                                      size_t size,
-                                      unsigned int frame)
-  {
-    Clear();
-    image_ = OrthancPluginDecodeDicomImage(context_, data, size, frame);
-    if (image_ == NULL)
-    {
-      OrthancPluginLogError(context_, "Cannot uncompress a DICOM image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancPluginPixelFormat OrthancImage::GetPixelFormat()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePixelFormat(context_, image_);
-  }
-
-
-  unsigned int OrthancImage::GetWidth()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageWidth(context_, image_);
-  }
-
-
-  unsigned int OrthancImage::GetHeight()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageHeight(context_, image_);
-  }
-
-
-  unsigned int OrthancImage::GetPitch()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePitch(context_, image_);
-  }
-
-    
-  const void* OrthancImage::GetBuffer()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageBuffer(context_, image_);
-  }
-
-
-  void OrthancImage::CompressPngImage(MemoryBuffer& target)
-  {
-    CheckImageAvailable();
-    
-    OrthancPluginMemoryBuffer tmp;
-    OrthancPluginCompressPngImage(context_, &tmp, GetPixelFormat(), 
-                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-
-    target.Assign(tmp);
-  }
-
-
-  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
-                                       uint8_t quality)
-  {
-    CheckImageAvailable();
-    
-    OrthancPluginMemoryBuffer tmp;
-    OrthancPluginCompressJpegImage(context_, &tmp, GetPixelFormat(), 
-                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-    
-    target.Assign(tmp);
-  }
-
-
-  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output)
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerPngImage(context_, output, GetPixelFormat(),
-                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-  }
-
-
-  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
-                                     uint8_t quality)
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerJpegImage(context_, output, GetPixelFormat(),
-                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-  }
-
-
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      bool applyPlugins)
-  {
-    MemoryBuffer answer(context);
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToJson(result);
-      return true;
-    }
-  }
-
-
-  bool RestApiPostJson(Json::Value& result,
-                       OrthancPluginContext* context,
-                       const std::string& uri,
-                       const char* body,
-                       size_t bodySize,
-                       bool applyPlugins)
-  {
-    MemoryBuffer answer(context);
-    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToJson(result);
-      return true;
-    }
-  }
-
-
-  bool RestApiPutJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      const char* body,
-                      size_t bodySize,
-                      bool applyPlugins)
-  {
-    MemoryBuffer answer(context);
-    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToJson(result);
-      return true;
-    }
-  }
-
-
-  bool RestApiDelete(OrthancPluginContext* context,
-                     const std::string& uri,
-                     bool applyPlugins)
-  {
-    OrthancPluginErrorCode error;
-
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiDeleteAfterPlugins(context, uri.c_str());
-    }
-    else
-    {
-      error = OrthancPluginRestApiDelete(context, uri.c_str());
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      throw PluginException(error);
-    }
-  }
-}
-
--- a/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,384 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <orthanc/OrthancCPlugin.h>
-#include <boost/noncopyable.hpp>
-#include <boost/lexical_cast.hpp>
-#include <json/value.h>
-
-#if HAS_ORTHANC_EXCEPTION == 1
-#  include <OrthancException.h>
-#endif
-
-
-namespace OrthancPlugins
-{
-  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
-                                const char* url,
-                                const OrthancPluginHttpRequest* request);
-
-
-  class PluginException
-  {
-  private:
-    OrthancPluginErrorCode  code_;
-
-  public:
-    PluginException(OrthancPluginErrorCode code) : code_(code)
-    {
-    }
-
-    OrthancPluginErrorCode GetErrorCode() const
-    {
-      return code_;
-    }
-
-    const char* GetErrorDescription(OrthancPluginContext* context) const;
-  };
-
-
-  class MemoryBuffer : public boost::noncopyable
-  {
-  private:
-    OrthancPluginContext*      context_;
-    OrthancPluginMemoryBuffer  buffer_;
-
-  public:
-    MemoryBuffer(OrthancPluginContext* context);
-
-    ~MemoryBuffer()
-    {
-      Clear();
-    }
-
-    OrthancPluginMemoryBuffer* operator*()
-    {
-      return &buffer_;
-    }
-
-    // This transfers ownership
-    void Assign(OrthancPluginMemoryBuffer& other);
-
-    const char* GetData() const
-    {
-      if (buffer_.size > 0)
-      {
-        return reinterpret_cast<const char*>(buffer_.data);
-      }
-      else
-      {
-        return NULL;
-      }
-    }
-
-    size_t GetSize() const
-    {
-      return buffer_.size;
-    }
-
-    void Clear();
-
-    void ToString(std::string& target) const;
-
-    void ToJson(Json::Value& target) const;
-
-    bool RestApiGet(const std::string& uri,
-                    bool applyPlugins);
-
-    bool RestApiPost(const std::string& uri,
-                     const char* body,
-                     size_t bodySize,
-                     bool applyPlugins);
-
-    bool RestApiPut(const std::string& uri,
-                    const char* body,
-                    size_t bodySize,
-                    bool applyPlugins);
-
-    bool RestApiPost(const std::string& uri,
-                     const std::string& body,
-                     bool applyPlugins)
-    {
-      return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
-    }
-
-    bool RestApiPut(const std::string& uri,
-                    const std::string& body,
-                    bool applyPlugins)
-    {
-      return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
-    }
-  };
-
-
-  class OrthancString : public boost::noncopyable
-  {
-  private:
-    OrthancPluginContext*  context_;
-    char*                  str_;
-
-  public:
-    OrthancString(OrthancPluginContext* context,
-                  char* str);
-
-    ~OrthancString()
-    {
-      Clear();
-    }
-
-    void Clear();
-
-    const char* GetContent() const
-    {
-      return str_;
-    }
-
-    void ToString(std::string& target) const;
-
-    void ToJson(Json::Value& target) const;
-  };
-
-
-  class OrthancConfiguration : public boost::noncopyable
-  {
-  private:
-    OrthancPluginContext*  context_;
-    Json::Value            configuration_;
-    std::string            path_;
-
-    std::string GetPath(const std::string& key) const;
-
-  public:
-    OrthancConfiguration() : context_(NULL)
-    {
-    }
-
-    OrthancConfiguration(OrthancPluginContext* context);
-
-    OrthancPluginContext* GetContext() const;
-
-    const Json::Value& GetJson() const
-    {
-      return configuration_;
-    }
-
-    void GetSection(OrthancConfiguration& target,
-                    const std::string& key) const;
-
-    bool LookupStringValue(std::string& target,
-                           const std::string& key) const;
-    
-    bool LookupIntegerValue(int& target,
-                            const std::string& key) const;
-
-    bool LookupUnsignedIntegerValue(unsigned int& target,
-                                    const std::string& key) const;
-
-    bool LookupBooleanValue(bool& target,
-                            const std::string& key) const;
-
-    bool LookupFloatValue(float& target,
-                          const std::string& key) const;
-
-    std::string GetStringValue(const std::string& key,
-                               const std::string& defaultValue) const;
-
-    int GetIntegerValue(const std::string& key,
-                        int defaultValue) const;
-
-    unsigned int GetUnsignedIntegerValue(const std::string& key,
-                                         unsigned int defaultValue) const;
-
-    bool GetBooleanValue(const std::string& key,
-                         bool defaultValue) const;
-
-    float GetFloatValue(const std::string& key,
-                        float defaultValue) const;
-  };
-
-  class OrthancImage
-  {
-  private:
-    OrthancPluginContext*  context_;
-    OrthancPluginImage*    image_;
-
-    void Clear();
-
-    void CheckImageAvailable();
-
-  public:
-    OrthancImage(OrthancPluginContext*  context);
-
-    OrthancImage(OrthancPluginContext*  context,
-                 OrthancPluginImage*    image);
-
-    OrthancImage(OrthancPluginContext*     context,
-                 OrthancPluginPixelFormat  format,
-                 uint32_t                  width,
-                 uint32_t                  height);
-
-    ~OrthancImage()
-    {
-      Clear();
-    }
-
-    void UncompressPngImage(const void* data,
-                            size_t size);
-
-    void UncompressJpegImage(const void* data,
-                             size_t size);
-
-    void DecodeDicomImage(const void* data,
-                          size_t size,
-                          unsigned int frame);
-
-    OrthancPluginPixelFormat GetPixelFormat();
-
-    unsigned int GetWidth();
-
-    unsigned int GetHeight();
-
-    unsigned int GetPitch();
-    
-    const void* GetBuffer();
-
-    void CompressPngImage(MemoryBuffer& target);
-
-    void CompressJpegImage(MemoryBuffer& target,
-                           uint8_t quality);
-
-    void AnswerPngImage(OrthancPluginRestOutput* output);
-
-    void AnswerJpegImage(OrthancPluginRestOutput* output,
-                         uint8_t quality);
-  };
-
-
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      bool applyPlugins);
-
-  bool RestApiPostJson(Json::Value& result,
-                       OrthancPluginContext* context,
-                       const std::string& uri,
-                       const char* body,
-                       size_t bodySize,
-                       bool applyPlugins);
-
-  bool RestApiPutJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      const char* body,
-                      size_t bodySize,
-                      bool applyPlugins);
-
-  inline bool RestApiPostJson(Json::Value& result,
-                              OrthancPluginContext* context,
-                              const std::string& uri,
-                              const std::string& body,
-                              bool applyPlugins)
-  {
-    return RestApiPostJson(result, context, uri, body.empty() ? NULL : body.c_str(), 
-                           body.size(), applyPlugins);
-  }
-
-  bool RestApiDelete(OrthancPluginContext* context,
-                     const std::string& uri,
-                     bool applyPlugins);
-
-  inline bool RestApiPutJson(Json::Value& result,
-                             OrthancPluginContext* context,
-                             const std::string& uri,
-                             const std::string& body,
-                             bool applyPlugins)
-  {
-    return RestApiPutJson(result, context, uri, body.empty() ? NULL : body.c_str(), 
-                          body.size(), applyPlugins);
-  }
-
-  bool RestApiDelete(OrthancPluginContext* context,
-                     const std::string& uri,
-                     bool applyPlugins);
-
-
-  namespace Internals
-  {
-    template <RestCallback Callback>
-    OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
-                                   const char* url,
-                                   const OrthancPluginHttpRequest* request)
-    {
-      try
-      {
-        Callback(output, url, request);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (OrthancPlugins::PluginException& e)
-      {
-        return e.GetErrorCode();
-      }
-#if HAS_ORTHANC_EXCEPTION == 1
-      catch (Orthanc::OrthancException& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-#endif
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-  }
-
-  
-  template <RestCallback Callback>
-  void RegisterRestCallback(OrthancPluginContext* context,
-                            const std::string& uri,
-                            bool isThreadSafe)
-  {
-    if (isThreadSafe)
-    {
-      OrthancPluginRegisterRestCallbackNoLock(context, uri.c_str(), Internals::Protect<Callback>);
-    }
-    else
-    {
-      OrthancPluginRegisterRestCallback(context, uri.c_str(), Internals::Protect<Callback>);
-    }
-  }
-}
--- a/Orthanc/Plugins/Samples/Common/VersionScript.map	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +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 ${ORTHANC_BOOST_COMPONENTS})
-
-  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.60.0
-  set(BOOST_NAME boost_1_60_0)
-  set(BOOST_BCP_SUFFIX bcpdigest-1.0.1)
-  set(BOOST_MD5 "a789f8ec2056ad1c2d5f0cb64687cc7b")
-  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
-      -DBOOST_LOCALE_NO_WINAPI_BACKEND=1
-      -DBOOST_LOCALE_NO_STD_BACKEND=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()
-
-    add_definitions(
-      -DBOOST_LOCALE_NO_POSIX_BACKEND=1
-      -DBOOST_LOCALE_NO_STD_BACKEND=1
-      )
-  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
-    )
-
-  if (USE_BOOST_LOCALE_BACKENDS)
-    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/locale/src/posix/codecvt.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/converter.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/numeric.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp
-        )
-    elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-      list(APPEND BOOST_SOURCES
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/lcid.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/numeric.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp
-        )
-    else()
-      message(FATAL_ERROR "Support your platform here")
-    endif()
-
-    list(APPEND BOOST_SOURCES
-      ${BOOST_REGEX_SOURCES}
-      ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
-      ${BOOST_SOURCES_DIR}/libs/system/src/error_code.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/shared/generator.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/date_time.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/formatting.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/ids.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/localization_backend.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/message.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/mo_lambda.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/codecvt_converter.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/default_locale.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/gregorian.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/info.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/locale_data.cpp
-      )        
-  endif()
-
-  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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +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 -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")
-
-  if (NOT DEFINED ENABLE_PLUGINS_VERSION_SCRIPT OR 
-      ENABLE_PLUGINS_VERSION_SCRIPT)
-    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
-  endif()
-
-  # 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()
-
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-  if (NOT HAVE_UUID_H)
-    message(FATAL_ERROR "Please install the uuid-dev package")
-  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)
-    # Some additional C/C++ compiler flags for MinGW
-    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
-
-    # 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)
-
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-  if (NOT HAVE_UUID_H)
-    message(FATAL_ERROR "Please install the uuid-dev package")
-  endif()
-
-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 (STATIC_BUILD)
-  add_definitions(-DORTHANC_STATIC=1)
-else()
-  add_definitions(-DORTHANC_STATIC=0)
-endif()
--- a/Orthanc/Resources/CMake/DownloadPackage.cmake	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +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") OR
-          ("${TMP_EXTENSION}" STREQUAL "xz"))
-        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}")
-        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
-          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
-        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
-          string(REGEX REPLACE ".xz" "" 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
-          )
-      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
-        execute_process(
-          COMMAND sh -c "${TAR_EXECUTABLE} xf ${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	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +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()
-
-  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
-  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
-    file(STRINGS
-      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
-      JSONCPP_VERSION_MAJOR1 REGEX
-      ".*define JSONCPP_VERSION_MAJOR.*")
-
-    if (NOT JSONCPP_VERSION_MAJOR1)
-      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
-    endif()
-    
-    string(REGEX REPLACE
-      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
-      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
-    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
-
-    if (CMAKE_COMPILER_IS_GNUCXX AND 
-        JSONCPP_VERSION_MAJOR GREATER 0)
-      message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
-      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
-    endif()
-  else()
-    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
-  endif()
-
-endif()
--- a/Orthanc/Resources/CMake/PugixmlConfiguration.cmake	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /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	Fri Jul 15 12:01:19 2016 +0200
+++ /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-2016 Sebastien Jodogne, Medical Physics
-# Department, University Hospital of Liege, Belgium
-#
-# This program is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# In addition, as a special exception, the copyright holders of this
-# program give permission to link the code of its release with the
-# OpenSSL project's "OpenSSL" library (or with modified versions of it
-# that use the same license as the "OpenSSL" library), and distribute
-# the linked executables. You must obey the GNU General Public License
-# in all respects for all of the code used other than "OpenSSL". If you
-# modify file(s) with this exception, you may extend this exception to
-# your version of the file(s), but you are not obligated to do so. If
-# you do not wish to do so, delete this exception statement from your
-# version. If you delete this exception statement from all source files
-# in the program, then also delete it here.
-# 
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-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	Fri Jul 15 12:01:19 2016 +0200
+++ /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-1.1.0/orthanc/OrthancCPlugin.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5365 +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 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 OrthancPluginRegisterIncomingHttpRequestFilter().
- * -# <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 DicomCallbaks DicomCallbaks
- * @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
- *
- * 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     1
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
-
-
-
-/********************************************************************
- ** 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 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,
-
-    /* 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,
-
-    /* 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,
-
-    /* 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_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 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 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 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
-   **/
-  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 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.
-   * @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 issuerAet 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.
-   *
-   * @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*                issuerAet,
-    const char*                sourceAet,
-    const char*                targetAet,
-    uint16_t                   moveOriginatorId);
-    
-
-  /**
-   * @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. 
-   **/
-  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.
-   **/
-  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.
-   **/
-  typedef void (*OrthancPluginFreeMove) (void* moveDriver);
-
-
-
-  /**
-   * @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.
-   * @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
-   * @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(), 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 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 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
-   **/
-  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 body of the POST 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.
-   **/
-  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);
-  }
-
-
-
-
-#ifdef  __cplusplus
-}
-#endif
-
-
-/** @} */
-
--- a/Plugin/Configuration.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/Configuration.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,17 @@
 
 #include "Configuration.h"
 
+#include "DicomWebServers.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Toolbox.h>
+
 #include <fstream>
 #include <json/reader.h>
 #include <boost/regex.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
-#include "Plugin.h"
-#include "DicomWebServers.h"
-#include "../Orthanc/Core/Toolbox.h"
 
 namespace OrthancPlugins
 {
@@ -67,15 +71,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;
       }
@@ -83,110 +87,32 @@
   }
 
 
-  void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          OrthancPluginContext* context,
-                          const char* body,
-                          const uint64_t bodySize,
-                          const std::string& boundary)
+  void ParseAssociativeArray(std::map<std::string, std::string>& target,
+                             const Json::Value& value)
   {
-    // Reference:
-    // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
-
-    result.clear();
-
-    const boost::regex separator("(^|\r\n)--" + boundary + "(--|\r\n)");
-    const boost::regex encapsulation("(.*)\r\n\r\n(.*)");
- 
-    std::vector< std::pair<const char*, const char*> > parts;
-    
-    const char* start = body;
-    const char* end = body + bodySize;
-
-    boost::cmatch what;
-    boost::match_flag_type flags = boost::match_perl | boost::match_single_line;
-    while (boost::regex_search(start, end, what, separator, flags))   
+    if (value.type() != Json::objectValue)
     {
-      if (start != body)  // Ignore the first separator
-      {
-        parts.push_back(std::make_pair(start, what[0].first));
-      }
-
-      if (*what[2].first == '-')
-      {
-        // This is the last separator (there is a trailing "--")
-        break;
-      }
-
-      start = what[0].second;
-      flags |= boost::match_prev_avail;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "The JSON object is not a JSON associative array as expected");
     }
 
-    for (size_t i = 0; i < parts.size(); i++)
-    {
-      if (boost::regex_match(parts[i].first, parts[i].second, what, encapsulation, boost::match_perl))
-      {
-        size_t dicomSize = what[2].second - what[2].first;
-
-        std::string contentType = "application/octet-stream";
-        std::vector<std::string> headers;
-
-        {
-          std::string tmp;
-          tmp.assign(what[1].first, what[1].second);
-          Orthanc::Toolbox::TokenizeString(headers, tmp, '\n');
-        }
-
-        bool valid = true;
-
-        for (size_t j = 0; j < headers.size(); j++)
-        {
-          std::vector<std::string> tokens;
-          Orthanc::Toolbox::TokenizeString(tokens, headers[j], ':');
-
-          if (tokens.size() == 2)
-          {
-            std::string key = Orthanc::Toolbox::StripSpaces(tokens[0]);
-            std::string value = Orthanc::Toolbox::StripSpaces(tokens[1]);
-            Orthanc::Toolbox::ToLowerCase(key);
+    Json::Value::Members names = value.getMemberNames();
 
-            if (key == "content-type")
-            {
-              contentType = value;
-            }
-            else if (key == "content-length")
-            {
-              try
-              {
-                size_t s = boost::lexical_cast<size_t>(value);
-                if (s != dicomSize)
-                {
-                  valid = false;
-                }
-              }
-              catch (boost::bad_lexical_cast&)
-              {
-                valid = false;
-              }
-            }
-          }
-        }
-
-        if (valid)
-        {
-          MultipartItem item;
-          item.data_ = what[2].first;
-          item.size_ = dicomSize;
-          item.contentType_ = contentType;
-          result.push_back(item);          
-        }
-        else
-        {
-          OrthancPluginLogWarning(context, "Ignoring a badly-formatted item in a multipart body");
-        }
-      }      
+    for (size_t i = 0; i < names.size(); i++)
+    {
+      if (value[names[i]].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Value \"" + names[i] + "\" in the associative array "
+                                        "is not a string as expected");
+      }
+      else
+      {
+        target[names[i]] = value[names[i]].asString();
+      }
     }
   }
-
+  
 
   void ParseAssociativeArray(std::map<std::string, std::string>& target,
                              const Json::Value& value,
@@ -194,39 +120,167 @@
   {
     if (value.type() != Json::objectValue)
     {
-      OrthancPlugins::Configuration::LogError("This is not a JSON object");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-
-    if (!value.isMember(key))
-    {
-      return;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "This is not a JSON object");
     }
 
-    const Json::Value& tmp = value[key];
+    if (value.isMember(key))
+    {
+      ParseAssociativeArray(target, value[key]);
+    }
+    else
+    {
+      target.clear();
+    }
+  }
+
 
-    if (tmp.type() != Json::objectValue)
+  bool ParseTag(Orthanc::DicomTag& target,
+                const std::string& name)
+  {
+    OrthancPluginDictionaryEntry entry;
+    
+    if (OrthancPluginLookupDictionary(OrthancPlugins::GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success)
+    {
+      target = Orthanc::DicomTag(entry.group, entry.element);
+      return true;
+    }
+    else
     {
-      OrthancPlugins::Configuration::LogError("The field \"" + key + "\" of a JSON object is "
-                                              "not a JSON associative array as expected");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      return false;
+    }
+  }
+
+
+  void ParseJsonBody(Json::Value& target,
+                     const OrthancPluginHttpRequest* request)
+  {
+    Json::Reader reader;
+    if (!reader.parse(reinterpret_cast<const char*>(request->body),
+                      reinterpret_cast<const char*>(request->body) + request->bodySize, target))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "A JSON file was expected");
+    }
+  }
+
+
+  std::string RemoveMultipleSlashes(const std::string& source)
+  {
+    std::string target;
+    target.reserve(source.size());
+
+    size_t prefix = 0;
+  
+    if (boost::starts_with(source, "https://"))
+    {
+      prefix = 8;
+    }
+    else if (boost::starts_with(source, "http://"))
+    {
+      prefix = 7;
     }
 
-    Json::Value::Members names = tmp.getMemberNames();
-
-    for (size_t i = 0; i < names.size(); i++)
+    for (size_t i = 0; i < prefix; i++)
     {
-      if (tmp[names[i]].type() != Json::stringValue)
+      target.push_back(source[i]);
+    }
+
+    bool isLastSlash = false;
+
+    for (size_t i = prefix; i < source.size(); i++)
+    {
+      if (source[i] == '/')
       {
-        OrthancPlugins::Configuration::LogError("Some value in the associative array \"" + key + 
-                                                "\" is not a string as expected");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+        if (!isLastSlash)
+        {
+          target.push_back('/');
+          isLastSlash = true;
+        }
       }
       else
       {
-        target[names[i]] = tmp[names[i]].asString();
+        target.push_back(source[i]);
+        isLastSlash = false;
       }
     }
+
+    return target;
+  }
+
+
+  bool LookupStringValue(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))
+    {
+      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;
+    }
+  }
+
+
+  bool LookupIntegerValue(int& 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))
+    {
+      return false;
+    }
+    else if (json[key].type() != Json::intValue &&
+             json[key].type() != Json::uintValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);      
+    }
+    else
+    {
+      target = json[key].asInt();
+      return true;
+    }
+  }
+
+
+  bool LookupBooleanValue(bool& 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))
+    {
+      return false;
+    }
+    else if (json[key].type() != Json::booleanValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);      
+    }
+    else
+    {
+      target = json[key].asBool();
+      return true;
+    }
   }
 
 
@@ -234,13 +288,15 @@
   {
     // Assume Latin-1 encoding by default (as in the Orthanc core)
     static Orthanc::Encoding defaultEncoding_ = Orthanc::Encoding_Latin1;
-    static OrthancConfiguration configuration_;
+    static std::auto_ptr<OrthancConfiguration> configuration_;
 
 
-    void Initialize(OrthancPluginContext* context)
-    {      
-      OrthancPlugins::OrthancConfiguration global(context);
-      global.GetSection(configuration_, "DicomWeb");
+    void Initialize()
+    {
+      configuration_.reset(new OrthancConfiguration);
+      
+      OrthancPlugins::OrthancConfiguration global;
+      global.GetSection(*configuration_, "DicomWeb");
 
       std::string s;
       if (global.LookupStringValue(s, "DefaultEncoding"))
@@ -249,41 +305,55 @@
       }
 
       OrthancPlugins::OrthancConfiguration servers;
-      configuration_.GetSection(servers, "Servers");
+      configuration_->GetSection(servers, "Servers");
       OrthancPlugins::DicomWebServers::GetInstance().Load(servers.GetJson());
-    }
 
+      // Check configuration during initialization
+      GetMetadataMode(Orthanc::ResourceType_Study);
+      GetMetadataMode(Orthanc::ResourceType_Series);
 
-    OrthancPluginContext* GetContext()
-    {
-      return configuration_.GetContext();
+      std::set<Orthanc::DicomTag> tags;
+      GetExtrapolatedMetadataTags(tags, Orthanc::ResourceType_Study);
+      GetExtrapolatedMetadataTags(tags, Orthanc::ResourceType_Series);
     }
 
 
     std::string GetStringValue(const std::string& key,
                                const std::string& defaultValue)
     {
-      return configuration_.GetStringValue(key, defaultValue);
+      assert(configuration_.get() != NULL);
+      return configuration_->GetStringValue(key, defaultValue);
     }
 
 
     bool GetBooleanValue(const std::string& key,
                          bool defaultValue)
     {
-      return configuration_.GetBooleanValue(key, defaultValue);
+      assert(configuration_.get() != NULL);
+      return configuration_->GetBooleanValue(key, defaultValue);
+    }
+
+
+    bool LookupBooleanValue(bool& target,
+                            const std::string& key)
+    {
+      assert(configuration_.get() != NULL);
+      return configuration_->LookupBooleanValue(target, key);
     }
 
 
     unsigned int GetUnsignedIntegerValue(const std::string& key,
                                          unsigned int defaultValue)
     {
-      return configuration_.GetUnsignedIntegerValue(key, defaultValue);
+      assert(configuration_.get() != NULL);
+      return configuration_->GetUnsignedIntegerValue(key, defaultValue);
     }
 
 
-    std::string GetRoot()
+    std::string GetDicomWebRoot()
     {
-      std::string root = configuration_.GetStringValue("Root", "/dicom-web/");
+      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 ||
@@ -300,10 +370,45 @@
       return root;
     }
 
+    
+    std::string GetOrthancApiRoot()
+    {
+      std::string root = OrthancPlugins::Configuration::GetDicomWebRoot();
+      std::vector<std::string> tokens;
+      Orthanc::Toolbox::TokenizeString(tokens, root, '/');
+
+      int depth = 0;
+      for (size_t i = 0; i < tokens.size(); i++)
+      {
+        if (tokens[i].empty() ||
+            tokens[i] == ".")
+        {
+          // Don't change the depth
+        }
+        else if (tokens[i] == "..")
+        {
+          depth--;
+        }
+        else
+        {
+          depth++;
+        }
+      }
+
+      std::string orthancRoot = "./";
+      for (int i = 0; i < depth; i++)
+      {
+        orthancRoot += "../";
+      }
+
+      return orthancRoot;
+    }
+
 
     std::string GetWadoRoot()
     {
-      std::string root = configuration_.GetStringValue("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 ||
@@ -322,20 +427,116 @@
     }
 
 
-    std::string  GetBaseUrl(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;
+      }
+    }
+
+
+    static bool LookupHttpHeader2(std::string& value,
+                                  const OrthancPlugins::HttpClient::HttpHeaders& headers,
+                                  const std::string& name)
+    {
+      for (OrthancPlugins::HttpClient::HttpHeaders::const_iterator
+             it = headers.begin(); it != headers.end(); ++it)
+      {
+        if (boost::iequals(it->first, name))
+        {
+          value = it->second;
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+
+    std::string GetBaseUrl(const OrthancPlugins::HttpClient::HttpHeaders& headers)
     {
-      std::string host = configuration_.GetStringValue("Host", "");
-      bool ssl = configuration_.GetBooleanValue("Ssl", false);
+      assert(configuration_.get() != NULL);
+      std::string host = configuration_->GetStringValue("Host", "");
+      bool https = configuration_->GetBooleanValue("Ssl", false);
+
+      std::string forwarded;
+      if (host.empty() &&
+          LookupHttpHeader2(forwarded, headers, "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"))
+          !LookupHttpHeader2(host, headers, "host"))
       {
         // Should never happen: The "host" header should always be present
         // in HTTP requests. Provide a default value anyway.
         host = "localhost:8042";
       }
 
-      return (ssl ? "https://" : "http://") + host + GetRoot();
+      return (https ? "https://" : "http://") + host + GetDicomWebRoot();
+    }
+
+
+    std::string GetBaseUrl(const OrthancPluginHttpRequest* request)
+    {
+      OrthancPlugins::HttpClient::HttpHeaders headers;
+
+      std::string value;
+      if (LookupHttpHeader(value, request, "forwarded"))
+      {
+        headers["Forwarded"] = value;
+      }
+
+      if (LookupHttpHeader(value, request, "host"))
+      {
+        headers["Host"] = value;
+      }
+
+      return GetBaseUrl(headers);
     }
 
 
@@ -360,27 +561,155 @@
     }
 
 
-    void LogError(const std::string& message)
-    {
-      OrthancPluginLogError(GetContext(), message.c_str());
-    }
-
-
-    void LogWarning(const std::string& message)
-    {
-      OrthancPluginLogWarning(GetContext(), message.c_str());
-    }
-
-
-    void LogInfo(const std::string& message)
-    {
-      OrthancPluginLogInfo(GetContext(), message.c_str());
-    }
-
-
     Orthanc::Encoding GetDefaultEncoding()
     {
       return defaultEncoding_;
     }
+
+
+    static bool IsXmlExpected(const std::string& acceptHeader)
+    {
+      std::string accept;
+      Orthanc::Toolbox::ToLowerCase(accept, acceptHeader);
+  
+      if (accept == "application/dicom+json" ||
+          accept == "application/json" ||
+          accept == "*/*")
+      {
+        return false;
+      }
+      else if (accept == "application/dicom+xml" ||
+               accept == "application/xml" ||
+               accept == "text/xml")
+      {
+        return true;
+      }
+      else
+      {
+        OrthancPlugins::LogError("Unsupported return MIME type: " + accept +
+                                 ", will return DICOM+JSON");
+        return false;
+      }
+    }
+
+
+    bool IsXmlExpected(const std::map<std::string, std::string>& headers)
+    {
+      std::map<std::string, std::string>::const_iterator found = headers.find("accept");
+
+      if (found == headers.end())
+      {
+        return false;   // By default, return DICOM+JSON
+      }
+      else
+      {
+        return IsXmlExpected(found->second);
+      }
+    }
+
+
+    bool IsXmlExpected(const OrthancPluginHttpRequest* request)
+    {
+      std::string accept;
+
+      if (OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
+      {
+        return IsXmlExpected(accept);
+      }
+      else
+      {
+        return false;   // By default, return DICOM+JSON
+      }
+    }
+
+    
+    MetadataMode GetMetadataMode(Orthanc::ResourceType level)
+    {
+      static const std::string FULL = "Full";
+      static const std::string MAIN_DICOM_TAGS = "MainDicomTags";
+      static const std::string EXTRAPOLATE = "Extrapolate";
+      
+      std::string key;
+      switch (level)
+      {
+        case Orthanc::ResourceType_Study:
+          key = "StudiesMetadata";
+          break;
+
+        case Orthanc::ResourceType_Series:
+          key = "SeriesMetadata";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      std::string value = GetStringValue(key, FULL);
+
+      if (value == FULL)
+      {
+        return MetadataMode_Full;
+      }
+      else if (value == MAIN_DICOM_TAGS)
+      {
+        return MetadataMode_MainDicomTags;
+      }
+      else if (value == EXTRAPOLATE)
+      {
+        return MetadataMode_Extrapolate;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Bad value for option \"" + key +
+                                        "\": Should be either \"" + FULL + "\" or \"" +
+                                        MAIN_DICOM_TAGS + "\" or \"" + EXTRAPOLATE + "\"");
+      }
+    }
+
+
+    void GetSetOfTags(std::set<Orthanc::DicomTag>& tags,
+                      const std::string& key)
+    {
+      tags.clear();
+
+      std::list<std::string> s;
+      
+      if (configuration_->LookupListOfStrings(s, key, false))
+      {
+        for (std::list<std::string>::const_iterator it = s.begin(); it != s.end(); ++it)
+        {
+          OrthancPluginDictionaryEntry entry;
+          if (OrthancPluginLookupDictionary(GetGlobalContext(), &entry, it->c_str()) == OrthancPluginErrorCode_Success)
+          {
+            tags.insert(Orthanc::DicomTag(entry.group, entry.element));
+          }
+          else
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                            "Unknown DICOM tag in option \"" + key + "\" of DICOMweb: " + *it);
+          }
+        }
+      }
+    }
+
+
+    void GetExtrapolatedMetadataTags(std::set<Orthanc::DicomTag>& tags,
+                                     Orthanc::ResourceType level)
+    {
+      switch (level)
+      {
+        case Orthanc::ResourceType_Study:
+          GetSetOfTags(tags, "StudiesMetadataExtrapolatedTags");
+          break;
+
+        case Orthanc::ResourceType_Series:
+          GetSetOfTags(tags, "SeriesMetadataExtrapolatedTags");
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
   }
 }
--- a/Plugin/Configuration.h	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/Configuration.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,7 +21,8 @@
 
 #pragma once
 
-#include "../Orthanc/Core/Enumerations.h"
+#include <Core/DicomFormat/DicomTag.h>
+#include <Core/Enumerations.h>
 
 #include <orthanc/OrthancCPlugin.h>
 #include <json/value.h>
@@ -35,13 +37,22 @@
 
 namespace OrthancPlugins
 {
-  struct MultipartItem
+  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);
+
+  enum MetadataMode
   {
-    const char*   data_;
-    size_t        size_;
-    std::string   contentType_;
+    MetadataMode_Full,           // Read all the DICOM instances from the storage area
+    MetadataMode_MainDicomTags,  // Only use the Orthanc database (main DICOM tags only)
+    MetadataMode_Extrapolate     // Extrapolate user-specified tags from a few DICOM instances
   };
 
+
   bool LookupHttpHeader(std::string& value,
                         const OrthancPluginHttpRequest* request,
                         const std::string& header);
@@ -50,35 +61,57 @@
                         std::map<std::string, std::string>& attributes,
                         const std::string& header);
 
-  void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          OrthancPluginContext* context,
-                          const char* body,
-                          const uint64_t bodySize,
-                          const std::string& boundary);
-
   void ParseAssociativeArray(std::map<std::string, std::string>& target,
                              const Json::Value& value,
                              const std::string& key);
 
+  void ParseAssociativeArray(std::map<std::string, std::string>& target,
+                             const Json::Value& value);
+
+  bool ParseTag(Orthanc::DicomTag& target,
+                const std::string& name);
+
+  void ParseJsonBody(Json::Value& target,
+                     const OrthancPluginHttpRequest* request);
+
+  std::string RemoveMultipleSlashes(const std::string& source);
+
+  bool LookupStringValue(std::string& target,
+                         const Json::Value& json,
+                         const std::string& key);
+
+  bool LookupIntegerValue(int& target,
+                          const Json::Value& json,
+                          const std::string& key);
+
+  bool LookupBooleanValue(bool& target,
+                          const Json::Value& json,
+                          const std::string& key);
+
   namespace Configuration
   {
-    void Initialize(OrthancPluginContext* context);
+    void Initialize();
 
-    OrthancPluginContext* GetContext();
+    bool HasKey(const std::string& key);
     
-    std::string GetStringValue(const std::string& key,
-                               const std::string& defaultValue);
-
     bool GetBooleanValue(const std::string& key,
                          bool defaultValue);
 
+    bool LookupBooleanValue(bool& target,
+                            const std::string& key);
+
     unsigned int GetUnsignedIntegerValue(const std::string& key,
                                          unsigned int defaultValue);
 
-    std::string GetRoot();
+    std::string GetDicomWebRoot();
+
+    std::string GetOrthancApiRoot();
 
     std::string GetWadoRoot();
       
+    std::string GetBaseUrl(const std::map<std::string, std::string>& headers);
+
+    // TODO => REMOVE
     std::string GetBaseUrl(const OrthancPluginHttpRequest* request);
 
     std::string GetWadoUrl(const std::string& wadoBase,
@@ -86,12 +119,19 @@
                            const std::string& seriesInstanceUid,
                            const std::string& sopInstanceUid);
 
-    void LogError(const std::string& message);
+    Orthanc::Encoding GetDefaultEncoding();
 
-    void LogWarning(const std::string& message);
+    bool IsXmlExpected(const std::map<std::string, std::string>& headers);
+
+    // TODO => REMOVE
+    bool IsXmlExpected(const OrthancPluginHttpRequest* request);
 
-    void LogInfo(const std::string& message);
+    MetadataMode GetMetadataMode(Orthanc::ResourceType level);
 
-    Orthanc::Encoding GetDefaultEncoding();
+    void GetSetOfTags(std::set<Orthanc::DicomTag>& tags,
+                      const std::string& key);
+
+    void GetExtrapolatedMetadataTags(std::set<Orthanc::DicomTag>& tags,
+                                     Orthanc::ResourceType level);
   }
 }
--- a/Plugin/Dicom.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,755 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Dicom.h"
-
-#include "Plugin.h"
-#include "ChunkedBuffer.h"
-
-#include "../Orthanc/Core/Toolbox.h"
-
-#include <gdcmDictEntry.h>
-#include <gdcmStringFilter.h>
-#include <boost/lexical_cast.hpp>
-#include <json/writer.h>
-
-namespace OrthancPlugins
-{
-  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::Dict& dictionary,
-                               const gdcm::Tag& tag,
-                               gdcm::VR vr)
-  {
-    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;
-    }
-  }
-
-
-  const char* GetVRName(bool& isSequence,
-                        const gdcm::Dict& dictionary,
-                        const gdcm::Tag& tag)
-  {
-    return GetVRName(isSequence, dictionary, tag, gdcm::VR::INVALID);
-  }
-
-
-  static const char* GetVRName(bool& isSequence,
-                               const gdcm::Dict& dictionary,
-                               const gdcm::DataElement& element)
-  {
-    return GetVRName(isSequence, dictionary, element.GetTag(), element.GetVR());
-  }
-
-
-  static bool ConvertDicomStringToUtf8(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 = MyStripSpaces(result);
-    return true;
-  }
-
-
-
-  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())
-    {
-      OrthancPlugins::Configuration::LogError("GDCM cannot decode this DICOM instance of length " +
-                                              boost::lexical_cast<std::string>(dicom.size()));
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-    }
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(const OrthancPlugins::MultipartItem& item)
-  {
-    // TODO Avoid this unnecessary memcpy by defining a stream over the MultipartItem
-    std::string dicom(item.data_, item.data_ + item.size_);
-    Setup(dicom);
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(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 ParsedDicomFile::GetRawTag(std::string& result,
-                                  const gdcm::Tag& tag,
-                                  bool stripSpaces) const
-  {
-    return OrthancPlugins::GetRawTag(result, GetDataSet(), tag, stripSpaces);
-  }
-
-
-  std::string ParsedDicomFile::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;
-    }
-  }
-
-
-  bool ParsedDicomFile::GetStringTag(std::string& result,
-                                     const gdcm::Dict& dictionary,
-                                     const gdcm::Tag& tag,
-                                     bool stripSpaces) const
-  {
-    if (!GetDataSet().FindDataElement(tag))
-    {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InexistentTag);
-    }
-
-    const gdcm::DataElement& element = GetDataSet().GetDataElement(tag);
-
-    if (!ConvertDicomStringToUtf8(result, dictionary, &GetFile(), element, GetEncoding()))
-    {
-      return false;
-    }
-
-    if (stripSpaces)
-    {
-      result = MyStripSpaces(result);
-    }
-
-    return true;
-  }
-
-
-  bool ParsedDicomFile::GetIntegerTag(int& result,
-                                      const gdcm::Dict& dictionary,
-                                      const gdcm::Tag& tag) const
-  {
-    std::string tmp;
-    if (!GetStringTag(tmp, dictionary, 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);
-  }
-
-
-  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";
-    }
-
-    return NULL;
-  }
-
-
-
-  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 GetWadoUrl(const std::string& wadoBase,
-                                const gdcm::DataSet& dicom)
-  {
-    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)
-  {
-    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  ParsedDicomFile::GetEncoding() const
-  {
-    return DetectEncoding(GetDataSet());
-  }
-  
-
-
-  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());
-
-      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());
-
-      const char* keyword = GetKeyword(dictionary, it->GetTag());
-      if (keyword != NULL)
-      {
-        node.append_attribute("keyword").set_value(keyword);
-      }
-
-      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 (ConvertDicomStringToUtf8(tmp, dictionary, file, *it, sourceEncoding)) 
-        {
-          value.append_child(pugi::node_pcdata).set_value(tmp.c_str());
-        }
-        else
-        {
-          value.append_child(pugi::node_pcdata).set_value("");
-        }
-      }
-    }
-  }
-
-
-  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();
-
-      bool ok = true;
-      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);
-          }
-        }
-
-        ok = true;
-      }
-      else if (IsBulkData(vr))
-      {
-        // Bulk data
-        if (!bulkUri.empty())
-        {
-          node["BulkDataURI"] = bulkUri + std::string(path);
-          ok = true;
-        }
-      }
-      else
-      {
-        // Deal with other value representations
-        node["Value"] = Json::arrayValue;
-
-        std::string value;
-        if (ConvertDicomStringToUtf8(value, dictionary, file, *it, sourceEncoding)) 
-        {
-          node["Value"].append(value.c_str());
-        }
-        else
-        {
-          node["Value"].append("");
-        }
-
-        ok = true;
-      }
-
-      if (ok)
-      {
-        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 = GetWadoUrl(wadoBase, dicom) + "bulk/";
-    }
-
-    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");
-  }
-
-
-  std::string ParsedDicomFile::GetWadoUrl(const OrthancPluginHttpRequest* request) const
-  {
-    const std::string base = OrthancPlugins::Configuration::GetBaseUrl(request);
-    return OrthancPlugins::GetWadoUrl(base, GetDataSet());
-  }
-
-
-  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]));
-  }
-
-
-  gdcm::Tag ParseTag(const gdcm::Dict& dictionary,
-                     const std::string& key)
-  {
-    if (key.find('.') != std::string::npos)
-    {
-      OrthancPlugins::Configuration::LogError("This DICOMweb plugin does not support hierarchical queries: " + key);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotImplemented);
-    }
-
-    if (key.size() == 8 &&  // This is the DICOMweb convention
-        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 if (key.size() == 9 &&  // This is the Orthanc convention
-             isxdigit(key[0]) &&
-             isxdigit(key[1]) &&
-             isxdigit(key[2]) &&
-             isxdigit(key[3]) &&
-             key[4] == ',' &&
-             isxdigit(key[5]) &&
-             isxdigit(key[6]) &&
-             isxdigit(key[7]) &&
-             isxdigit(key[8]))        
-    {
-      return gdcm::Tag(GetTagValue(key.c_str()),
-                       GetTagValue(key.c_str() + 5));
-    }
-    else
-    {
-      gdcm::Tag tag;
-      dictionary.GetDictEntryByKeyword(key.c_str(), tag);
-
-      if (tag.IsIllegal() || tag.IsPrivate())
-      {
-        if (key.find('.') != std::string::npos)
-        {
-          OrthancPlugins::Configuration::LogError("This QIDO-RS implementation does not support search over sequences: " + key);
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotImplemented);
-        }
-        else
-        {
-          OrthancPlugins::Configuration::LogError("Illegal tag name in QIDO-RS: " + key);
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownDicomTag);
-        }
-      }
-
-      return tag;
-    }
-  }
-}
--- a/Plugin/Dicom.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Configuration.h"
-
-#include "../Orthanc/Core/ChunkedBuffer.h"
-#include "../Orthanc/Core/Enumerations.h"
-#include "../Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.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);
-  static const gdcm::Tag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
-  static const gdcm::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011);
-  static const gdcm::Tag DICOM_TAG_ROWS(0x0028, 0x0010);
-  static const gdcm::Tag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
-
-  class ParsedDicomFile
-  {
-  private:
-    gdcm::Reader reader_;
-
-    void Setup(const std::string& dicom);
-
-  public:
-    ParsedDicomFile(const OrthancPlugins::MultipartItem& item);
-
-    ParsedDicomFile(const OrthancPlugins::MemoryBuffer& 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 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;
-
-    bool GetStringTag(std::string& result,
-                      const gdcm::Dict& dictionary,
-                      const gdcm::Tag& tag,
-                      bool stripSpaces) const;
-
-    bool GetIntegerTag(int& result,
-                       const gdcm::Dict& dictionary,
-                       const gdcm::Tag& tag) const;
-
-    Orthanc::Encoding  GetEncoding() const;
-
-    std::string GetWadoUrl(const OrthancPluginHttpRequest* request) const;
-  };
-
-
-  const char* GetVRName(bool& isSequence /* out */,
-                        const gdcm::Dict& dictionary,
-                        const gdcm::Tag& tag);
-
-  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);
-
-  gdcm::Tag ParseTag(const gdcm::Dict& dictionary,
-                     const std::string& key);
-
-  std::string FormatTag(const gdcm::Tag& tag);
-
-  const char* GetKeyword(const gdcm::Dict& dictionary,
-                         const gdcm::Tag& tag);
-
-  class ChunkedBufferWriter : public pugi::xml_writer
-  {
-  private:
-    Orthanc::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);
-    }
-  };
-}
--- a/Plugin/DicomResults.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,425 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomResults.h"
-
-#include "Dicom.h"
-#include "../Orthanc/Core/Toolbox.h"
-#include "../Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
-
-#include <boost/lexical_cast.hpp>
-#include <boost/noncopyable.hpp>
-
-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)
-    {
-      OrthancPlugins::Configuration::LogError("Unable to create a multipart stream of DICOM+XML answers");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
-    }
-
-    jsonWriter_.AddChunk("[\n");
-  }
-
-
-  void DicomResults::AddInternal(const std::string& item)
-  {
-    if (isXml_)
-    {
-      if (OrthancPluginSendMultipartItem(context_, output_, item.c_str(), item.size()) != 0)
-      {
-        OrthancPlugins::Configuration::LogError("Unable to create a multipart stream of DICOM+XML answers");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
-      }
-    }
-    else
-    {
-      if (!isFirst_)
-      {
-        jsonWriter_.AddChunk(",\n");
-      }
-
-      jsonWriter_.AddChunk(item);
-    }
-
-    isFirst_ = false;
-  }
-
-
-  void DicomResults::AddInternal(const gdcm::File* file,
-                                 const gdcm::DataSet& dicom)
-  {
-    std::string item;
-
-    if (isXml_)
-    {
-      GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, true, isBulkAccessible_);
-    }
-    else
-    {
-      GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, false, isBulkAccessible_);
-    }
-
-    AddInternal(item);
-
-    isFirst_ = false;
-  }
-
-
-
-  namespace
-  {
-    class ITagVisitor : public boost::noncopyable
-    {
-    public:
-      virtual ~ITagVisitor()
-      {
-      }
-
-      virtual void Visit(const gdcm::Tag& tag,
-                         bool isSequence,
-                         const std::string& vr,
-                         const std::string& type,
-                         const Json::Value& value) = 0;
-
-      static void Apply(ITagVisitor& visitor,
-                        const Json::Value& source,
-                        const gdcm::Dict& dictionary)
-      {
-        if (source.type() != Json::objectValue)
-        {
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
-        }
-
-        Json::Value::Members members = source.getMemberNames();
-        for (size_t i = 0; i < members.size(); i++)
-        {
-          if (members[i].size() != 9 ||
-              members[i][4] != ',' ||
-              source[members[i]].type() != Json::objectValue ||
-              !source[members[i]].isMember("Value") ||
-              !source[members[i]].isMember("Type") ||
-              source[members[i]]["Type"].type() != Json::stringValue)
-          {
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
-          }        
-
-          const Json::Value& value = source[members[i]]["Value"];
-          const std::string type = source[members[i]]["Type"].asString();
-
-          gdcm::Tag tag(OrthancPlugins::ParseTag(dictionary, members[i]));
-
-          bool isSequence = false;
-          std::string vr = GetVRName(isSequence, dictionary, tag);
-
-          if (tag == DICOM_TAG_RETRIEVE_URL)
-          {
-            // The VR of this attribute has changed from UT to UR.
-            vr = "UR";
-          }
-          else
-          {
-            vr = GetVRName(isSequence, dictionary, tag);
-          }
-
-          visitor.Visit(tag, isSequence, vr, type, value);
-        }
-      }
-    };
-
-
-    class TagVisitorBase : public ITagVisitor
-    {
-    protected:
-      const Json::Value&  source_;
-      const gdcm::Dict&   dictionary_;
-      const std::string&  bulkUri_;
-
-    public:
-      TagVisitorBase(const Json::Value&  source,
-                     const gdcm::Dict&   dictionary,
-                     const std::string&  bulkUri) :
-        source_(source),
-        dictionary_(dictionary),
-        bulkUri_(bulkUri)
-      {
-      }
-    };
-
-
-    class JsonVisitor : public TagVisitorBase
-    {
-    private:
-      Json::Value&   target_;
-
-    public:
-      JsonVisitor(Json::Value&        target,
-                  const Json::Value&  source,
-                  const gdcm::Dict&   dictionary,
-                  const std::string&  bulkUri) :
-        TagVisitorBase(source, dictionary, bulkUri),
-        target_(target)
-      {
-        target_ = Json::objectValue;
-      }
-
-      virtual void Visit(const gdcm::Tag& tag,
-                         bool isSequence,
-                         const std::string& vr,
-                         const std::string& type,
-                         const Json::Value& value)
-      {
-        const std::string formattedTag = OrthancPlugins::FormatTag(tag);
-
-        Json::Value node = Json::objectValue;
-        node["vr"] = vr;
-
-        bool ok = false;
-        if (isSequence)
-        {
-          // Deal with sequences
-          if (type != "Sequence" ||
-              value.type() != Json::arrayValue)
-          {
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
-          }
-
-          node["Value"] = Json::arrayValue;
-
-          for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
-          {
-            if (value[i].type() != Json::objectValue)
-            {
-              throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
-            }
-
-            Json::Value child;
-
-            std::string childUri;
-            if (!bulkUri_.empty())
-            {
-              std::string number = boost::lexical_cast<std::string>(i);
-              childUri = bulkUri_ + formattedTag + "/" + number + "/";
-            }
-
-            JsonVisitor visitor(child, value[i], dictionary_, childUri);
-            JsonVisitor::Apply(visitor, value[i], dictionary_);
-
-            node["Value"].append(child);
-          }
-
-          ok = true;
-        }
-        else if (type == "String" &&
-                 value.type() == Json::stringValue)
-        {
-          // Deal with string representations
-          node["Value"] = Json::arrayValue;
-          node["Value"].append(value.asString());
-          ok = true;
-        }
-        else
-        {
-          // Bulk data
-          if (!bulkUri_.empty())
-          {
-            node["BulkDataURI"] = bulkUri_ + formattedTag;
-            ok = true;
-          }
-        }
-
-        if (ok)
-        {
-          target_[formattedTag] = node;
-        }
-      }
-    };
-
-
-    class XmlVisitor : public TagVisitorBase
-    {
-    private:
-      pugi::xml_node&  target_;
-
-    public:
-      XmlVisitor(pugi::xml_node&     target,
-                 const Json::Value&  source,
-                 const gdcm::Dict&   dictionary,
-                 const std::string&  bulkUri) :
-        TagVisitorBase(source, dictionary, bulkUri),
-        target_(target)
-      {
-      }
-
-      virtual void Visit(const gdcm::Tag& tag,
-                         bool isSequence,
-                         const std::string& vr,
-                         const std::string& type,
-                         const Json::Value& value)
-      {
-        const std::string formattedTag = OrthancPlugins::FormatTag(tag);
-
-        pugi::xml_node node = target_.append_child("DicomAttribute");
-        node.append_attribute("tag").set_value(formattedTag.c_str());
-        node.append_attribute("vr").set_value(vr.c_str());
-
-        const char* keyword = GetKeyword(dictionary_, tag);
-        if (keyword != NULL)
-        {
-          node.append_attribute("keyword").set_value(keyword);
-        }
-
-        if (isSequence)
-        {
-          // Deal with sequences
-          if (type != "Sequence" ||
-              value.type() != Json::arrayValue)
-          {
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
-          }
-
-          for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
-          {
-            if (value[i].type() != Json::objectValue)
-            {
-              throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
-            }
-
-            pugi::xml_node child = node.append_child("Item");
-            std::string number = boost::lexical_cast<std::string>(i + 1);
-            child.append_attribute("number").set_value(number.c_str());
-
-            std::string childUri;
-            if (!bulkUri_.empty())
-            {
-              childUri = bulkUri_ + formattedTag + "/" + number + "/";
-            }
-
-            XmlVisitor visitor(child, value[i], dictionary_, childUri);
-            XmlVisitor::Apply(visitor, value[i], dictionary_);
-          }
-        }
-        else if (type == "String" &&
-                 value.type() == Json::stringValue)
-        {
-          // Deal with string representations
-          pugi::xml_node item = node.append_child("Value");
-          item.append_attribute("number").set_value("1");
-          item.append_child(pugi::node_pcdata).set_value(value.asCString());
-        }
-        else
-        {
-          // Bulk data
-          if (!bulkUri_.empty())
-          {
-            pugi::xml_node value = node.append_child("BulkData");
-            std::string uri = bulkUri_ + formattedTag;
-            value.append_attribute("uri").set_value(uri.c_str());
-          }
-        }
-      }
-    };
-  }
-
-
-  static void OrthancToDicomWebXml(pugi::xml_document& target,
-                                   const Json::Value& source,
-                                   const gdcm::Dict& dictionary,
-                                   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");
-
-    XmlVisitor visitor(root, source, dictionary, bulkUriRoot);
-    ITagVisitor::Apply(visitor, source, dictionary);
-
-    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");
-  }
-
-
-  void DicomResults::AddFromOrthanc(const Json::Value& dicom,
-                                    const std::string& wadoUrl)
-  { 
-    std::string bulkUriRoot;
-    if (isBulkAccessible_)
-    {
-      bulkUriRoot = wadoUrl + "bulk/";
-    }
-
-    if (isXml_)
-    {
-      pugi::xml_document doc;
-      OrthancToDicomWebXml(doc, dicom, dictionary_, bulkUriRoot);
-    
-      ChunkedBufferWriter writer;
-      doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
-
-      std::string item;
-      writer.Flatten(item);
-
-      AddInternal(item);
-    }
-    else
-    {
-      Json::Value v;
-      JsonVisitor visitor(v, dicom, dictionary_, bulkUriRoot);
-      ITagVisitor::Apply(visitor, dicom, dictionary_);
-
-      Json::FastWriter writer;
-      AddInternal(writer.write(v));
-    }
-  }
-
-
-  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	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ChunkedBuffer.h"
-
-#include <orthanc/OrthancCPlugin.h>
-#include <gdcmDataSet.h>
-#include <gdcmDict.h>
-#include <gdcmFile.h>
-#include <json/value.h>
-
-namespace OrthancPlugins
-{
-  class DicomResults
-  {
-  private:
-    OrthancPluginContext*     context_;
-    OrthancPluginRestOutput*  output_;
-    std::string               wadoBase_;
-    const gdcm::Dict&         dictionary_;
-    Orthanc::ChunkedBuffer    jsonWriter_;  // Used for JSON output
-    bool                      isFirst_; 
-    bool                      isXml_;
-    bool                      isBulkAccessible_;
-
-    void AddInternal(const std::string& item);
-
-    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 AddFromOrthanc(const Json::Value& dicom,
-                        const std::string& wadoUrl);
-
-    void Answer();
-  };
-}
--- a/Plugin/DicomWebClient.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/DicomWebClient.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,7 +21,6 @@
 
 #include "DicomWebClient.h"
 
-#include "Plugin.h"
 #include "DicomWebServers.h"
 
 #include <json/reader.h>
@@ -28,22 +28,364 @@
 #include <set>
 #include <boost/lexical_cast.hpp>
 
-#include "../Orthanc/Core/ChunkedBuffer.h"
-#include "../Orthanc/Core/Toolbox.h"
+#include <Core/HttpServer/MultipartStreamReader.h>
+#include <Core/ChunkedBuffer.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+
+#include <boost/thread.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+
+static const std::string HAS_WADO_RS_UNIVERSAL_TRANSFER_SYNTAX = "HasWadoRsUniversalTransferSyntax";
+        
+
+class SingleFunctionJob : public OrthancPlugins::OrthancJob
+{
+public:
+  class JobContext : public boost::noncopyable
+  {
+  private:
+    SingleFunctionJob&  that_;
+
+  public:
+    explicit JobContext(SingleFunctionJob& that) :
+      that_(that)
+    {
+    }
+
+    void SetContent(const std::string& key,
+                    const std::string& value)
+    {
+      that_.SetContent(key, value);
+    }
+
+    void SetProgress(unsigned int position,
+                     unsigned int maxPosition)
+    {
+      boost::mutex::scoped_lock lock(that_.mutex_);
+
+      if (maxPosition == 0 || 
+          position > maxPosition)
+      {
+        that_.UpdateProgress(1);
+      }
+      else
+      {
+        that_.UpdateProgress(static_cast<float>(position) / static_cast<float>(maxPosition));
+      }
+    }
+  };
+
+
+  class IFunction : public boost::noncopyable
+  {
+  public:
+    virtual ~IFunction()
+    {
+    }
+
+    virtual void Execute(JobContext& context) = 0;
+  };
+
+
+  class IFunctionFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IFunctionFactory()
+    {
+    }
+
+    // Called when the job is paused or canceled. WARNING:
+    // "CancelFunction()" will be invoked while "Execute()" is
+    // running. Mutex is probably necessary.
+    virtual void CancelFunction() = 0;
+
+    virtual void PauseFunction() = 0;
+
+    virtual IFunction* CreateFunction() = 0;
+  };
+
+
+protected:
+  void SetFactory(IFunctionFactory& factory)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (factory_ != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      factory_ = &factory;
+    }
+  }
+  
+
+private:
+  enum FunctionResult
+  {
+    FunctionResult_Running,
+    FunctionResult_Done,
+    FunctionResult_Failure
+  };
+
+  boost::mutex                  mutex_;
+  FunctionResult                functionResult_;  // Can only be modified by the "Worker()" function
+  std::auto_ptr<boost::thread>  worker_;
+  Json::Value                   content_;
+  IFunctionFactory*             factory_;
+  bool                          stopping_;
+
+  void JoinWorker()
+  {
+    assert(factory_ != NULL);
+
+    if (worker_.get() != NULL)
+    {
+      if (worker_->joinable())
+      {
+        worker_->join();
+      }
+
+      worker_.reset();
+    }
+  }
+
+  void StartWorker()
+  {
+    assert(factory_ != NULL);
+
+    if (worker_.get() == NULL)
+    {
+      stopping_ = false;
+      worker_.reset(new boost::thread(Worker, this, factory_));
+    }
+  }
+
+  void SetContent(const std::string& key,
+                  const std::string& value)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    content_[key] = value;
+    UpdateContent(content_);
+  }
+
+  static void Worker(SingleFunctionJob* job,
+                     IFunctionFactory* factory)
+  {
+    assert(job != NULL && factory != NULL);
+
+    JobContext context(*job);
+
+    try
+    {
+      std::auto_ptr<IFunction> function(factory->CreateFunction());
+      function->Execute(context);
+
+      {
+        boost::mutex::scoped_lock lock(job->mutex_);
+        job->functionResult_ = FunctionResult_Done;
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Error in a job: " << e.What();
+
+      {
+        boost::mutex::scoped_lock lock(job->mutex_);
+
+        job->functionResult_ = FunctionResult_Failure;
+
+        if (!job->stopping_)
+        {
+          // Don't report exceptions that are a consequence of stopping the function
+          job->content_["FunctionErrorCode"] = e.GetErrorCode();
+          job->content_["FunctionErrorDescription"] = e.What();
+          if (e.HasDetails())
+          {
+            job->content_["FunctionErrorDetails"] = e.GetDetails();
+          }
+          job->UpdateContent(job->content_);
+        }
+      }
+    }
+  }  
+
+public:
+  explicit SingleFunctionJob(const std::string& jobName) :
+    OrthancJob(jobName),
+    functionResult_(FunctionResult_Running),
+    content_(Json::objectValue),
+    factory_(NULL),
+    stopping_(false)
+  {
+  }
+
+  virtual ~SingleFunctionJob()
+  {
+    if (worker_.get() != NULL)
+    {
+      LOG(ERROR) << "Classes deriving from SingleFunctionJob must "
+                 << "explicitly call Finalize() in their destructor";
+      Finalize();
+    }
+  }
+
+  void Finalize()
+  {
+    try
+    {
+      Stop(OrthancPluginJobStopReason_Canceled);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+    }
+  }
+
+  virtual OrthancPluginJobStepStatus Step()
+  {
+    if (factory_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    FunctionResult result;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      result = functionResult_;
+    }
+
+    switch (result)
+    {
+      case FunctionResult_Running:
+        StartWorker();
+        boost::this_thread::sleep(boost::posix_time::milliseconds(500));
+        return OrthancPluginJobStepStatus_Continue;
+
+      case FunctionResult_Done:
+        JoinWorker();
+        return OrthancPluginJobStepStatus_Success;
+
+      case FunctionResult_Failure:
+        JoinWorker();
+        return OrthancPluginJobStepStatus_Failure;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  virtual void Stop(OrthancPluginJobStopReason reason)
+  {
+    if (factory_ == NULL)
+    {
+      return;
+    }
+    else if (reason == OrthancPluginJobStopReason_Paused ||
+             reason == OrthancPluginJobStopReason_Canceled)
+    {
+      stopping_ = true;
+
+      if (reason == OrthancPluginJobStopReason_Paused)
+      {
+        factory_->PauseFunction();
+      }
+      else
+      {
+        factory_->CancelFunction();
+      }
+
+      JoinWorker();
+
+      // Be ready for the next possible call to "Step()" that will resume the function
+      functionResult_ = FunctionResult_Running;
+    }
+  }
+
+  virtual void Reset()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    assert(worker_.get() == NULL);
+    functionResult_ = FunctionResult_Running;
+    content_ = Json::objectValue;
+    ClearContent();
+  }
+};
+
+
+
+
+static const std::string MULTIPART_RELATED = "multipart/related";
+
+
+
+static void SubmitJob(OrthancPluginRestOutput* output,
+                      OrthancPlugins::OrthancJob* job,
+                      const Json::Value& body,
+                      bool defaultSynchronous)
+{
+  std::auto_ptr<OrthancPlugins::OrthancJob> protection(job);
+
+  bool synchronous;
+
+  bool b;
+  if (OrthancPlugins::LookupBooleanValue(b, body, "Synchronous"))
+  {
+    synchronous = b;
+  }
+  else if (OrthancPlugins::LookupBooleanValue(b, body, "Asynchronous"))
+  {
+    synchronous = !b;
+  }
+  else
+  {
+    synchronous = defaultSynchronous;
+  }
+
+  int priority;
+  if (!OrthancPlugins::LookupIntegerValue(priority, body, "Priority"))
+  {
+    priority = 0;
+  }
+
+  Json::Value answer;
+
+  if (synchronous)
+  {
+    OrthancPlugins::OrthancJob::SubmitAndWait(answer, protection.release(), priority);
+  }
+  else
+  {
+    std::string jobId = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
+
+    answer = Json::objectValue;
+    answer["ID"] = jobId;
+    answer["Path"] = OrthancPlugins::RemoveMultipleSlashes
+      ("../" + OrthancPlugins::Configuration::GetOrthancApiRoot() + "/jobs/" + jobId);
+  }
+
+  std::string s = answer.toStyledString();
+  OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(),
+                            output, s.c_str(), s.size(), "application/json");    
+}
 
 
 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)
+  std::string id;
+  if (OrthancPlugins::LookupStringValue(id, instance, "ID"))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    target.push_back(id);
   }
   else
   {
-    target.push_back(instance["ID"].asString());
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 }
 
@@ -70,9 +412,10 @@
   }
   else if (isMandatory)
   {
-    OrthancPlugins::Configuration::LogError("The STOW-RS JSON response from DICOMweb server " + server + 
-                                            " does not contain the mandatory tag " + upper);
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "The STOW-RS JSON response from DICOMweb server " + server + 
+      " does not contain the mandatory tag " + upper);
   }
   else
   {
@@ -80,76 +423,126 @@
   }
 
   if (value->type() != Json::objectValue ||
-      !value->isMember("Value") ||
-      (*value) ["Value"].type() != Json::arrayValue)
+      (value->isMember("Value") &&
+       (*value) ["Value"].type() != Json::arrayValue))
   {
-    OrthancPlugins::Configuration::LogError("Unable to parse STOW-RS JSON response from DICOMweb server " + server);
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "Unable to parse STOW-RS JSON response from DICOMweb server " + server);
   }
 
-  result = (*value) ["Value"].size();
+  if (value->isMember("Value"))
+  {
+    result = (*value) ["Value"].size();
+  }
+  else
+  {
+    result = 0;
+  }
+
   return true;
 }
 
 
 
+static void CheckStowAnswer(const Json::Value& response,
+                            const std::string& serverName,
+                            size_t instancesCount)
+{
+  if (response.type() != Json::objectValue ||
+      !response.isMember("00081199"))
+  {
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_NetworkProtocol,
+      "Unable to parse STOW-RS JSON response from DICOMweb server " + serverName);
+  }
+
+  size_t size;
+  if (!GetSequenceSize(size, response, "00081199", true, serverName) ||
+      size != instancesCount)
+  {
+    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>(instancesCount));
+  }
+
+  if (GetSequenceSize(size, response, "00081198", false, serverName) &&
+      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, serverName) &&
+      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");
+  }
+}
+
+
 static void ParseStowRequest(std::list<std::string>& instances /* out */,
                              std::map<std::string, std::string>& httpHeaders /* out */,
-                             const OrthancPluginHttpRequest* request /* in */)
+                             const Json::Value& body /* in */)
 {
   static const char* RESOURCES = "Resources";
   static const char* HTTP_HEADERS = "HttpHeaders";
 
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-  Json::Value body;
-  Json::Reader reader;
-  if (!reader.parse(request->body, request->body + request->bodySize, body) ||
-      body.type() != Json::objectValue ||
+  if (body.type() != Json::objectValue ||
       !body.isMember(RESOURCES) ||
       body[RESOURCES].type() != Json::arrayValue)
   {
-    OrthancPlugins::Configuration::LogError("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");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    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(httpHeaders, body, HTTP_HEADERS);
 
-  Json::Value& resources = body[RESOURCES];
+  const 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 OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
     std::string resource = resources[i].asString();
     if (resource.empty())
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownResource);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
     // Test whether this resource is an instance
     Json::Value tmp;
-    if (OrthancPlugins::RestApiGetJson(tmp, context, "/instances/" + resource, false))
+    if (OrthancPlugins::RestApiGet(tmp, "/instances/" + resource, false))
     {
       AddInstance(instances, tmp);
     }
     // This was not an instance, successively try with series/studies/patients
-    else if ((OrthancPlugins::RestApiGetJson(tmp, context, "/series/" + resource, false) &&
-              OrthancPlugins::RestApiGetJson(tmp, context, "/series/" + resource + "/instances", false)) ||
-             (OrthancPlugins::RestApiGetJson(tmp, context, "/studies/" + resource, false) &&
-              OrthancPlugins::RestApiGetJson(tmp, context, "/studies/" + resource + "/instances", false)) ||
-             (OrthancPlugins::RestApiGetJson(tmp, context, "/patients/" + resource, false) &&
-              OrthancPlugins::RestApiGetJson(tmp, context, "/patients/" + resource + "/instances", false)))
+    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 OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
       for (Json::Value::ArrayIndex j = 0; j < tmp.size(); j++)
@@ -159,93 +552,269 @@
     }
     else
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownResource);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }   
   }
 }
 
 
-static void SendStowChunks(const Orthanc::WebServiceParameters& server,
-                           const std::map<std::string, std::string>& httpHeaders,
-                           const std::string& boundary,
-                           Orthanc::ChunkedBuffer& chunks,
-                           size_t& countInstances,
-                           bool force)
+class StowClientJob :
+  public SingleFunctionJob,
+  private SingleFunctionJob::IFunctionFactory
 {
-  unsigned int maxInstances = OrthancPlugins::Configuration::GetUnsignedIntegerValue("StowMaxInstances", 10);
-  size_t maxSize = static_cast<size_t>(OrthancPlugins::Configuration::GetUnsignedIntegerValue("StowMaxSize", 10)) * 1024 * 1024;
+private:
+  enum Action
+  {
+    Action_None,
+    Action_Pause,
+    Action_Cancel
+  };
+  
+  boost::mutex                             mutex_;
+  std::string                              serverName_;
+  std::vector<std::string>                 instances_;
+  OrthancPlugins::HttpClient::HttpHeaders  headers_;
+  std::string                              boundary_;
+  size_t                                   position_;
+  Action                                   action_;
+  size_t                                   networkSize_;
+  bool                                     debug_;
+
+  bool ReadNextInstance(std::string& dicom,
+                        JobContext& context)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
 
-  if ((force && countInstances > 0) ||
-      (maxInstances != 0 && countInstances >= maxInstances) ||
-      (maxSize != 0 && chunks.GetNumBytes() >= maxSize))
-  {
-    chunks.AddChunk("\r\n--" + boundary + "--\r\n");
+    if (action_ != Action_None)
+    {
+      return false;
+    }
+
+    while (position_ < instances_.size())
+    {
+      context.SetProgress(position_, instances_.size());
+      
+      size_t i = position_++;
+
+      if (debug_)
+      {
+        boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+      }
+
+      if (OrthancPlugins::RestApiGetString(dicom, "/instances/" + instances_[i] + "/file", false))
+      {
+        networkSize_ += dicom.size();
+        context.SetContent("NetworkSizeMB", boost::lexical_cast<std::string>
+                           (networkSize_ / static_cast<uint64_t>(1024 * 1024)));
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+
 
-    std::string body;
-    chunks.Flatten(body);
+  class RequestBody : public OrthancPlugins::HttpClient::IRequestBody
+  {
+  private:
+    StowClientJob&  that_;
+    JobContext&     context_;
+    std::string     boundary_;
+    bool            done_;
 
-    OrthancPlugins::MemoryBuffer answerBody(OrthancPlugins::Configuration::GetContext());
-    std::map<std::string, std::string> answerHeaders;
-    OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Post,
-                               httpHeaders, "studies", body);
+  public:
+    RequestBody(StowClientJob& that,
+                JobContext& context) :
+      that_(that),
+      context_(context),
+      boundary_(that.boundary_),
+      done_(false)
+    {
+    }
+
+    virtual bool ReadNextChunk(std::string& chunk)
+    {
+      if (done_)
+      {
+        context_.SetProgress(1, 1);
+        return false;
+      }
+      else
+      {
+        std::string dicom;
 
-    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 (that_.ReadNextInstance(dicom, context_))
+        {
+          chunk = ("--" + boundary_ + "\r\n" +
+                   "Content-Type: application/dicom\r\n" +
+                   "Content-Length: " + boost::lexical_cast<std::string>(dicom.size()) + 
+                   "\r\n\r\n" + dicom + "\r\n");
+        }
+        else
+        {
+          done_ = true;
+          chunk = ("--" + boundary_ + "--");
+        }
+
+        //boost::this_thread::sleep(boost::posix_time::seconds(1));
 
-    if (!success ||
-        response.type() != Json::objectValue ||
-        !response.isMember("00081199"))
+        return true;
+      }
+    }
+  };
+
+
+  class F : public IFunction
+  {
+  private:
+    StowClientJob& that_;
+
+  public:
+    explicit F(StowClientJob& that) :
+      that_(that)
     {
-      OrthancPlugins::Configuration::LogError("Unable to parse STOW-RS JSON response from DICOMweb server " + server.GetUrl());
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
     }
 
-    size_t size;
-    if (!GetSequenceSize(size, response, "00081199", true, server.GetUrl()) ||
-        size != countInstances)
+    virtual void Execute(JobContext& context)
     {
-      OrthancPlugins::Configuration::LogError("The STOW-RS server was only able to receive " + 
-                                              boost::lexical_cast<std::string>(size) + " instances out of " +
-                                              boost::lexical_cast<std::string>(countInstances));
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+      std::string serverName;
+      size_t startPosition;
+
+      // The lifetime of "body" should be larger than "client"
+      std::auto_ptr<RequestBody> body;
+      std::auto_ptr<OrthancPlugins::HttpClient> client;
+
+      {
+        boost::mutex::scoped_lock lock(that_.mutex_);
+        context.SetContent("InstancesCount", boost::lexical_cast<std::string>(that_.instances_.size()));
+        serverName = that_.serverName_;
+        
+        startPosition = that_.position_;        
+        body.reset(new RequestBody(that_, context));
+
+        client.reset(new OrthancPlugins::HttpClient);
+        std::map<std::string, std::string> userProperties;
+        OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient(*client, userProperties, serverName, "/studies");
+        client->SetMethod(OrthancPluginHttpMethod_Post);
+        client->AddHeaders(that_.headers_);
+      }
+
+      OrthancPlugins::HttpClient::HttpHeaders answerHeaders;
+      Json::Value answerBody;
+
+      client->SetBody(*body);
+
+      try
+      {
+        client->Execute(answerHeaders, answerBody);
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        if (client->GetHttpStatus() == 411)
+        {
+          /**
+           * "Length required" error. This might indicate an older
+           * version of Orthanc (<= 1.5.6) that does not support
+           * chunked transfers.
+           **/
+          LOG(ERROR) << "The remote DICOMweb server \"" << serverName << "\" does not support chunked transfers, "
+                     << "set configuration option \"ChunkedTransfers\" to \"0\" in the configuration";
+        }
+
+        throw;
+      }
+
+      {
+        boost::mutex::scoped_lock lock(that_.mutex_);
+        size_t endPosition = that_.position_;
+        CheckStowAnswer(answerBody, serverName, endPosition - startPosition);
+
+        if (that_.action_ == Action_Cancel)
+        {
+          that_.position_ = 0;
+        }
+      }
+    }
+  };
+  
+
+  virtual void CancelFunction()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    action_ = Action_Cancel;
+  }
+  
+
+  virtual void PauseFunction()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    action_ = Action_Pause;
+  }
+
+  
+  virtual IFunction* CreateFunction()
+  {
+    action_ = Action_None;
+    return new F(*this);
+  }
+
+  
+public:
+  StowClientJob(const std::string& serverName,
+                const std::list<std::string>& instances,
+                const OrthancPlugins::HttpClient::HttpHeaders& headers) :
+    SingleFunctionJob("DicomWebStowClient"),
+    serverName_(serverName),
+    headers_(headers),
+    position_(0),
+    action_(Action_None),
+    networkSize_(0),
+    debug_(false)
+  {
+    SetFactory(*this);
+
+    instances_.reserve(instances.size());
+
+    for (std::list<std::string>::const_iterator
+           it = instances.begin(); it != instances.end(); ++it)
+    {
+      instances_.push_back(*it);
     }
 
-    if (GetSequenceSize(size, response, "00081198", false, server.GetUrl()) &&
-        size != 0)
     {
-      OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
-                                              boost::lexical_cast<std::string>(size) + 
-                                              " items in its Failed SOP Sequence (0008,1198) tag");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);    
+      OrthancPlugins::OrthancString tmp;
+      tmp.Assign(OrthancPluginGenerateUuid(OrthancPlugins::GetGlobalContext()));
+
+      if (tmp.GetContent() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                        "Cannot generate a UUID");
+      }
+
+      tmp.ToString(boundary_);
     }
 
-    if (GetSequenceSize(size, response, "0008119A", false, server.GetUrl()) &&
-        size != 0)
-    {
-      OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
-                                              boost::lexical_cast<std::string>(size) + 
-                                              " items in its Other Failures Sequence (0008,119A) tag");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);    
-    }
+    boundary_ = (boundary_ + "-" + boundary_);  // Make the boundary longer
+
+    headers_["Accept"] = "application/dicom+json";
+    headers_["Expect"] = "";
+    headers_["Content-Type"] = "multipart/related; type=\"application/dicom\"; boundary=" + boundary_;
+  }
 
-    countInstances = 0;
+  void SetDebug(bool debug)
+  {
+    debug_ = debug;
   }
-}
+};
+
 
 
 void StowClient(OrthancPluginRestOutput* output,
                 const char* /*url*/,
                 const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-  if (request->groupsCount != 1)
-  {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
-  }
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Post)
   {
@@ -253,99 +822,91 @@
     return;
   }
 
-  Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
-
-  std::string boundary;
-
+  if (request->groupsCount != 1)
   {
-    char* uuid = OrthancPluginGenerateUuid(context);
-    try
-    {
-      boundary.assign(uuid);
-    }
-    catch (...)
-    {
-      OrthancPluginFreeString(context, uuid);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotEnoughMemory);
-    }
-
-    OrthancPluginFreeString(context, uuid);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
   }
 
-  std::string mime = "multipart/related; type=application/dicom; boundary=" + boundary;
+  std::string serverName(request->groups[0]);
 
-  std::map<std::string, std::string> httpHeaders;
-  httpHeaders["Accept"] = "application/json";
-  httpHeaders["Expect"] = "";
-  httpHeaders["Content-Type"] = mime;
+  Json::Value body;
+  OrthancPlugins::ParseJsonBody(body, request);
 
   std::list<std::string> instances;
-  ParseStowRequest(instances, httpHeaders, request);
-
-  OrthancPlugins::Configuration::LogInfo("Sending " + boost::lexical_cast<std::string>(instances.size()) + 
-                                         " instances using STOW-RS to DICOMweb server: " + server.GetUrl());
+  std::map<std::string, std::string> httpHeaders;
+  ParseStowRequest(instances, httpHeaders, body);
 
-  Orthanc::ChunkedBuffer chunks;
-  size_t countInstances = 0;
+  OrthancPlugins::LogInfo("Sending " + boost::lexical_cast<std::string>(instances.size()) +
+                          " instances using STOW-RS to DICOMweb server: " + serverName);
 
-  for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); it++)
+  std::auto_ptr<StowClientJob> job(new StowClientJob(serverName, instances, httpHeaders));
+
+  bool debug;
+  if (OrthancPlugins::LookupBooleanValue(debug, body, "Debug"))
   {
-    OrthancPlugins::MemoryBuffer dicom(context);
-    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, boundary, chunks, countInstances, false);
-    }
+    job->SetDebug(debug);
   }
-
-  SendStowChunks(server, httpHeaders, boundary, chunks, countInstances, true);
-
-  std::string answer = "{}\n";
-  OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+  
+  Json::Value answer;
+  SubmitJob(output, job.release(), body, 
+            true /* synchronous by default, for compatibility with <= 0.6 */);
 }
 
 
-static bool GetStringValue(std::string& target,
-                           const Json::Value& json,
-                           const std::string& key)
+
+static void ParseGetFromServer(std::string& uri,
+                               std::map<std::string, std::string>& additionalHeaders,
+                               const Json::Value& resource)
 {
-  if (json.type() != Json::objectValue)
-  {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-  }
-  else if (!json.isMember(key))
+  static const char* URI = "Uri";
+  static const char* HTTP_HEADERS = "HttpHeaders";
+  static const char* GET_ARGUMENTS = "Arguments";
+
+  std::string tmp;
+  if (resource.type() != Json::objectValue ||
+      !OrthancPlugins::LookupStringValue(tmp, resource, URI))
   {
-    target.clear();
-    return false;
+    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");
   }
-  else if (json[key].type() != Json::stringValue)
+
+  std::map<std::string, std::string> getArguments;
+  OrthancPlugins::ParseAssociativeArray(getArguments, resource, GET_ARGUMENTS); 
+  OrthancPlugins::DicomWebServers::UriEncode(uri, tmp, getArguments);
+
+  OrthancPlugins::ParseAssociativeArray(additionalHeaders, resource, HTTP_HEADERS);
+}
+
+
+
+static void ConfigureGetFromServer(OrthancPlugins::HttpClient& client,
+                                   const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Post)
   {
-    OrthancPlugins::Configuration::LogError("The field \"" + key + "\" in a JSON object should be a string");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
   }
-  else
-  {
-    target = json[key].asString();
-    return true;
-  }
+
+  Json::Value body;
+  OrthancPlugins::ParseJsonBody(body, request);
+
+  std::string uri;
+  std::map<std::string, std::string> additionalHeaders;
+  ParseGetFromServer(uri, additionalHeaders, body);
+
+  std::map<std::string, std::string> userProperties;
+  OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient(client, userProperties, request->groups[0], uri);
+  client.AddHeaders(additionalHeaders);
 }
 
 
+
 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::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Post)
   {
@@ -353,32 +914,12 @@
     return;
   }
 
-  Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
-
-  std::string tmp;
-  Json::Value body;
-  Json::Reader reader;
-  if (!reader.parse(request->body, request->body + request->bodySize, body) ||
-      body.type() != Json::objectValue ||
-      !GetStringValue(tmp, body, URI))
-  {
-    OrthancPlugins::Configuration::LogError("A request to the DICOMweb STOW-RS client must provide a JSON object "
-                                            "with the field \"Uri\" containing the URI of interest");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-  }
-
-  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(context);
+  OrthancPlugins::HttpClient client;
+  ConfigureGetFromServer(client, request);
+  
   std::map<std::string, std::string> answerHeaders;
-  OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
+  std::string answer;
+  client.Execute(answerHeaders, answer);
 
   std::string contentType = "application/octet-stream";
 
@@ -392,9 +933,11 @@
     {
       contentType = it->second;
     }
-    else if (key == "transfer-encoding")
+    else if (key == "transfer-encoding" ||
+             key == "content-length" ||
+             key == "connection")
     {
-      // Do not forward this header
+      // Do not forward these headers
     }
     else
     {
@@ -402,215 +945,615 @@
     }
   }
 
-  OrthancPluginAnswerBuffer(context, output, 
-                            reinterpret_cast<const char*>(answerBody.GetData()),
-                            answerBody.GetSize(), contentType.c_str());
+  OrthancPluginAnswerBuffer(context, output, answer.empty() ? NULL : answer.c_str(), 
+                            answer.size(), contentType.c_str());
+}
+
+
+void GetFromServer(Json::Value& result,
+                   const OrthancPluginHttpRequest* request)
+{
+  OrthancPlugins::HttpClient client;
+  ConfigureGetFromServer(client, request);
+
+  std::map<std::string, std::string> answerHeaders;
+  client.Execute(answerHeaders, result);
 }
 
 
 
-static void RetrieveFromServerInternal(std::set<std::string>& instances,
-                                       const Orthanc::WebServiceParameters& server,
-                                       const std::map<std::string, std::string>& httpHeaders,
-                                       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";
+
 
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-  if (resource.type() != Json::objectValue)
+class WadoRetrieveAnswer : 
+  public OrthancPlugins::HttpClient::IAnswer,
+  private Orthanc::MultipartStreamReader::IHandler
+{
+private:
+  enum State
   {
-    OrthancPlugins::Configuration::LogError("Resources of interest for the DICOMweb WADO-RS Retrieve client "
-                                            "must be provided as a JSON object");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-  }
+    State_Headers,
+    State_Body,
+    State_Canceled
+  };
 
-  std::string study, series, instance;
-  if (!GetStringValue(study, resource, STUDY) ||
-      study.empty())
+  bool                                           debug_;
+  boost::mutex                                   mutex_;
+  State                                          state_;
+  std::list<std::string>                         instances_;
+  std::auto_ptr<Orthanc::MultipartStreamReader>  reader_;
+  uint64_t                                       networkSize_;
+
+  virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
+                          const void* part,
+                          size_t size)
   {
-    OrthancPlugins::Configuration::LogError("A non-empty \"" + STUDY + "\" field is mandatory for the "
-                                            "DICOMweb WADO-RS Retrieve client");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-  }
+    std::string contentType;
+    if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                      "Missing Content-Type for a part of WADO-RS answer");
+    }
 
-  GetStringValue(series, resource, SERIES);
-  GetStringValue(instance, resource, INSTANCE);
+    size_t pos = contentType.find(';');
+    if (pos != std::string::npos)
+    {
+      contentType = contentType.substr(0, pos);
+    }
 
-  if (series.empty() && 
-      !instance.empty())
-  {
-    OrthancPlugins::Configuration::LogError("When specifying a \"" + INSTANCE + "\" field in a call to DICOMweb "
-                                            "WADO-RS Retrieve client, the \"" + SERIES + "\" field is mandatory");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
-  }
+    contentType = Orthanc::Toolbox::StripSpaces(contentType);
+    if (!boost::iequals(contentType, "application/dicom"))
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NetworkProtocol,
+        "Parts of a WADO-RS retrieve should have \"application/dicom\" type, but received: " + contentType);
+    }
+
+    OrthancPlugins::MemoryBuffer tmp;
+    tmp.RestApiPost("/instances", part, size, false);
+
+    Json::Value result;
+    tmp.ToJson(result);
 
-  std::string uri = "studies/" + study;
-  if (!series.empty())
-  {
-    uri += "/series/" + series;
-    if (!instance.empty())
+    std::string id;
+    if (OrthancPlugins::LookupStringValue(id, result, "ID"))
+    {
+      instances_.push_back(id);
+    }
+    else
     {
-      uri += "/instances/" + instance;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
+    }
+
+    if (debug_)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(50));
     }
   }
 
-  OrthancPlugins::MemoryBuffer answerBody(context);
-  std::map<std::string, std::string> answerHeaders;
-  OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
+public:
+  WadoRetrieveAnswer() :
+    debug_(false),
+    state_(State_Headers),
+    networkSize_(0)
+  {
+  }
+
+  virtual ~WadoRetrieveAnswer()
+  {
+  }
 
-  std::vector<std::string> contentType;
-  for (std::map<std::string, std::string>::const_iterator 
-         it = answerHeaders.begin(); it != answerHeaders.end(); ++it)
+  void SetDebug(bool debug)
+  {
+    debug_ = debug;
+  }
+
+  void Close()
   {
-    std::string s = Orthanc::Toolbox::StripSpaces(it->first);
-    Orthanc::Toolbox::ToLowerCase(s);
-    if (s == "content-type")
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (state_ != State_Canceled &&
+        reader_.get() != NULL)
     {
-      Orthanc::Toolbox::TokenizeString(contentType, it->second, ';');
-      break;
+      reader_->CloseStream();
     }
   }
 
-  if (contentType.empty())
-  {
-    OrthancPlugins::Configuration::LogError("No Content-Type provided by the remote WADO-RS server");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
-  }
-
-  Orthanc::Toolbox::ToLowerCase(contentType[0]);
-  if (Orthanc::Toolbox::StripSpaces(contentType[0]) != MULTIPART_RELATED)
-  {
-    OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + contentType[0] +
-                                            "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
-  }
-
-  std::string type, boundary;
-  for (size_t i = 1; i < contentType.size(); i++)
+  virtual void AddHeader(const std::string& key,
+                         const std::string& value)
   {
-    std::vector<std::string> tokens;
-    Orthanc::Toolbox::TokenizeString(tokens, contentType[i], '=');
+    boost::mutex::scoped_lock lock(mutex_);
 
-    if (tokens.size() == 2)
+    if (state_ == State_Canceled)
+    {
+      return;
+    }
+    else if (state_ != State_Headers)
     {
-      std::string s = Orthanc::Toolbox::StripSpaces(tokens[0]);
-      Orthanc::Toolbox::ToLowerCase(s);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (boost::iequals(key, "Content-Type"))
+    {
+      if (reader_.get() != NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Received twice a Content-Type header in WADO-RS");
+      }
+
+      std::string contentType, subType, boundary;
 
-      if (s == "type")
+      if (!Orthanc::MultipartStreamReader::ParseMultipartContentType
+          (contentType, subType, boundary, value))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Cannot parse the Content-Type for WADO-RS: " + value);
+      }
+
+      if (!boost::iequals(contentType, MULTIPART_RELATED))
       {
-        type = Orthanc::Toolbox::StripSpaces(tokens[1]);
-        Orthanc::Toolbox::ToLowerCase(type);
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_NetworkProtocol,
+          "The remote WADO-RS server answers with a \"" + contentType +
+          "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected");
       }
-      else if (s == "boundary")
+
+      reader_.reset(new Orthanc::MultipartStreamReader(boundary));
+      reader_->SetHandler(*this);
+
+      if (debug_)
       {
-        boundary = Orthanc::Toolbox::StripSpaces(tokens[1]);
+        reader_->SetBlockSize(1024 * 64);
       }
     }
   }
 
-  if (type != APPLICATION_DICOM)
+  virtual void AddChunk(const void* data,
+                        size_t size)
   {
-    OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + type +
-                                            "\" multipart Content-Type, but \"" + APPLICATION_DICOM + "\" is expected");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (state_ == State_Canceled)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CanceledJob);
+    }
+    else if (reader_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                      "No Content-Type provided by the remote WADO-RS server, "
+                                      "your remote DICOMweb server might need client option \"" +
+                                      HAS_WADO_RS_UNIVERSAL_TRANSFER_SYNTAX + "\" set to \"false\"");
+    }
+    else
+    {
+      state_ = State_Body;
+      networkSize_ += size;
+      reader_->AddChunk(data, size);
+    }
   }
 
-  if (boundary.empty())
+  void GetReceivedInstances(std::list<std::string>& target)
   {
-    OrthancPlugins::Configuration::LogError("The remote WADO-RS server does not provide a boundary for its multipart answer");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    boost::mutex::scoped_lock lock(mutex_);
+    target = instances_;
+  }
+
+  void Cancel()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    LOG(ERROR) << "A WADO-RS retrieve job has been canceled, expect \"Error in the network protocol\" errors";
+    state_ = State_Canceled;
   }
 
-  std::vector<OrthancPlugins::MultipartItem> parts;
-  OrthancPlugins::ParseMultipartBody(parts, context, 
-                                     reinterpret_cast<const char*>(answerBody.GetData()),
-                                     answerBody.GetSize(), boundary);
+  uint64_t GetNetworkSize()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return networkSize_;
+  }
+};
+
+
+
+
 
-  OrthancPlugins::Configuration::LogInfo("The remote WADO-RS server has provided " +
-                                         boost::lexical_cast<std::string>(parts.size()) + 
-                                         " DICOM instances");
+class WadoRetrieveJob : 
+  public SingleFunctionJob,
+  private SingleFunctionJob::IFunctionFactory
+{
+private:
+  class Resource : public boost::noncopyable
+  {
+  private:
+    std::string                        uri_;
+    std::map<std::string, std::string> additionalHeaders_;
 
-  for (size_t i = 0; i < parts.size(); i++)
-  {
-    if (parts[i].contentType_ != APPLICATION_DICOM)
+  public:
+    explicit Resource(const std::string& uri) :
+      uri_(uri)
     {
-      OrthancPlugins::Configuration::LogError("The remote WADO-RS server has provided a non-DICOM file in its multipart answer");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);      
+    }
+
+    Resource(const std::string& uri,
+             const std::map<std::string, std::string>& additionalHeaders) :
+      uri_(uri),
+      additionalHeaders_(additionalHeaders)
+    {
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
     }
 
-    OrthancPlugins::MemoryBuffer tmp(context);
-    tmp.RestApiPost("/instances", parts[i].data_, parts[i].size_, false);
+    const std::map<std::string, std::string>& GetAdditionalHeaders() const
+    {
+      return additionalHeaders_;
+    }
+  };
+
+
+  class F : public IFunction
+  {
+  private:
+    WadoRetrieveJob&   that_;
 
-    Json::Value result;
-    tmp.ToJson(result);
+  public:
+    explicit F(WadoRetrieveJob& that) :
+      that_(that)
+    {
+    }
+
+    virtual void Execute(JobContext& context)
+    {
+      for (;;)
+      {
+        OrthancPlugins::HttpClient client;
 
-    if (result.type() != Json::objectValue ||
-        !result.isMember("ID") ||
-        result["ID"].type() != Json::stringValue)
+        if (that_.SetupNextResource(client, context))
+        {
+          client.Execute(*that_.answer_);
+          that_.CloseResource(context);
+        }
+        else
+        {
+          return;   // We're done
+        }
+      }
+    }
+  };
+
+
+  boost::mutex            mutex_;
+  std::string             serverName_;
+  size_t                  position_;
+  std::vector<Resource*>  resources_;
+  bool                    stopped_;
+  std::list<std::string>  retrievedInstances_;
+  std::auto_ptr<WadoRetrieveAnswer>  answer_;
+  uint64_t                networkSize_;
+  bool                    debug_;
+
+  bool SetupNextResource(OrthancPlugins::HttpClient& client,
+                         JobContext& context)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (stopped_ ||
+        position_ == resources_.size())
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);      
+      return false;
     }
     else
     {
-      instances.insert(result["ID"].asString());
+      context.SetProgress(position_, resources_.size());
+
+      answer_.reset(new WadoRetrieveAnswer);
+      answer_->SetDebug(debug_);
+
+      const Resource* resource = resources_[position_++];
+      if (resource == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      const std::map<std::string, std::string>& headers = resource->GetAdditionalHeaders();
+
+      std::map<std::string, std::string> userProperties;
+      OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient
+        (client, userProperties, serverName_, resource->GetUri());
+      client.AddHeaders(headers);
+
+      /**
+       * From documentation of Google Healthcare API: "The response's
+       * default transfer syntax is Little Endian Explicit. As a
+       * result, if the file was uploaded using a compressed transfer
+       * syntax, the returned object will be decompressed. This can
+       * negatively impact performance and lead to errors for transfer
+       * syntaxes that the Cloud Healthcare API doesn't support. To
+       * avoid these issues, and if the returned object's transfer
+       * syntax does not matter to your application, use the [...]
+       * Accept Header."
+       * https://cloud.google.com/healthcare/docs/dicom
+       * https://groups.google.com/d/msg/orthanc-users/w1Ekrsc6-U8/T2a_DoQ5CwAJ
+       *
+       * WARNING - This breaks compatibility with Orthanc servers
+       * equiped with DICOMweb <= 1.0, as can be seen in integration
+       * test "Orthanc.test_server_retrieve". The configuration option
+       * "HasWadoRsUniversalTransferSyntax" enables compatibility with
+       * DICOMweb <= 1.0.
+       **/
+
+      if (headers.find("Accept") == headers.end())
+      {
+        bool hasUniversal;
+          
+        // The "Accept" field was not provided in the "HttpHeaders"
+        // field of the POST body of: "/dicom-web/servers/.../retrieve"
+        std::map<std::string, std::string>::const_iterator found = 
+          userProperties.find(HAS_WADO_RS_UNIVERSAL_TRANSFER_SYNTAX);
+
+        if (found == userProperties.end())
+        {
+          hasUniversal = true;  // By default, assume "true"
+        }
+        else if (found->second == "true" ||
+                 found->second == "1")
+        {
+          hasUniversal = true;
+        }
+        else if (found->second == "false" ||
+                 found->second == "0")
+        {
+          hasUniversal = false;
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                          "Configuration option \"" + HAS_WADO_RS_UNIVERSAL_TRANSFER_SYNTAX +
+                                          "\" of remote DICOMweb server \"" + serverName_ +
+                                          "\" must be a Boolean, found: " + found->second);
+        }
+        
+        if (hasUniversal)
+        {
+          client.AddHeader("Accept", "multipart/related; type=\"application/dicom\"; transfer-syntax=*");
+        }
+      }
+
+      return true;
     }
   }
+
+
+  void CloseResource(JobContext& context)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    answer_->Close();
+
+    std::list<std::string> instances;
+    answer_->GetReceivedInstances(instances);
+    networkSize_ += answer_->GetNetworkSize();
+
+    answer_.reset();
+
+    retrievedInstances_.splice(retrievedInstances_.end(), instances);
+
+    context.SetProgress(position_, resources_.size());
+    context.SetContent("NetworkUsageMB", boost::lexical_cast<std::string>
+                       (networkSize_ / static_cast<uint64_t>(1024 * 1024)));
+    context.SetContent("ReceivedInstancesCount", boost::lexical_cast<std::string>(retrievedInstances_.size()));
+  }
+
+
+  virtual void CancelFunction()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    stopped_ = true;
+    if (answer_.get() != NULL)
+    {      
+      answer_->Cancel();
+    }
+  }
+
+  virtual void PauseFunction()
+  {
+    // This type of job cannot be paused
+    CancelFunction();
+  }
+
+  virtual IFunction* CreateFunction()
+  {
+    // This type of job cannot be paused: If restarting, always go
+    // back to the beginning
+
+    stopped_ = false;
+    position_ = 0;
+    retrievedInstances_.clear();
+
+    return new F(*this);
+  }
+
+public:
+  explicit WadoRetrieveJob(const std::string& serverName) :
+    SingleFunctionJob("DicomWebWadoRetrieveClient"),
+    serverName_(serverName),
+    position_(0),
+    stopped_(false),
+    networkSize_(0),
+    debug_(false)
+  {
+    SetFactory(*this);
+  }
+
+  virtual ~WadoRetrieveJob()
+  {
+    SingleFunctionJob::Finalize();
+
+    for (size_t i = 0; i < resources_.size(); i++)
+    {
+      assert(resources_[i] != NULL);
+      delete resources_[i];
+    }
+  }
+
+  void SetDebug(bool debug)
+  {
+    debug_ = debug;
+  }
+
+  /*void AddResource(const std::string& uri)
+  {
+    resources_.push_back(new Resource(uri));
+    }*/
+
+  void AddResource(const std::string& uri,
+                   const std::map<std::string, std::string>& additionalHeaders)
+  {
+    resources_.push_back(new Resource(uri, additionalHeaders));
+  }
+
+  void AddResourceFromRequest(const Json::Value& resource)
+  {
+    std::string uri;
+    std::map<std::string, std::string> additionalHeaders;
+    ParseGetFromServer(uri, additionalHeaders, resource);
+
+    resources_.push_back(new Resource(uri, additionalHeaders));
+  }
+};
+
+
+void WadoRetrieveClient(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+  if (request->groupsCount != 1)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
+  }
+
+  std::string serverName(request->groups[0]);
+
+  Json::Value body;
+  OrthancPlugins::ParseJsonBody(body, request);
+
+  std::auto_ptr<WadoRetrieveJob>  job(new WadoRetrieveJob(serverName));
+  job->AddResourceFromRequest(body);
+
+  bool debug;
+  if (OrthancPlugins::LookupBooleanValue(debug, body, "Debug"))
+  {
+    job->SetDebug(debug);
+  }
+
+  SubmitJob(output, job.release(), body, false /* asynchronous by default */);
 }
 
 
 
 void RetrieveFromServer(OrthancPluginRestOutput* output,
-                        const char* /*url*/,
+                        const char* url,
                         const OrthancPluginHttpRequest* request)
 {
-  static const std::string RESOURCES("Resources");
-  static const char* HTTP_HEADERS = "HttpHeaders";
-
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  static const char* const GET_ARGUMENTS = "GetArguments";
+  static const char* const HTTP_HEADERS = "HttpHeaders";
+  static const char* const RESOURCES = "Resources";
+  static const char* const STUDY = "Study";
+  static const char* const SERIES = "Series";
+  static const char* const INSTANCE = "Instance";
 
   if (request->method != OrthancPluginHttpMethod_Post)
   {
-    OrthancPluginSendMethodNotAllowed(context, output, "POST");
-    return;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+  if (request->groupsCount != 1)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
   }
 
-  Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
+  std::string serverName(request->groups[0]);
 
   Json::Value body;
-  Json::Reader reader;
-  if (!reader.parse(request->body, request->body + request->bodySize, body) ||
-      body.type() != Json::objectValue ||
+  OrthancPlugins::ParseJsonBody(body, request);
+
+  std::map<std::string, std::string> getArguments;
+  OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS); 
+
+  std::map<std::string, std::string> additionalHeaders;
+  OrthancPlugins::ParseAssociativeArray(additionalHeaders, body, HTTP_HEADERS);
+
+  std::auto_ptr<WadoRetrieveJob> job(new WadoRetrieveJob(serverName));
+
+  if (body.type() != Json::objectValue ||
       !body.isMember(RESOURCES) ||
       body[RESOURCES].type() != Json::arrayValue)
   {
-    OrthancPlugins::Configuration::LogError("A request to the DICOMweb WADO-RS Retrieve client must provide a JSON object "
-                                            "with the field \"" + RESOURCES + "\" containing an array of resources");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                    "The body must be a JSON object containing an array \"" + 
+                                    std::string(RESOURCES) + "\"");
   }
 
-  std::map<std::string, std::string> httpHeaders;
-  OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
+  const Json::Value& resources = body[RESOURCES];
+
+  for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
+  {
+    std::string study;
+    if (!OrthancPlugins::LookupStringValue(study, resources[i], STUDY))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Missing \"Study\" field in the body");
+    }
+
+    std::string series;
+    if (!OrthancPlugins::LookupStringValue(series, resources[i], SERIES))
+    {
+      series.clear();
+    }
+
+    std::string instance;
+    if (!OrthancPlugins::LookupStringValue(instance, resources[i], INSTANCE))
+    {
+      instance.clear();
+    }
 
-  std::set<std::string> instances;
-  for (Json::Value::ArrayIndex i = 0; i < body[RESOURCES].size(); i++)
-  {
-    RetrieveFromServerInternal(instances, server, httpHeaders, body[RESOURCES][i]);
+    if (series.empty() &&
+        !instance.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Missing \"Series\" field in the body, as \"Instance\" is present");
+    }
+
+    std::string tmp = "/studies/" + study;
+
+    if (!series.empty())
+    {
+      tmp += "/series/" + series;
+    }
+
+    if (!instance.empty())
+    {
+      tmp += "/instances/" + instance;
+    }
+
+    std::string uri;
+    OrthancPlugins::DicomWebServers::UriEncode(uri, tmp, getArguments);
+
+    job->AddResource(uri, additionalHeaders);
   }
 
-  Json::Value status = Json::objectValue;
-  status["Instances"] = Json::arrayValue;
-  
-  for (std::set<std::string>::const_iterator
-         it = instances.begin(); it != instances.end(); ++it)
+  bool debug;
+  if (OrthancPlugins::LookupBooleanValue(debug, body, "Debug"))
   {
-    status["Instances"].append(*it);
+    job->SetDebug(debug);
   }
 
-  std::string s = status.toStyledString();
-  OrthancPluginAnswerBuffer(context, output, s.c_str(), s.size(), "application/json");
+  SubmitJob(output, job.release(), body, 
+            true /* synchronous by default, for compatibility with <= 0.6 */);
 }
+
--- a/Plugin/DicomWebClient.h	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/DicomWebClient.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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
@@ -31,6 +32,14 @@
                    const char* /*url*/,
                    const OrthancPluginHttpRequest* request);
 
+void GetFromServer(Json::Value& result,
+                   const OrthancPluginHttpRequest* request);
+
+// TODO => Mark as deprecated
 void RetrieveFromServer(OrthancPluginRestOutput* output,
                         const char* /*url*/,
                         const OrthancPluginHttpRequest* request);
+
+void WadoRetrieveClient(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomWebFormatter.cpp	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,259 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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>
+
+#if !defined(NDEBUG)
+#  include <json/reader.h>
+#endif
+
+
+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));
+    }
+
+    if (s.GetContent() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                      "Cannot convert DICOM to DICOMweb");
+    }
+    else
+    {
+      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::AddOrthancMap(const Orthanc::DicomMap& value)
+  {
+    Json::Value json = Json::objectValue;
+
+    std::set<Orthanc::DicomTag> tags;
+    value.GetTags(tags);
+    
+    for (std::set<Orthanc::DicomTag>::const_iterator
+           it = tags.begin(); it != tags.end(); ++it)
+    {
+      std::string s;
+      if (value.LookupStringValue(s, *it, false))
+      {
+        json[it->Format()] = s;
+      }
+    }
+    
+    AddOrthancJson(json);
+  }
+
+
+  void DicomWebFormatter::HttpWriter::AddOrthancJson(const Json::Value& value)
+  {
+    MemoryBuffer dicom;
+    dicom.CreateDicom(value, OrthancPluginCreateDicomFlags_None);
+
+    AddInternal(dicom.GetData(), dicom.GetSize(), OrthancPluginDicomWebBinaryMode_Ignore, "");
+  }
+
+
+  void DicomWebFormatter::HttpWriter::AddDicomWebSerializedJson(const void* data,
+                                                                size_t size)
+  {
+    if (isXml_)
+    {
+      // This function can only be used in the JSON case
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+#if !defined(NDEBUG)  // In debug mode, check that the value is actually a JSON string
+    Json::Reader reader;
+    Json::Value json;
+    if (!reader.parse(reinterpret_cast<const char*>(data),
+                      reinterpret_cast<const char*>(data) + size, json))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+#endif
+    
+    if (first_)
+    {
+      first_ = false;
+    }
+    else
+    {
+      jsonBuffer_.AddChunk(",");
+    }
+    
+    jsonBuffer_.AddChunk(data, size);
+  }
+
+
+  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	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,123 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 <Core/DicomFormat/DicomMap.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);
+
+      bool IsXml() const
+      {
+        return isXml_;
+      }
+
+      void AddDicom(const void* dicom,
+                    size_t size,
+                    const std::string& bulkRoot)
+      {
+        AddInternal(dicom, size, OrthancPluginDicomWebBinaryMode_BulkDataUri, bulkRoot);
+      }
+
+      void AddOrthancMap(const Orthanc::DicomMap& value);
+
+      void AddOrthancJson(const Json::Value& value);
+
+      void AddDicomWebSerializedJson(const void* data,
+                                     size_t size);
+
+      void Send();
+    };
+  };
+}
--- a/Plugin/DicomWebServers.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/DicomWebServers.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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
@@ -21,7 +22,10 @@
 #include "DicomWebServers.h"
 
 #include "Configuration.h"
-#include "../Orthanc/Core/Toolbox.h"
+
+#include <Core/Toolbox.h>
+
+#include <boost/algorithm/string/predicate.hpp>
 
 namespace OrthancPlugins
 {
@@ -54,8 +58,8 @@
 
         for (size_t i = 0; i < members.size(); i++)
         {
-          std::auto_ptr<Orthanc::WebServiceParameters> parameters(new Orthanc::WebServiceParameters);
-          parameters->FromJson(servers[members[i]]);
+          std::auto_ptr<Orthanc::WebServiceParameters> parameters
+            (new Orthanc::WebServiceParameters(servers[members[i]]));
 
           servers_[members[i]] = parameters.release();
         }
@@ -63,15 +67,16 @@
     }
     catch (Orthanc::OrthancException& e)
     {
-      OrthancPlugins::Configuration::LogError("Exception while parsing the \"DicomWeb.Servers\" section "
-                                              "of the configuration file: " + std::string(e.What()));
+      OrthancPlugins::LogError("Exception while parsing the \"DicomWeb.Servers\" section "
+                               "of the configuration file: " + std::string(e.What()));
       throw;
     }
 
     if (!ok)
     {
-      OrthancPlugins::Configuration::LogError("Cannot parse the \"DicomWeb.Servers\" section of the configuration file");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_BadFileFormat,
+        "Cannot parse the \"DicomWeb.Servers\" section of the configuration file");
     }
   }
 
@@ -91,8 +96,8 @@
     if (server == servers_.end() ||
         server->second == NULL)
     {
-      OrthancPlugins::Configuration::LogError("Inexistent server: " + name);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InexistentItem);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
+                                      "Inexistent server: " + name);
     }
     else
     {
@@ -113,6 +118,80 @@
   }
 
 
+  void DicomWebServers::ConfigureHttpClient(HttpClient& client,
+                                            std::map<std::string, std::string>& userProperties,
+                                            const std::string& name,
+                                            const std::string& uri)
+  {
+    static const char* HAS_CHUNKED_TRANSFERS = "ChunkedTransfers";
+    
+    const Orthanc::WebServiceParameters parameters = GetServer(name);
+
+    client.SetUrl(RemoveMultipleSlashes(parameters.GetUrl() + "/" + uri));
+    client.SetHeaders(parameters.GetHttpHeaders());
+
+    if (!parameters.GetUsername().empty())
+    {
+      client.SetCredentials(parameters.GetUsername(), parameters.GetPassword());
+    }
+
+    if (!parameters.GetCertificateFile().empty())
+    {
+      client.SetCertificate(
+        parameters.GetCertificateFile(),
+        parameters.GetCertificateKeyFile(),
+        parameters.GetCertificateKeyPassword());
+    }
+
+    client.SetPkcs11(parameters.IsPkcs11Enabled());
+
+    // By default, enable chunked transfers
+    client.SetChunkedTransfersAllowed(
+      parameters.GetBooleanUserProperty(HAS_CHUNKED_TRANSFERS, true));
+
+    userProperties = parameters.GetUserProperties();
+  }
+
+
+  void DicomWebServers::DeleteServer(const std::string& name)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Servers::iterator found = servers_.find(name);
+
+    if (found == servers_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Unknown DICOMweb server: " + name);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      delete found->second;
+      servers_.erase(found);
+    }
+  }
+
+
+  void DicomWebServers::SetServer(const std::string& name,
+                                  const Orthanc::WebServiceParameters& parameters)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Servers::iterator found = servers_.find(name);
+
+    if (found != servers_.end())
+    {
+      assert(found->second != NULL);
+      delete found->second;
+      servers_.erase(found);
+    }
+
+    servers_[name] = new Orthanc::WebServiceParameters(parameters);
+  }
+
+  
+
   static const char* ConvertToCString(const std::string& s)
   {
     if (s.empty())
@@ -142,7 +221,6 @@
     assert(!url.empty() && url[url.size() - 1] == '/');
 
     // Remove the leading "/" in the URI if need be
-    std::string tmp;
     if (!uri.empty() &&
         uri[0] == '/')
     {
@@ -153,18 +231,32 @@
       url += uri;
     }
 
-    std::vector<const char*> httpHeadersKeys(httpHeaders.size());
-    std::vector<const char*> httpHeadersValues(httpHeaders.size());
+    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 = httpHeaders.begin(); it != httpHeaders.end(); ++it)
+             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;
@@ -178,10 +270,10 @@
       bodySize = body.size();
     }
 
-    OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+    OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
     uint16_t status = 0;
-    MemoryBuffer answerHeadersTmp(context);
+    MemoryBuffer answerHeadersTmp;
     OrthancPluginErrorCode code = OrthancPluginHttpClient(
       context, 
       /* Outputs */
@@ -189,7 +281,7 @@
       method,
       url.c_str(), 
       /* HTTP headers*/
-      httpHeaders.size(),
+      allHttpHeaders.size(),
       httpHeadersKeys.empty() ? NULL : &httpHeadersKeys[0],
       httpHeadersValues.empty() ? NULL : &httpHeadersValues[0],
       bodyContent, bodySize,
@@ -204,9 +296,10 @@
     if (code != OrthancPluginErrorCode_Success ||
         (status < 200 || status >= 300))
     {
-      OrthancPlugins::Configuration::LogError("Cannot issue an HTTP query to " + url + 
-                                              " (HTTP status: " + boost::lexical_cast<std::string>(status) + ")");
-      throw PluginException(code);
+      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;
@@ -215,7 +308,7 @@
 
     if (json.type() != Json::objectValue)
     {
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
 
     Json::Value::Members members = json.getMemberNames();
@@ -225,7 +318,7 @@
 
       if (json[key].type() != Json::stringValue)
       {
-        throw PluginException(OrthancPluginErrorCode_InternalError);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
       else
       {
@@ -235,15 +328,15 @@
   }
 
 
-  void UriEncode(std::string& uri,
-                 const std::string& resource,
-                 const std::map<std::string, std::string>& getArguments)
+  void DicomWebServers::UriEncode(std::string& uri,
+                                  const std::string& resource,
+                                  const std::map<std::string, std::string>& getArguments)
   {
     if (resource.find('?') != std::string::npos)
     {
-      OrthancPlugins::Configuration::LogError("The GET arguments must be provided in a separate field "
-                                              "(explicit \"?\" is disallowed): " + resource);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "The GET arguments must be provided in a separate field "
+                                      "(explicit \"?\" is disallowed): " + resource);
     }
 
     uri = resource;
--- a/Plugin/DicomWebServers.h	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/DicomWebServers.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,8 +20,8 @@
 
 #pragma once
 
-#include "../Orthanc/Core/WebServiceParameters.h"
-#include "../Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+#include <Core/WebServiceParameters.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
 
 #include <list>
 #include <string>
@@ -44,6 +45,10 @@
     }
 
   public:
+    static void UriEncode(std::string& uri,
+                          const std::string& resource,
+                          const std::map<std::string, std::string>& getArguments);
+
     void Load(const Json::Value& configuration);
 
     ~DicomWebServers()
@@ -56,6 +61,16 @@
     Orthanc::WebServiceParameters GetServer(const std::string& name);
 
     void ListServers(std::list<std::string>& servers);
+
+    void ConfigureHttpClient(HttpClient& client,
+                             std::map<std::string, std::string>& userProperties,
+                             const std::string& name,
+                             const std::string& uri);
+
+    void DeleteServer(const std::string& name);
+
+    void SetServer(const std::string& name,
+                   const Orthanc::WebServiceParameters& parameters);
   };
 
 
@@ -66,8 +81,4 @@
                   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	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,438 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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());
+  }
+}
+
+
+#include "./GdcmParsedDicomFile_TransferSyntaxes.impl.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/GdcmParsedDicomFile.h	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,98 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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;
+
+    Orthanc::DicomTransferSyntax GetTransferSyntax() const
+    {
+      return GetOrthancTransferSyntax(GetFile().GetHeader().GetDataSetTransferSyntax());
+    }
+    
+    static gdcm::TransferSyntax GetGdcmTransferSyntax(Orthanc::DicomTransferSyntax syntax);
+    
+    static Orthanc::DicomTransferSyntax GetOrthancTransferSyntax(gdcm::TransferSyntax syntax);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/GdcmParsedDicomFile_TransferSyntaxes.impl.h	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,121 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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/>.
+ **/
+
+// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py"
+
+namespace OrthancPlugins
+{
+  gdcm::TransferSyntax GdcmParsedDicomFile::GetGdcmTransferSyntax(Orthanc::DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case Orthanc::DicomTransferSyntax_LittleEndianImplicit:
+        return gdcm::TransferSyntax::ImplicitVRLittleEndian;
+
+      case Orthanc::DicomTransferSyntax_LittleEndianExplicit:
+        return gdcm::TransferSyntax::ExplicitVRLittleEndian;
+
+      case Orthanc::DicomTransferSyntax_JPEGProcess1:
+        return gdcm::TransferSyntax::JPEGBaselineProcess1;
+
+      case Orthanc::DicomTransferSyntax_JPEGProcess2_4:
+        return gdcm::TransferSyntax::JPEGExtendedProcess2_4;
+
+      case Orthanc::DicomTransferSyntax_JPEGProcess14:
+        return gdcm::TransferSyntax::JPEGLosslessProcess14;
+
+      case Orthanc::DicomTransferSyntax_JPEGProcess14SV1:
+        return gdcm::TransferSyntax::JPEGLosslessProcess14_1;
+
+      case Orthanc::DicomTransferSyntax_JPEGLSLossless:
+        return gdcm::TransferSyntax::JPEGLSLossless;
+
+      case Orthanc::DicomTransferSyntax_JPEGLSLossy:
+        return gdcm::TransferSyntax::JPEGLSNearLossless;
+
+      case Orthanc::DicomTransferSyntax_JPEG2000LosslessOnly:
+        return gdcm::TransferSyntax::JPEG2000Lossless;
+
+      case Orthanc::DicomTransferSyntax_JPEG2000:
+        return gdcm::TransferSyntax::JPEG2000;
+
+      case Orthanc::DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+        return gdcm::TransferSyntax::JPEG2000Part2Lossless;
+
+      case Orthanc::DicomTransferSyntax_JPEG2000Multicomponent:
+        return gdcm::TransferSyntax::JPEG2000Part2;
+
+      case Orthanc::DicomTransferSyntax_RLELossless:
+        return gdcm::TransferSyntax::RLELossless;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  Orthanc::DicomTransferSyntax GdcmParsedDicomFile::GetOrthancTransferSyntax(gdcm::TransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case gdcm::TransferSyntax::ImplicitVRLittleEndian:
+        return Orthanc::DicomTransferSyntax_LittleEndianImplicit;
+
+      case gdcm::TransferSyntax::ExplicitVRLittleEndian:
+        return Orthanc::DicomTransferSyntax_LittleEndianExplicit;
+
+      case gdcm::TransferSyntax::JPEGBaselineProcess1:
+        return Orthanc::DicomTransferSyntax_JPEGProcess1;
+
+      case gdcm::TransferSyntax::JPEGExtendedProcess2_4:
+        return Orthanc::DicomTransferSyntax_JPEGProcess2_4;
+
+      case gdcm::TransferSyntax::JPEGLosslessProcess14:
+        return Orthanc::DicomTransferSyntax_JPEGProcess14;
+
+      case gdcm::TransferSyntax::JPEGLosslessProcess14_1:
+        return Orthanc::DicomTransferSyntax_JPEGProcess14SV1;
+
+      case gdcm::TransferSyntax::JPEGLSLossless:
+        return Orthanc::DicomTransferSyntax_JPEGLSLossless;
+
+      case gdcm::TransferSyntax::JPEGLSNearLossless:
+        return Orthanc::DicomTransferSyntax_JPEGLSLossy;
+
+      case gdcm::TransferSyntax::JPEG2000Lossless:
+        return Orthanc::DicomTransferSyntax_JPEG2000LosslessOnly;
+
+      case gdcm::TransferSyntax::JPEG2000:
+        return Orthanc::DicomTransferSyntax_JPEG2000;
+
+      case gdcm::TransferSyntax::JPEG2000Part2Lossless:
+        return Orthanc::DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly;
+
+      case gdcm::TransferSyntax::JPEG2000Part2:
+        return Orthanc::DicomTransferSyntax_JPEG2000Multicomponent;
+
+      case gdcm::TransferSyntax::RLELossless:
+        return Orthanc::DicomTransferSyntax_RLELossless;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/OrthancExplorer.js	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,142 @@
+function ChooseDicomWebServer(callback)
+{
+  var clickedModality = '';
+  var clickedPeer = '';
+  var items = $('<ul>')
+    .attr('data-divider-theme', 'd')
+    .attr('data-role', 'listview');
+
+  $.ajax({
+    url: '../${DICOMWEB_ROOT}/servers',
+    type: 'GET',
+    dataType: 'json',
+    async: false,
+    cache: false,
+    success: function(servers) {
+      var name, item;
+      
+      if (servers.length > 0)
+      {
+        items.append('<li data-role="list-divider">DICOMweb servers</li>');
+
+        for (var i = 0; i < servers.length; i++) {
+          name = servers[i];
+          item = $('<li>')
+            .html('<a href="#" rel="close">' + name + '</a>')
+            .attr('name', name)
+            .click(function() { 
+              clickedModality = $(this).attr('name');
+            });
+          items.append(item);
+        }
+      }
+
+      // Launch the dialog
+      $(document).simpledialog2({
+        mode: 'blank',
+        animate: false,
+        headerText: 'Choose target',
+        headerClose: true,
+        forceInput: false,
+        width: '100%',
+        blankContent: items,
+        callbackClose: function() {
+          var timer;
+          function WaitForDialogToClose() {
+            if (!$('#dialog').is(':visible')) {
+              clearInterval(timer);
+              callback(clickedModality, clickedPeer);
+            }
+          }
+          timer = setInterval(WaitForDialogToClose, 100);
+        }
+      });
+    }
+  });
+}
+
+
+function ConfigureDicomWebStowClient(resourceId, buttonId, positionOnPage)
+{
+  $('#' + buttonId).remove();
+
+  var b = $('<a>')
+      .attr('id', buttonId)
+      .attr('data-role', 'button')
+      .attr('href', '#')
+      .attr('data-icon', 'forward')
+      .attr('data-theme', 'e')
+      .text('Send to DICOMweb server')
+      .button();
+
+  b.insertAfter($('#' + positionOnPage));
+
+  b.click(function() {
+    if ($.mobile.pageData) {
+      ChooseDicomWebServer(function(server) {
+        if (server != '' && resourceId != '') {
+          var query = {
+            'Resources' : [ resourceId ],
+            'Synchronous' : false
+          };
+          
+          $.ajax({
+            url: '../${DICOMWEB_ROOT}/servers/' + server + '/stow',
+            type: 'POST',
+            dataType: 'json',
+            data: JSON.stringify(query),
+            async: false,
+            error: function() {
+              alert('Cannot submit job');
+            },
+            success: function(job) {
+            }
+          });
+        }
+      });
+    }
+  });
+}
+
+
+$('#patient').live('pagebeforeshow', function() {
+  ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-patient', 'patient-info');
+});
+
+$('#study').live('pagebeforeshow', function() {
+  ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-study', 'study-info');
+});
+
+$('#series').live('pagebeforeshow', function() {
+  ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-series', 'series-info');
+});
+
+$('#instance').live('pagebeforeshow', function() {
+  ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-instance', 'instance-info');
+});
+
+$('#lookup').live('pagebeforeshow', function() {
+  $('#open-dicomweb-client').remove();
+  
+  var b = $('<fieldset>')
+      .attr('id', 'open-dicomweb-client')
+      .addClass('ui-grid-b')
+      .append($('<div>')
+              .addClass('ui-block-a'))
+      .append($('<div>')
+              .addClass('ui-block-b')
+              .append($('<a>')
+                      .attr('id', 'coucou')
+                      .attr('data-role', 'button')
+                      .attr('href', '#')
+                      .attr('data-icon', 'forward')
+                      .attr('data-theme', 'a')
+                      .text('Open DICOMweb client')
+                      .button()
+                      .click(function(e) {
+                        window.open('../${DICOMWEB_ROOT}/app/client/index.html');
+                      })));
+  
+  b.insertAfter($('#lookup-result'));
+});
+
--- a/Plugin/Plugin.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/Plugin.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,73 +18,35 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-
-#include "Plugin.h"
-
+#include "DicomWebClient.h"
+#include "DicomWebServers.h"
+#include "GdcmParsedDicomFile.h"
 #include "QidoRs.h"
 #include "StowRs.h"
-#include "DicomWebClient.h"
 #include "WadoRs.h"
 #include "WadoUri.h"
-#include "Configuration.h"
-#include "DicomWebServers.h"
-
-#include "../Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
-#include "../Orthanc/Core/Toolbox.h"
 
-#include <gdcmDictEntry.h>
-#include <gdcmDict.h>
-#include <gdcmDicts.h>
-#include <gdcmGlobal.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/SystemToolbox.h>
+#include <Core/Toolbox.h>
 
+#include <EmbeddedResources.h>
 
-// Global state
-const gdcm::Dict* dictionary_ = NULL;
+#include <boost/algorithm/string/predicate.hpp>
 
 
-void SwitchStudies(OrthancPluginRestOutput* output,
-                   const char* url,
-                   const OrthancPluginHttpRequest* request)
-{
-  switch (request->method)
-  {
-    case OrthancPluginHttpMethod_Get:
-      // This is QIDO-RS
-      SearchForStudies(output, url, request);
-      break;
+static const char* const HAS_DELETE = "HasDelete";
 
-    case OrthancPluginHttpMethod_Post:
-      // This is STOW-RS
-      StowCallback(output, url, request);
-      break;
-
-    default:
-      OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "GET,POST");
-      break;
-  }
-}
 
 
-void SwitchStudy(OrthancPluginRestOutput* output,
-                 const char* url,
-                 const OrthancPluginHttpRequest* request)
+bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key)
 {
-  switch (request->method)
+  for (uint32_t i = 0; i < request->getCount; i++)
   {
-    case OrthancPluginHttpMethod_Get:
-      // This is WADO-RS
-      RetrieveDicomStudy(output, url, request);
-      break;
-
-    case OrthancPluginHttpMethod_Post:
-      // This is STOW-RS
-      StowCallback(output, url, request);
-      break;
-
-    default:
-      OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "GET,POST");
-      break;
+    if (strcmp(key, request->getKeys[i]) == 0)
+      return true;
   }
+  return false;
 }
 
 
@@ -91,7 +54,7 @@
                  const char* url,
                  const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -102,23 +65,100 @@
     std::list<std::string> servers;
     OrthancPlugins::DicomWebServers::GetInstance().ListServers(servers);
 
-    Json::Value json = Json::arrayValue;
-    for (std::list<std::string>::const_iterator it = servers.begin(); it != servers.end(); ++it)
+    if (RequestHasKey(request, "expand"))
     {
-      json.append(*it);
-    }
+      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
+        server.FormatPublic(jsonServer);
+        result[*it] = jsonServer;
+      }
 
-    std::string answer = json.toStyledString(); 
-    OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+      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::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  switch (request->method)
+  {
+    case OrthancPluginHttpMethod_Get:
+    {
+      // Make sure the server does exist
+      const Orthanc::WebServiceParameters& server = 
+        OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]);
+
+      Json::Value json = Json::arrayValue;
+      json.append("get");
+      json.append("retrieve");
+      json.append("stow");
+      json.append("wado");
+      json.append("qido");
+
+      if (server.GetBooleanUserProperty(HAS_DELETE, false))
+      {
+        json.append("delete");
+      }
+
+      std::string answer = json.toStyledString(); 
+      OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+      break;
+    }
+    
+    case OrthancPluginHttpMethod_Delete:
+    {
+      OrthancPlugins::DicomWebServers::GetInstance().DeleteServer(request->groups[0]);
+      std::string answer = "{}";
+      OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+      break;
+    }
+    
+    case OrthancPluginHttpMethod_Put:
+    {
+      Json::Value body;
+      OrthancPlugins::ParseJsonBody(body, request);
+      
+      Orthanc::WebServiceParameters parameters(body);
+      
+      OrthancPlugins::DicomWebServers::GetInstance().SetServer(request->groups[0], parameters);
+      std::string answer = "{}";
+      OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
+      break;
+    }
+    
+    default:
+      OrthancPluginSendMethodNotAllowed(context, output, "GET,PUT,DELETE");
+      break;
+  }
+}
+
+
+
+void GetClientInformation(OrthancPluginRestOutput* output,
+                          const char* /*url*/,
+                          const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -126,24 +166,298 @@
   }
   else
   {
-    // Make sure the server does exist
-    OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]);
+    Json::Value info = Json::objectValue;
+    info["DicomWebRoot"] = OrthancPlugins::Configuration::GetDicomWebRoot();
+    info["OrthancApiRoot"] = OrthancPlugins::Configuration::GetOrthancApiRoot();
 
-    Json::Value json = Json::arrayValue;
-    json.append("get");
-    json.append("retrieve");
-    json.append("stow");
-
-    std::string answer = json.toStyledString(); 
+    std::string answer = info.toStyledString();
     OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
   }
 }
 
 
+
+void QidoClient(OrthancPluginRestOutput* output,
+                const char* /*url*/,
+                const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+  }
+  else
+  {
+    Json::Value answer;
+    GetFromServer(answer, request);
+    
+    if (answer.type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    Json::Value result = Json::arrayValue;
+    for (Json::Value::ArrayIndex i = 0; i < answer.size(); i++)
+    {
+      if (answer[i].type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      Json::Value::Members tags = answer[i].getMemberNames();
+
+      Json::Value item = Json::objectValue;
+      
+      for (size_t j = 0; j < tags.size(); j++)
+      {
+        Orthanc::DicomTag tag(0, 0);
+        if (Orthanc::DicomTag::ParseHexadecimal(tag, tags[j].c_str()))
+        {
+          Json::Value value = Json::objectValue;
+          value["Group"] = tag.GetGroup();
+          value["Element"] = tag.GetElement();
+          
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+          OrthancPlugins::OrthancString name;
+
+          name.Assign(OrthancPluginGetTagName(context, tag.GetGroup(), tag.GetElement(), NULL));
+          if (name.GetContent() != NULL)
+          {
+            value["Name"] = std::string(name.GetContent());
+          }
+#endif
+
+          const Json::Value& source = answer[i][tags[j]];
+          if (source.type() != Json::objectValue ||
+              !source.isMember("vr") ||
+              source["vr"].type() != Json::stringValue)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+          }
+
+          value["vr"] = source["vr"].asString();
+
+          if (source.isMember("Value") &&
+              source["Value"].type() == Json::arrayValue &&
+              source["Value"].size() >= 1)
+          {
+            const Json::Value& content = source["Value"][0];
+
+            switch (content.type())
+            {
+              case Json::stringValue:
+                value["Value"] = content.asString();
+                break;
+
+              case Json::objectValue:
+                if (content.isMember("Alphabetic") &&
+                    content["Alphabetic"].type() == Json::stringValue)
+                {
+                  value["Value"] = content["Alphabetic"].asString();
+                }
+                break;
+
+              default:
+                break;
+            }
+          }
+
+          item[tags[j]] = value;
+        }
+      }
+
+      result.append(item);
+    }
+
+    std::string tmp = result.toStyledString();
+    OrthancPluginAnswerBuffer(context, output, tmp.c_str(), tmp.size(), "application/json");
+  }
+}
+
+
+void DeleteClient(OrthancPluginRestOutput* output,
+                const char* /*url*/,
+                const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "POST");
+  }
+  else
+  {
+    static const char* const LEVEL = "Level";
+    static const char* const SERIES_INSTANCE_UID = "SeriesInstanceUID";
+    static const char* const STUDY_INSTANCE_UID = "StudyInstanceUID";
+    static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
+
+    const std::string serverName = request->groups[0];
+
+    const Orthanc::WebServiceParameters& server = 
+      OrthancPlugins::DicomWebServers::GetInstance().GetServer(serverName);
+
+    if (!server.GetBooleanUserProperty(HAS_DELETE, false))
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_BadFileFormat,
+        "Cannot delete on DICOMweb server, check out property \"" + std::string(HAS_DELETE) + "\": " + serverName);
+    }
+
+    Json::Value body;
+    OrthancPlugins::ParseJsonBody(body, request);
+
+    if (body.type() != Json::objectValue ||
+        !body.isMember(LEVEL) ||
+        !body.isMember(STUDY_INSTANCE_UID) ||
+        body[LEVEL].type() != Json::stringValue ||
+        body[STUDY_INSTANCE_UID].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_BadFileFormat,
+        "The request body must contain a JSON object with fields \"Level\" and \"StudyInstanceUID\"");
+    }
+
+    Orthanc::ResourceType level = Orthanc::StringToResourceType(body[LEVEL].asCString());
+
+    const std::string study = body[STUDY_INSTANCE_UID].asString();
+
+    std::string series;    
+    if (level == Orthanc::ResourceType_Series ||
+        level == Orthanc::ResourceType_Instance)
+    {
+      if (!body.isMember(SERIES_INSTANCE_UID) ||
+          body[SERIES_INSTANCE_UID].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_BadFileFormat,
+          "The request body must contain the field \"SeriesInstanceUID\"");
+      }
+      else
+      {
+        series = body[SERIES_INSTANCE_UID].asString();
+      }
+    }
+
+    std::string instance;    
+    if (level == Orthanc::ResourceType_Instance)
+    {
+      if (!body.isMember(SOP_INSTANCE_UID) ||
+          body[SOP_INSTANCE_UID].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_BadFileFormat,
+          "The request body must contain the field \"SOPInstanceUID\"");
+      }
+      else
+      {
+        instance = body[SOP_INSTANCE_UID].asString();
+      }
+    }
+
+    std::string uri;
+    switch (level)
+    {
+      case Orthanc::ResourceType_Study:
+        uri = "/studies/" + study;
+        break;
+
+      case Orthanc::ResourceType_Series:
+        uri = "/studies/" + study + "/series/" + series;
+        break;
+
+      case Orthanc::ResourceType_Instance:
+        uri = "/studies/" + study + "/series/" + series + "/instances/" + instance;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::HttpClient client;
+    std::map<std::string, std::string> userProperties;
+    OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient(client, userProperties, serverName, uri);
+    client.SetMethod(OrthancPluginHttpMethod_Delete);
+    client.Execute();
+
+    std::string tmp = "{}";
+    OrthancPluginAnswerBuffer(context, output, tmp.c_str(), tmp.size(), "application/json");
+  }
+}
+
+
+
+
+
+static bool DisplayPerformanceWarning(OrthancPluginContext* context)
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context, "Performance warning in DICOMweb: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
+}
+
+
+template <enum Orthanc::EmbeddedResources::DirectoryResourceId folder>
+void ServeEmbeddedFolder(OrthancPluginRestOutput* output,
+                         const char* url,
+                         const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    std::string path = "/" + std::string(request->groups[0]);
+    const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path));
+
+    std::string s;
+    Orthanc::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str());
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime);
+  }
+}
+
+
+#if ORTHANC_STANDALONE == 0
+void ServeDicomWebClient(OrthancPluginRestOutput* output,
+                         const char* url,
+                         const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    const std::string path = std::string(DICOMWEB_CLIENT_PATH) + std::string(request->groups[0]);
+    const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path));
+
+    OrthancPlugins::MemoryBuffer f;
+    f.ReadFile(path);
+
+    OrthancPluginAnswerBuffer(context, output, f.GetData(), f.GetSize(), mime);
+  }
+}
+#endif
+
+
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
+    assert(DisplayPerformanceWarning(context));
+
+    OrthancPlugins::SetGlobalContext(context);
+    Orthanc::Logging::Initialize(context);
+
     /* Check the version of the Orthanc core */
     if (OrthancPluginCheckVersion(context) == 0)
     {
@@ -157,79 +471,131 @@
       return -1;
     }
 
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 0
+    LOG(WARNING) << "Performance warning in DICOMweb: The plugin was compiled against "
+                 << "Orthanc SDK <= 1.5.6. STOW and WADO chunked transfers will be entirely stored in RAM.";
+#endif
+
     OrthancPluginSetDescription(context, "Implementation of DICOMweb (QIDO-RS, STOW-RS and WADO-RS) and WADO-URI.");
 
     try
     {
       // Read the configuration
-      OrthancPlugins::Configuration::Initialize(context);
+      OrthancPlugins::Configuration::Initialize();
 
       // Initialize GDCM
-      dictionary_ = &gdcm::Global::GetInstance().GetDicts().GetPublicDict();
+      OrthancPlugins::GdcmParsedDicomFile::Initialize();
 
       // Configure the DICOMweb callbacks
       if (OrthancPlugins::Configuration::GetBooleanValue("Enable", true))
       {
-        std::string root = OrthancPlugins::Configuration::GetRoot();
+        std::string root = OrthancPlugins::Configuration::GetDicomWebRoot();
         assert(!root.empty() && root[root.size() - 1] == '/');
 
-        OrthancPlugins::Configuration::LogWarning("URI to the DICOMweb REST API: " + root);
+        OrthancPlugins::LogWarning("URI to the DICOMweb REST API: " + root);
+
+        OrthancPlugins::ChunkedRestRegistration<
+          SearchForStudies /* TODO => Rename as QIDO-RS */,
+          OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies");
+
+        OrthancPlugins::ChunkedRestRegistration<
+          RetrieveDicomStudy /* TODO => Rename as WADO-RS */,
+          OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies/([^/]*)");
+
+        OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "instances", true);
+        OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "series", 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);
+
+        OrthancPlugins::RegisterRestCallback<ListServers>(root + "servers", true);
+        OrthancPlugins::RegisterRestCallback<ListServerOperations>(root + "servers/([^/]*)", true);
+        OrthancPlugins::RegisterRestCallback<StowClient>(root + "servers/([^/]*)/stow", true);
+        OrthancPlugins::RegisterRestCallback<WadoRetrieveClient>(root + "servers/([^/]*)/wado", true);
+        OrthancPlugins::RegisterRestCallback<GetFromServer>(root + "servers/([^/]*)/get", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFromServer>(root + "servers/([^/]*)/retrieve", true);
+        OrthancPlugins::RegisterRestCallback<QidoClient>(root + "servers/([^/]*)/qido", true);
+        OrthancPlugins::RegisterRestCallback<DeleteClient>(root + "servers/([^/]*)/delete", true);
+
+        OrthancPlugins::RegisterRestCallback
+          <ServeEmbeddedFolder<Orthanc::EmbeddedResources::JAVASCRIPT_LIBS> >
+          (root + "app/libs/(.*)", true);
 
-        OrthancPlugins::RegisterRestCallback<SearchForInstances>(context, root + "instances", true);
-        OrthancPlugins::RegisterRestCallback<SearchForSeries>(context, root + "series", true);    
-        OrthancPlugins::RegisterRestCallback<SwitchStudies>(context, root + "studies", true);
-        OrthancPlugins::RegisterRestCallback<SwitchStudy>(context, root + "studies/([^/]*)", true);
-        OrthancPlugins::RegisterRestCallback<SearchForInstances>(context, root + "studies/([^/]*)/instances", true);    
-        OrthancPlugins::RegisterRestCallback<RetrieveStudyMetadata>(context, root + "studies/([^/]*)/metadata", true);
-        OrthancPlugins::RegisterRestCallback<SearchForSeries>(context, root + "studies/([^/]*)/series", true);    
-        OrthancPlugins::RegisterRestCallback<RetrieveDicomSeries>(context, root + "studies/([^/]*)/series/([^/]*)", true);
-        OrthancPlugins::RegisterRestCallback<SearchForInstances>(context, root + "studies/([^/]*)/series/([^/]*)/instances", true);    
-        OrthancPlugins::RegisterRestCallback<RetrieveDicomInstance>(context, root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveBulkData>(context, root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/bulk/(.*)", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveInstanceMetadata>(context, root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/metadata", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveSeriesMetadata>(context, root + "studies/([^/]*)/series/([^/]*)/metadata", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveFrames>(context, root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveFrames>(context, root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)", true);
+        OrthancPlugins::RegisterRestCallback<GetClientInformation>(root + "info", true);
+
+        OrthancPlugins::RegisterRestCallback<RetrieveStudyRendered>(root + "studies/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveSeriesRendered>(root + "studies/([^/]*)/series/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true);
+
+
+        // Extend the default Orthanc Explorer with custom JavaScript for STOW client
+        std::string explorer;
 
-        OrthancPlugins::RegisterRestCallback<ListServers>(context, root + "servers", true);
-        OrthancPlugins::RegisterRestCallback<ListServerOperations>(context, root + "servers/([^/]*)", true);
-        OrthancPlugins::RegisterRestCallback<StowClient>(context, root + "servers/([^/]*)/stow", true);
-        OrthancPlugins::RegisterRestCallback<GetFromServer>(context, root + "servers/([^/]*)/get", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveFromServer>(context, root + "servers/([^/]*)/retrieve", true);
+#if ORTHANC_STANDALONE == 1
+        Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
+        OrthancPlugins::RegisterRestCallback
+          <ServeEmbeddedFolder<Orthanc::EmbeddedResources::WEB_APPLICATION> >
+          (root + "app/client/(.*)", true);
+#else
+        Orthanc::SystemToolbox::ReadFile(explorer, std::string(DICOMWEB_CLIENT_PATH) + "../Plugin/OrthancExplorer.js");
+        OrthancPlugins::RegisterRestCallback<ServeDicomWebClient>(root + "app/client/(.*)", true);
+#endif
+
+        {
+          if (root.size() < 2 ||
+              root[0] != '/' ||
+              root[root.size() - 1] != '/')
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+
+          std::map<std::string, std::string> dictionary;
+          dictionary["DICOMWEB_ROOT"] = root.substr(1, root.size() - 2);  // Remove heading and trailing slashes
+          std::string configured = Orthanc::Toolbox::SubstituteVariables(explorer, dictionary);
+
+          OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), configured.c_str());
+        }
+        
+        
+        std::string uri = root + "app/client/index.html";
+        OrthancPluginSetRootUri(context, uri.c_str());
       }
       else
       {
-        OrthancPlugins::Configuration::LogWarning("DICOMweb support is disabled");
+        OrthancPlugins::LogWarning("DICOMweb support is disabled");
       }
 
       // Configure the WADO callback
       if (OrthancPlugins::Configuration::GetBooleanValue("EnableWado", true))
       {
         std::string wado = OrthancPlugins::Configuration::GetWadoRoot();
-        OrthancPlugins::Configuration::LogWarning("URI to the WADO-URI API: " + wado);
+        OrthancPlugins::LogWarning("URI to the WADO-URI API: " + wado);
 
-        OrthancPlugins::RegisterRestCallback<WadoUriCallback>(context, wado, true);
+        OrthancPlugins::RegisterRestCallback<WadoUriCallback>(wado, true);
       }
       else
       {
-        OrthancPlugins::Configuration::LogWarning("WADO-URI support is disabled");
+        OrthancPlugins::LogWarning("WADO-URI support is disabled");
       }
     }
-    catch (OrthancPlugins::PluginException& e)
-    {
-      OrthancPlugins::Configuration::LogError("Exception while initializing the DICOMweb plugin: " + 
-                                              std::string(e.GetErrorDescription(context)));
-      return -1;
-    }
     catch (Orthanc::OrthancException& e)
     {
-      OrthancPlugins::Configuration::LogError("Exception while initializing the DICOMweb plugin: " + 
-                                              std::string(e.What()));
+      OrthancPlugins::LogError("Exception while initializing the DICOMweb plugin: " + 
+                               std::string(e.What()));
       return -1;
     }
     catch (...)
     {
-      OrthancPlugins::Configuration::LogError("Exception while initializing the DICOMweb plugin");
+      OrthancPlugins::LogError("Exception while initializing the DICOMweb plugin");
       return -1;
     }
 
--- a/Plugin/Plugin.h	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
-
-#include <gdcmDict.h>
-
-// TODO Remove this file
-
-// Global state
-extern const gdcm::Dict* dictionary_;
--- a/Plugin/QidoRs.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/QidoRs.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,152 +21,116 @@
 
 #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 "DicomWebFormatter.h"
 
-#include <gdcmTag.h>
+#include <Core/DicomFormat/DicomMap.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
 {
-  static std::string FormatOrthancTag(const gdcm::Tag& tag)
-  {
-    char b[16];
-    sprintf(b, "%04x,%04x", tag.GetGroup(), tag.GetElement());
-    return std::string(b);
-  }
-
-
-  static std::string GetOrthancTag(const Json::Value& source,
-                                   const gdcm::Tag& tag,
-                                   const std::string& defaultValue)
-  {
-    std::string s = FormatOrthancTag(tag);
-      
-    if (source.isMember(s) &&
-        source[s].type() == Json::objectValue &&
-        source[s].isMember("Value") &&
-        source[s].isMember("Type") &&
-        source[s]["Type"] == "String" &&
-        source[s]["Value"].type() == Json::stringValue)
-    {
-      return source[s]["Value"].asString();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  enum QueryLevel
-  {
-    QueryLevel_Study,
-    QueryLevel_Series,
-    QueryLevel_Instance
-  };
-
-
   class ModuleMatcher
   {
   public:
-    typedef std::map<gdcm::Tag, std::string>  Filters;
+    typedef std::map<Orthanc::DicomTag, std::string>  Filters;
 
   private:
-    bool                  fuzzy_;
-    unsigned int          offset_;
-    unsigned int          limit_;
-    std::list<gdcm::Tag>  includeFields_;
-    bool                  includeAllFields_;
-    Filters               filters_;
+    bool                          fuzzy_;
+    unsigned int                  offset_;
+    unsigned int                  limit_;
+    std::list<Orthanc::DicomTag>  includeFields_;
+    bool                          includeAllFields_;
+    Filters                       filters_;
+    bool                          filteredStudyInstanceUid_;
+    bool                          filteredSeriesInstanceUid_;
 
 
-    static void AddResultAttributesForLevel(std::list<gdcm::Tag>& result,
-                                            QueryLevel level)
+    static void AddResultAttributesForLevel(std::set<Orthanc::DicomTag>& result,
+                                            Orthanc::ResourceType level)
     {
       switch (level)
       {
-        case QueryLevel_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  => SPECIAL CASE
-          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  => SPECIAL CASE
-          //result.push_back(gdcm::Tag(0x0020, 0x1208));  // Number of Study Related Instances  => SPECIAL CASE
+        case Orthanc::ResourceType_Study:
+          // http://dicom.nema.org/medical/dicom/2019a/output/html/part18.html#table_6.7.1-2
+          //result.insert(Orthanc::DicomTag(0x0008, 0x0005));  // Specific Character Set  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0008, 0x0020));  // Study Date
+          result.insert(Orthanc::DicomTag(0x0008, 0x0030));  // Study Time
+          result.insert(Orthanc::DicomTag(0x0008, 0x0050));  // Accession Number
+          result.insert(Orthanc::DicomTag(0x0008, 0x0056));  // Instance Availability
+          //result.insert(Orthanc::DicomTag(0x0008, 0x0061));  // Modalities in Study  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0008, 0x0090));  // Referring Physician's Name
+          result.insert(Orthanc::DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+          //result.insert(Orthanc::DicomTag(0x0008, 0x1190));  // Retrieve URL  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0010, 0x0010));  // Patient's Name
+          result.insert(Orthanc::DicomTag(0x0010, 0x0020));  // Patient ID
+          result.insert(Orthanc::DicomTag(0x0010, 0x0030));  // Patient's Birth Date
+          result.insert(Orthanc::DicomTag(0x0010, 0x0040));  // Patient's Sex
+          result.insert(Orthanc::DicomTag(0x0020, 0x000D));  // Study Instance UID
+          result.insert(Orthanc::DicomTag(0x0020, 0x0010));  // Study ID
+          //result.insert(Orthanc::DicomTag(0x0020, 0x1206));  // Number of Study Related Series  => SPECIAL CASE
+          //result.insert(Orthanc::DicomTag(0x0020, 0x1208));  // Number of Study Related Instances  => SPECIAL CASE
           break;
 
-        case QueryLevel_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, 0x0060));  // 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  => SPECIAL CASE
-          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
+        case Orthanc::ResourceType_Series:
+          // http://dicom.nema.org/medical/dicom/2019a/output/html/part18.html#table_6.7.1-2a
+          //result.insert(Orthanc::DicomTag(0x0008, 0x0005));  // Specific Character Set  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0008, 0x0060));  // Modality
+          result.insert(Orthanc::DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+          result.insert(Orthanc::DicomTag(0x0008, 0x103E));  // Series Description
+          //result.insert(Orthanc::DicomTag(0x0008, 0x1190));  // Retrieve URL  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0020, 0x000E));  // Series Instance UID
+          result.insert(Orthanc::DicomTag(0x0020, 0x0011));  // Series Number
+          //result.insert(Orthanc::DicomTag(0x0020, 0x1209));  // Number of Series Related Instances  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0040, 0x0244));  // Performed Procedure Step Start Date
+          result.insert(Orthanc::DicomTag(0x0040, 0x0245));  // Performed Procedure Step Start Time
+          result.insert(Orthanc::DicomTag(0x0040, 0x0275));  // Request Attribute Sequence
           break;
 
-        case QueryLevel_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
+        case Orthanc::ResourceType_Instance:
+          // http://dicom.nema.org/medical/dicom/2019a/output/html/part18.html#table_6.7.1-2b
+          //result.insert(Orthanc::DicomTag(0x0008, 0x0005));  // Specific Character Set  => SPECIAL CASE
+          result.insert(Orthanc::DicomTag(0x0008, 0x0016));  // SOP Class UID
+          result.insert(Orthanc::DicomTag(0x0008, 0x0018));  // SOP Instance UID
+          result.insert(Orthanc::DicomTag(0x0008, 0x0056));  // Instance Availability
+          result.insert(Orthanc::DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+          result.insert(Orthanc::DicomTag(0x0008, 0x1190));  // Retrieve URL
+          result.insert(Orthanc::DicomTag(0x0020, 0x0013));  // Instance Number
+          result.insert(Orthanc::DicomTag(0x0028, 0x0010));  // Rows
+          result.insert(Orthanc::DicomTag(0x0028, 0x0011));  // Columns
+          result.insert(Orthanc::DicomTag(0x0028, 0x0100));  // Bits Allocated
+          result.insert(Orthanc::DicomTag(0x0028, 0x0008));  // Number of Frames
           break;
 
         default:
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
     }
 
 
   public:
-    ModuleMatcher(const OrthancPluginHttpRequest* request) :
-    fuzzy_(false),
-    offset_(0),
-    limit_(0),
-    includeAllFields_(false)
+    explicit ModuleMatcher(const OrthancPluginHttpRequest* request) :
+      fuzzy_(false),
+      offset_(0),
+      limit_(0),
+      includeAllFields_(false),
+      filteredStudyInstanceUid_(false),
+      filteredSeriesInstanceUid_(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")
         {
@@ -187,13 +152,14 @@
           }
           else
           {
-            OrthancPlugins::Configuration::LogError("Not a proper value for fuzzy matching (true or false): " + value);
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_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;
           }
@@ -202,17 +168,38 @@
             // 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(OrthancPlugins::ParseTag(*dictionary_, tags[i]));
+              Orthanc::DicomTag tag(0, 0);
+              if (OrthancPlugins::ParseTag(tag, tags[i]))
+              {
+                includeFields_.push_back(tag);
+              }
             }
           }
         }
         else
         {
-          filters_[OrthancPlugins::ParseTag(*dictionary_, key)] = value;
+          Orthanc::DicomTag tag(0, 0);
+          if (OrthancPlugins::ParseTag(tag, key))
+          {
+            // The following lines are new in DICOMweb > 1.0, and
+            // allow to query against a list of multiple values
+            // http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part18/sect_6.7.html#sect_6.7.1.1.1
+            boost::replace_all(value, "\\", "");  // Remove backslashes from source request
+
+            // Replace commas by backslashes
+            boost::replace_all(value, ",", "\\");
+            boost::replace_all(value, "%2c", "\\");
+            boost::replace_all(value, "%2C", "\\");
+            
+            AddFilter(tag, value, false);
+          }
         }
       }
+
+      OrthancPlugins::LogInfo("Arguments of QIDO-RS request:" + args);
     }
 
     unsigned int GetLimit() const
@@ -225,10 +212,23 @@
       return offset_;
     }
 
-    void AddFilter(const gdcm::Tag& tag,
-                   const std::string& constraint)
+    void AddFilter(const Orthanc::DicomTag& tag,
+                   const std::string& constraint,
+                   bool isFromPath)
     {
       filters_[tag] = constraint;
+
+      if (!isFromPath)
+      {
+        if (tag == Orthanc::DICOM_TAG_STUDY_INSTANCE_UID)
+        {
+          filteredStudyInstanceUid_ = true;
+        }
+        else if (tag == Orthanc::DICOM_TAG_SERIES_INSTANCE_UID)
+        {
+          filteredSeriesInstanceUid_ = true;
+        }
+      }
     }
 
     void Print(std::ostream& out) const 
@@ -238,76 +238,117 @@
       {
         printf("Filter [%04x,%04x] = [%s]\n", it->first.GetGroup(), it->first.GetElement(), it->second.c_str());
       }
+      printf("QIDO on StudyInstanceUID: %d\n", filteredStudyInstanceUid_);
+      printf("QIDO on SeriesInstanceUID: %d\n\n", filteredSeriesInstanceUid_);
     }
 
     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;
 
         default:
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      bool caseSensitive;
+      if (OrthancPlugins::Configuration::LookupBooleanValue(caseSensitive, "QidoCaseSensitive"))
+      {
+        result["CaseSensitive"] = caseSensitive;
       }
 
       result["Expand"] = false;
-      result["CaseSensitive"] = 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"][FormatOrthancTag(it->first)] = it->second;
+        result["Query"][it->first.Format()] = it->second;
       }
     }
 
 
-    void ComputeDerivedTags(Filters& target,
-                            QueryLevel level,
+    void ComputeDerivedTags(Orthanc::DicomMap& target,
+                            std::string& someInstance,
+                            Orthanc::ResourceType level,
                             const std::string& resource) const
     {
-      OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-      target.clear();
-
+      static const char* const INSTANCES = "Instances";      
+      static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
+      static const char* const MODALITY = "Modality";
+      
       switch (level)
       {
-        case QueryLevel_Study:
+        case Orthanc::ResourceType_Instance:
+          someInstance = resource;
+          break;
+
+        case Orthanc::ResourceType_Study:
         {
-          Json::Value series, instances;
-          if (OrthancPlugins::RestApiGetJson(series, context, "/studies/" + resource + "/series?expand", false) &&
-              OrthancPlugins::RestApiGetJson(instances, context, "/studies/" + resource + "/instances", false))
+          Json::Value series;
+          if (OrthancPlugins::RestApiGet(series, "/studies/" + resource + "/series?expand", false) &&
+              series.type() == Json::arrayValue)
           {
-            // Number of Study Related Series
-            target[gdcm::Tag(0x0020, 0x1206)] = boost::lexical_cast<std::string>(series.size());
-
-            // Number of Study Related Instances
-            target[gdcm::Tag(0x0020, 0x1208)] = boost::lexical_cast<std::string>(instances.size());
-
-            // Collect the Modality of all the child series
+            // Collect the Modality of all the child series, and 
             std::set<std::string> modalities;
+            unsigned int countInstances = 0;
+            
             for (Json::Value::ArrayIndex i = 0; i < series.size(); i++)
             {
-              if (series[i].isMember("MainDicomTags") &&
-                  series[i]["MainDicomTags"].isMember("Modality"))
+              if (series[i].type() == Json::objectValue)
               {
-                modalities.insert(series[i]["MainDicomTags"]["Modality"].asString());
+                if (series[i].isMember(MAIN_DICOM_TAGS) &&
+                    series[i][MAIN_DICOM_TAGS].type() == Json::objectValue &&
+                    series[i][MAIN_DICOM_TAGS].isMember(MODALITY) &&
+                    series[i][MAIN_DICOM_TAGS][MODALITY].type() == Json::stringValue)
+                {
+                  modalities.insert(series[i][MAIN_DICOM_TAGS][MODALITY].asString());
+                }
+                
+                if (series[i].isMember(INSTANCES) &&
+                    series[i][INSTANCES].type() == Json::arrayValue)
+                {
+                  if (series[i][INSTANCES].size() > 0 &&
+                      series[i][INSTANCES][0].type() == Json::stringValue)
+                  {
+                    someInstance = series[i][INSTANCES][0].asString();
+                  }
+                  
+                  countInstances += series[i][INSTANCES].size();
+                }
               }
             }
 
+            target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES,
+                            boost::lexical_cast<std::string>(series.size()), false);
+            
+            target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES,
+                            boost::lexical_cast<std::string>(countInstances), false);
+            
             std::string s;
             for (std::set<std::string>::const_iterator 
                    it = modalities.begin(); it != modalities.end(); ++it)
@@ -320,46 +361,63 @@
               s += *it;
             }
 
-            target[gdcm::Tag(0x0008, 0x0061)] = s;  // Modalities in Study
+            target.SetValue(Orthanc::DICOM_TAG_MODALITIES_IN_STUDY, s, false);
           }
           else
           {
-            target[gdcm::Tag(0x0008, 0x0061)] = "";   // Modalities in Study
-            target[gdcm::Tag(0x0020, 0x1206)] = "0";  // Number of Study Related Series
-            target[gdcm::Tag(0x0020, 0x1208)] = "0";  // Number of Study Related Instances
+            target.SetValue(Orthanc::DICOM_TAG_MODALITIES_IN_STUDY, "", false);
+            target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, "0", false);
+            target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, "0", false);
           }
 
           break;
         }
 
-        case QueryLevel_Series:
+        case Orthanc::ResourceType_Series:
         {
-          Json::Value instances;
-          if (OrthancPlugins::RestApiGetJson(instances, context, "/series/" + resource + "/instances", false))
+          Json::Value series;
+          if (OrthancPlugins::RestApiGet(series, "/series/" + resource, false) &&
+              series.type() == Json::objectValue &&
+              series.isMember(INSTANCES) &&
+              series[INSTANCES].type() == Json::arrayValue)
           {
+            if (series[INSTANCES].size() > 0 &&
+                series[INSTANCES][0].type() == Json::stringValue)
+            {
+              someInstance = series[INSTANCES][0].asString();
+            }
+            
             // Number of Series Related Instances
-            target[gdcm::Tag(0x0020, 0x1209)] = boost::lexical_cast<std::string>(instances.size());
+            target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, 
+                            boost::lexical_cast<std::string>(series[INSTANCES].size()), false);
           }
           else
           {
-            target[gdcm::Tag(0x0020, 0x1209)] = "0";  // Number of Series Related Instances
+            // Should never happen
+            target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, "0", false);
           }
 
           break;
         }
 
         default:
-          break;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
     }                              
 
 
-    void ExtractFields(gdcm::DataSet& result,
-                       const OrthancPlugins::ParsedDicomFile& dicom,
+    void ExtractFields(Orthanc::DicomMap& result,
+                       const Orthanc::DicomMap& source,
                        const std::string& wadoBase,
-                       QueryLevel level) const
+                       Orthanc::ResourceType level) const
     {
-      std::list<gdcm::Tag> fields = includeFields_;
+      std::set<Orthanc::DicomTag> fields;
+
+      for (std::list<Orthanc::DicomTag>::const_iterator
+             it = includeFields_.begin(); it != includeFields_.end(); ++it)
+      {
+        fields.insert(*it);
+      }
 
       // The list of attributes for this query level
       AddResultAttributesForLevel(fields, level);
@@ -368,125 +426,54 @@
       for (Filters::const_iterator it = filters_.begin();
            it != filters_.end(); ++it)
       {
-        fields.push_back(it->first);
+        fields.insert(it->first);
       }
 
       // 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 (!filteredStudyInstanceUid_ &&
+          (level == Orthanc::ResourceType_Instance ||
+           level == Orthanc::ResourceType_Series))
       {
-        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 (!filteredSeriesInstanceUid_ &&
+          level == Orthanc::ResourceType_Instance)
       {
-        AddResultAttributesForLevel(fields, QueryLevel_Series);
+        AddResultAttributesForLevel(fields, Orthanc::ResourceType_Series);
       }
 
       // Copy all the required fields to the target
-      for (std::list<gdcm::Tag>::const_iterator
+      for (std::set<Orthanc::DicomTag>::const_iterator
              it = fields.begin(); it != fields.end(); ++it)
       {
-        if (dicom.GetDataSet().FindDataElement(*it))
+        std::string value;
+        if (source.LookupStringValue(value, *it, false /* no binary */))
         {
-          const gdcm::DataElement& element = dicom.GetDataSet().GetDataElement(*it);
-          result.Replace(element);
+          result.SetValue(*it, value, false);
         }
       }
 
       // Set the retrieve URL for WADO-RS
-      std::string url = (wadoBase + "studies/" + 
-                         dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, "", true));
-
-      if (level == QueryLevel_Series || level == QueryLevel_Instance)
-      {
-        url += "/series/" + dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, "", true);
-      }
-
-      if (level == QueryLevel_Instance)
-      {
-        url += "/instances/" + dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID, "", true);
-      }
-    
-      gdcm::DataElement element(OrthancPlugins::DICOM_TAG_RETRIEVE_URL);
-      element.SetByteValue(url.c_str(), url.size());
-      result.Replace(element);
-    }
+      std::string url = (wadoBase + "studies/" +
+                         source.GetStringValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, "", false));
 
-
-    void ExtractFields(Json::Value& result,
-                       const Json::Value& source,
-                       const std::string& wadoBase,
-                       QueryLevel level) const
-    {
-      result = Json::objectValue;
-      std::list<gdcm::Tag> fields = includeFields_;
-
-      // The list of attributes for this query level
-      AddResultAttributesForLevel(fields, level);
-
-      // All other attributes passed as query keys
-      for (Filters::const_iterator it = filters_.begin();
-           it != filters_.end(); ++it)
+      if (level == Orthanc::ResourceType_Series || 
+          level == Orthanc::ResourceType_Instance)
       {
-        fields.push_back(it->first);
+        url += "/series/" + source.GetStringValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, "", false);
       }
 
-      // 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()
-        )
-      {
-        AddResultAttributesForLevel(fields, QueryLevel_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)
       {
-        AddResultAttributesForLevel(fields, QueryLevel_Series);
-      }
-
-      // Copy all the required fields to the target
-      for (std::list<gdcm::Tag>::const_iterator
-             it = fields.begin(); it != fields.end(); ++it)
-      {
-        std::string tag = FormatOrthancTag(*it);
-        if (source.isMember(tag))
-        {
-          result[tag] = source[tag];
-        }
-      }
-
-      // Set the retrieve URL for WADO-RS
-      std::string url = (wadoBase + "studies/" + 
-                         GetOrthancTag(source, OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, ""));
-
-      if (level == QueryLevel_Series || level == QueryLevel_Instance)
-      {
-        url += "/series/" + GetOrthancTag(source, OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, "");
-      }
-
-      if (level == QueryLevel_Instance)
-      {
-        url += "/instances/" + GetOrthancTag(source, OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID, "");
+        url += "/instances/" + source.GetStringValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "", false);
       }
     
-      Json::Value tmp = Json::objectValue;
-      tmp["Name"] = "RetrieveURL";
-      tmp["Type"] = "String";
-      tmp["Value"] = url;
-
-      result[FormatOrthancTag(OrthancPlugins::DICOM_TAG_RETRIEVE_URL)] = tmp;
+      static const Orthanc::DicomTag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
+      result.SetValue(DICOM_TAG_RETRIEVE_URL, url, false);
     }
   };
 }
@@ -496,126 +483,63 @@
 static void ApplyMatcher(OrthancPluginRestOutput* output,
                          const OrthancPluginHttpRequest* request,
                          const ModuleMatcher& matcher,
-                         QueryLevel level)
+                         Orthanc::ResourceType level)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
   Json::Value find;
   matcher.ConvertToOrthanc(find, level);
 
-  Json::FastWriter writer;
-  std::string body = writer.write(find);
+  LOG(INFO) << "Body of the call from QIDO-RS to /tools/find: " << find.toStyledString();
+  
+  std::string body;
+
+  {
+    Json::FastWriter writer;
+    body = writer.write(find);
+  }
   
   Json::Value resources;
-  if (!OrthancPlugins::RestApiPostJson(resources, context, "/tools/find", body, false) ||
+  if (!OrthancPlugins::RestApiPost(resources, "/tools/find", body, false) ||
       resources.type() != Json::arrayValue)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 
-  typedef std::list< std::pair<std::string, std::string> > ResourcesAndInstances;
+  std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
 
-  ResourcesAndInstances resourcesAndInstances;
-  std::string root = (level == QueryLevel_Study ? "/studies/" : "/series/");
-    
+  OrthancPlugins::DicomWebFormatter::HttpWriter writer(
+    output, OrthancPlugins::Configuration::IsXmlExpected(request));
+
+  // Fix of issue #13
   for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
   {
     const std::string resource = resources[i].asString();
 
-    if (level == QueryLevel_Study ||
-        level == QueryLevel_Series)
+    Orthanc::DicomMap derivedTags;
+    std::string someInstance;
+    matcher.ComputeDerivedTags(derivedTags, someInstance, level, resource);
+    
+    Json::Value tags;
+    if (!someInstance.empty() &&
+        OrthancPlugins::RestApiGet(tags, "/instances/" + someInstance + "/tags", false))
     {
-      // Find one child instance of this resource
-      Json::Value tmp;
-      if (OrthancPlugins::RestApiGetJson(tmp, context, root + resource + "/instances", false) &&
-          tmp.type() == Json::arrayValue &&
-          tmp.size() > 0)
-      {
-        resourcesAndInstances.push_back(std::make_pair(resource, tmp[0]["ID"].asString()));
-      }
-    }
-    else
-    {
-      resourcesAndInstances.push_back(std::make_pair(resource, resource));
-    }
-  }
-  
-  std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
-
-  OrthancPlugins::DicomResults results(context, output, wadoBase, *dictionary_, IsXmlExpected(request), true);
+      Orthanc::DicomMap source;
+      source.FromDicomAsJson(tags);
 
-#if 0
-  // Implementation up to version 0.2 of the plugin. Each instance is
-  // downloaded and decoded using GDCM, which slows down things
-  // wrt. the new implementation below that directly uses the Orthanc
-  // pre-computed JSON summary.
-  for (ResourcesAndInstances::const_iterator
-         it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it)
-  {
-    ModuleMatcher::Filters derivedTags;
-    matcher.ComputeDerivedTags(derivedTags, level, it->first);
-
-    std::string file;
-    if (OrthancPlugins::RestApiGetString(file, context, "/instances/" + it->second + "/file", false))
-    {
-      OrthancPlugins::ParsedDicomFile dicom(file);
+      std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl(
+        wadoBase,
+        source.GetStringValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, "", false),
+        source.GetStringValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, "", false),
+        source.GetStringValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "", false));
 
-      std::auto_ptr<gdcm::DataSet> result(new gdcm::DataSet);
-      matcher.ExtractFields(*result, dicom, wadoBase, level);
-
-      // Inject the derived tags
-      ModuleMatcher::Filters derivedTags;
-      matcher.ComputeDerivedTags(derivedTags, level, it->first);
+      Orthanc::DicomMap target;
+      target.Assign(derivedTags);
+      matcher.ExtractFields(target, source, wadoBase, level);
 
-      for (ModuleMatcher::Filters::const_iterator
-             tag = derivedTags.begin(); tag != derivedTags.end(); ++tag)
-      {
-        gdcm::DataElement element(tag->first);
-        element.SetByteValue(tag->second.c_str(), tag->second.size());
-        result->Replace(element);
-      }
-
-      results.Add(dicom.GetFile(), *result);
+      writer.AddOrthancMap(target);
     }
   }
 
-#else
-  // Fix of issue #13
-  for (ResourcesAndInstances::const_iterator
-         it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it)
-  {
-    Json::Value tags;
-    if (OrthancPlugins::RestApiGetJson(tags, context, "/instances/" + it->second + "/tags", false))
-    {
-      std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl(
-        wadoBase, 
-        GetOrthancTag(tags, OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, ""),
-        GetOrthancTag(tags, OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, ""),
-        GetOrthancTag(tags, OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID, ""));
-
-      Json::Value result;
-      matcher.ExtractFields(result, tags, wadoBase, level);
-
-      // 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)
-      {
-        Json::Value tmp = Json::objectValue;
-        tmp["Name"] = OrthancPlugins::GetKeyword(*dictionary_, tag->first);
-        tmp["Type"] = "String";
-        tmp["Value"] = tag->second;
-        result[FormatOrthancTag(tag->first)] = tmp;
-      }
-
-      results.AddFromOrthanc(result, wadoUrl);
-    }
-  }
-#endif
-
-  results.Answer();
+  writer.Send();
 }
 
 
@@ -626,12 +550,12 @@
 {
   if (request->method != OrthancPluginHttpMethod_Get)
   {
-    OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "GET");
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
   }
   else
   {
     ModuleMatcher matcher(request);
-    ApplyMatcher(output, request, matcher, QueryLevel_Study);
+    ApplyMatcher(output, request, matcher, Orthanc::ResourceType_Study);
   }
 }
 
@@ -642,7 +566,7 @@
 {
   if (request->method != OrthancPluginHttpMethod_Get)
   {
-    OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "GET");
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
   }
   else
   {
@@ -651,10 +575,10 @@
     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], true);
     }
 
-    ApplyMatcher(output, request, matcher, QueryLevel_Series);
+    ApplyMatcher(output, request, matcher, Orthanc::ResourceType_Series);
   }
 }
 
@@ -665,24 +589,25 @@
 {
   if (request->method != OrthancPluginHttpMethod_Get)
   {
-    OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "GET");
+    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], true);
     }
 
     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], true);
     }
 
-    ApplyMatcher(output, request, matcher, QueryLevel_Instance);
+    ApplyMatcher(output, request, matcher, Orthanc::ResourceType_Instance);
   }
 }
--- a/Plugin/QidoRs.h	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/QidoRs.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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/Plugin/StowRs.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/StowRs.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,208 +20,218 @@
 
 
 #include "StowRs.h"
-#include "Plugin.h"
 
 #include "Configuration.h"
-#include "Dicom.h"
-#include "../Orthanc/Core/Toolbox.h"
-
-#include <stdexcept>
-
-
-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 "DicomWebFormatter.h"
 
 
-bool IsXmlExpected(const OrthancPluginHttpRequest* request)
+namespace OrthancPlugins
 {
-  std::string accept;
-
-  if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
-  {
-    return true;   // By default, return XML Native DICOM Model
-  }
-
-  Orthanc::Toolbox::ToLowerCase(accept);
-  if (accept == "application/json")
-  {
-    return false;
-  }
-
-  if (accept != "application/dicom+xml" &&
-      accept != "application/xml" &&
-      accept != "text/xml" &&
-      accept != "*/*")
-  {
-    OrthancPlugins::Configuration::LogError("Unsupported return MIME type: " + accept + ", will return XML");
-  }
-
-  return true;
-}
-
-
-
-void StowCallback(OrthancPluginRestOutput* output,
-                  const char* url,
-                  const OrthancPluginHttpRequest* request)
-{
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-  const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
+  StowServer::StowServer(OrthancPluginContext* context,
+                         const std::map<std::string, std::string>& headers,
+                         const std::string& expectedStudy) :
+    context_(context),
+    xml_(Configuration::IsXmlExpected(headers)),
+    wadoBase_(Configuration::GetBaseUrl(headers)),
+    expectedStudy_(expectedStudy),
+    isFirst_(true),
+    result_(Json::objectValue),
+    success_(Json::arrayValue),
+    failed_(Json::arrayValue)
+  { 
+    std::string tmp, contentType, subType, boundary;
+    if (!Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers) ||
+        !Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
+                                      "The STOW-RS server expects a multipart body in its 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::Configuration::LogInfo("STOW-RS request without study");
-  }
-  else
-  {
-    OrthancPlugins::Configuration::LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
-  }
+    if (contentType != "multipart/related")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
+                                      "The Content-Type of a STOW-RS request must be \"multipart/related\"");
+    }
 
-  bool isXml = IsXmlExpected(request);
-
-  std::string header;
-  if (!OrthancPlugins::LookupHttpHeader(header, request, "content-type"))
-  {
-    OrthancPlugins::Configuration::LogError("No content type in the HTTP header of a STOW-RS request");
-    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
-    return;
-  }
+    if (subType != "application/dicom")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
+                                      "The STOW-RS plugin currently only supports \"application/dicom\" subtype");
+    }
 
-  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::Configuration::LogError("Unable to parse the content type of a STOW-RS request (" + application + ")");
-    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
-    return;
+    parser_.reset(new Orthanc::MultipartStreamReader(boundary));
+    parser_->SetHandler(*this);
   }
 
 
-  std::string boundary = attributes["boundary"]; 
-
-  if (attributes["type"] != "application/dicom")
+  void StowServer::HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
+                              const void* part,
+                              size_t size)
   {
-    OrthancPlugins::Configuration::LogError("The STOW-RS plugin currently only supports application/dicom");
-    OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
-    return;
-  }
+    std::string contentType;
 
+    if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers) ||
+        contentType != "application/dicom")
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_UnsupportedMediaType,
+        "The STOW-RS request contains a part that is not "
+        "\"application/dicom\" (it is: \"" + contentType + "\")");
+    }
+
+    Json::Value dicom;
+    bool ok = false;
 
-  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, context, request->body, request->bodySize, boundary);
-
+    try
+    {
+      OrthancString s;
+      s.Assign(OrthancPluginDicomBufferToJson(context_, part, size,
+                                              OrthancPluginDicomToJsonFormat_Short,
+                                              OrthancPluginDicomToJsonFlags_None, 256));
 
-  for (size_t i = 0; i < items.size(); i++)
-  {
-    if (!items[i].contentType_.empty() &&
-        items[i].contentType_ != "application/dicom")
+      if (s.GetContent() != NULL)
+      {
+        ok = true;
+        s.ToJson(dicom);
+      }
+    }
+    catch (Orthanc::OrthancException&)
     {
-      OrthancPlugins::Configuration::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 */);
+    }           
+
+    if (!ok)
+    {
+      // Bad DICOM file => TODO add to error
+      LogWarning("STOW-RS cannot parse an incoming DICOM file");
       return;
     }
 
-    OrthancPlugins::ParsedDicomFile dicom(items[i]);
-
-    std::string studyInstanceUid = dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, "", true);
-    std::string sopClassUid = dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_SOP_CLASS_UID, "", true);
-    std::string sopInstanceUid = dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID, "", true);
-
-    gdcm::Item item;
-    item.SetVLToUndefined();
-    gdcm::DataSet &status = item.GetNestedDataSet();
+    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)
+    {
+      LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
+      return;
+    }
 
-    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);
+    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();
 
-    if (!expectedStudy.empty() &&
-        studyInstanceUid != expectedStudy)
+    Json::Value item = Json::objectValue;
+    item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
+    item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
+      
+    if (!expectedStudy_.empty() &&
+        studyInstanceUid != expectedStudy_)
     {
-      OrthancPlugins::Configuration::LogInfo("STOW-RS request restricted to study [" + expectedStudy + 
-                                             "]: Ignoring instance from study [" + studyInstanceUid + "]");
+      LogInfo("STOW-RS request restricted to study [" + expectedStudy_ + 
+              "]: Ignoring instance from study [" + studyInstanceUid + "]");
 
-      SetTag(status, OrthancPlugins::DICOM_TAG_WARNING_REASON, gdcm::VR::US, "B006");  // Elements discarded
-      success->AddItem(item);      
+      /*item[DICOM_TAG_WARNING_REASON.Format()] =
+        boost::lexical_cast<std::string>(0xB006);  // Elements discarded
+        success.append(item);*/
     }
     else
     {
-      if (isFirst)
+      if (isFirst_)
       {
-        std::string url = wadoBase + "studies/" + studyInstanceUid;
-        SetTag(result, OrthancPlugins::DICOM_TAG_RETRIEVE_URL, gdcm::VR::UT, url);
-        isFirst = false;
+        std::string url = wadoBase_ + "studies/" + studyInstanceUid;
+        result_[DICOM_TAG_RETRIEVE_URL.Format()] = url;
+        isFirst_ = false;
       }
 
-      OrthancPlugins::MemoryBuffer tmp(context);
-      bool ok = tmp.RestApiPost("/instances", items[i].data_, items[i].size_, false);
+      MemoryBuffer tmp;
+      bool ok = tmp.RestApiPost("/instances", part, size, false);
       tmp.Clear();
 
       if (ok)
       {
-        std::string url = (wadoBase + 
+        std::string url = (wadoBase_ + 
                            "studies/" + studyInstanceUid +
-                           "/series/" + dicom.GetRawTagWithDefault(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, "", true) +
+                           "/series/" + seriesInstanceUid +
                            "/instances/" + sopInstanceUid);
 
-        SetTag(status, OrthancPlugins::DICOM_TAG_RETRIEVE_URL, gdcm::VR::UT, url);
-        success->AddItem(item);
+        item[DICOM_TAG_RETRIEVE_URL.Format()] = url;
+        success_.append(item);      
       }
       else
       {
-        OrthancPlugins::Configuration::LogError("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);
+        LogError("Orthanc was unable to store one instance in a STOW-RS request");
+        item[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);
+
+  void StowServer::AddChunk(const void* data,
+                            size_t size)
+  {
+    assert(parser_.get() != NULL);
+    parser_->AddChunk(data, size);
+  }
+
+
+  void StowServer::Execute(OrthancPluginRestOutput* output)
+  {
+    assert(parser_.get() != NULL);
+    parser_->CloseStream();
+
+    result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_;
+    result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_;
+    
+    std::string answer;
+    
+    {
+      DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
+      locker.Apply(answer, context_, result_, xml_);
+    }
+      
+    OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(),
+                              xml_ ? "application/dicom+xml" : "application/dicom+json");
+  };
 
-  OrthancPlugins::AnswerDicom(context, output, wadoBase, *dictionary_, result, isXml, false);
+  
+  IChunkedRequestReader* StowServer::PostCallback(const char* url,
+                                                  const OrthancPluginHttpRequest* request)
+  {
+    OrthancPluginContext* context = GetGlobalContext();
+  
+    if (request->method != OrthancPluginHttpMethod_Post)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    std::map<std::string, std::string> headers;
+    for (uint32_t i = 0; i < request->headersCount; i++)
+    {
+      headers[request->headersKeys[i]] = request->headersValues[i];
+    }
+
+    std::string expectedStudy;
+    if (request->groupsCount == 1)
+    {
+      expectedStudy = request->groups[0];
+    }
+
+    if (expectedStudy.empty())
+    {
+      LogInfo("STOW-RS request without study");
+    }
+    else
+    {
+      LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
+    }
+
+    return new StowServer(context, headers, expectedStudy);
+  }
 }
--- a/Plugin/StowRs.h	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/StowRs.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,10 +21,42 @@
 
 #pragma once
 
-#include "Configuration.h"
+#include <Core/HttpServer/MultipartStreamReader.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
 
-bool IsXmlExpected(const OrthancPluginHttpRequest* request);
+namespace OrthancPlugins
+{
+  class StowServer : 
+    public IChunkedRequestReader,
+    private Orthanc::MultipartStreamReader::IHandler
+  {
+  private:
+    OrthancPluginContext*  context_;
+    bool                   xml_;
+    std::string            wadoBase_;
+    std::string            expectedStudy_;
+    bool                   isFirst_;
+    Json::Value            result_;
+    Json::Value            success_;
+    Json::Value            failed_;
 
-void StowCallback(OrthancPluginRestOutput* output,
-                  const char* url,
-                  const OrthancPluginHttpRequest* request);
+    std::auto_ptr<Orthanc::MultipartStreamReader>  parser_;
+
+    virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
+                            const void* part,
+                            size_t size);
+
+  public:
+    StowServer(OrthancPluginContext* context,
+               const std::map<std::string, std::string>& headers,
+               const std::string& expectedStudy);
+
+    virtual void AddChunk(const void* data,
+                          size_t size);
+
+    virtual void Execute(OrthancPluginRestOutput* output);
+
+    static IChunkedRequestReader* PostCallback(const char* url,
+                                               const OrthancPluginHttpRequest* request);
+  };
+}
--- a/Plugin/WadoRs.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/WadoRs.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,46 @@
  **/
 
 
-#include "Plugin.h"
+#include "Configuration.h"
+#include "DicomWebFormatter.h"
 
-#include "Configuration.h"
-#include "Dicom.h"
-#include "DicomResults.h"
-#include "../Orthanc/Core/Toolbox.h"
+#include <Core/ChunkedBuffer.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+
+#include <Core/DicomFormat/DicomArray.h>  // TODO - remove
+
 
 #include <memory>
 
+
+static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
+static const char* const INSTANCES = "Instances";
+static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
+
+
+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)
 {
   std::string accept;
@@ -43,8 +75,8 @@
   if (application != "multipart/related" &&
       application != "*/*")
   {
-    OrthancPlugins::Configuration::LogError("This WADO-RS plugin cannot generate the following content type: " + accept);
-    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())
@@ -53,17 +85,24 @@
     Orthanc::Toolbox::ToLowerCase(s);
     if (s != "application/dicom")
     {
-      OrthancPlugins::Configuration::LogError("This WADO-RS plugin only supports application/dicom "
-                                              "return type for DICOM retrieval (" + accept + ")");
-      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())
+  static const char* const TRANSFER_SYNTAX = "transfer-syntax";
+
+  /**
+   * The "*" case below is related to Google Healthcare API:
+   * https://groups.google.com/d/msg/orthanc-users/w1Ekrsc6-U8/T2a_DoQ5CwAJ
+   **/
+  if (attributes.find(TRANSFER_SYNTAX) != attributes.end() &&
+      attributes[TRANSFER_SYNTAX] != "*")
   {
-    OrthancPlugins::Configuration::LogError("This WADO-RS plugin cannot change the transfer syntax to " + 
-                                            attributes["transfer-syntax"]);
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot change the transfer syntax to " + 
+                                    attributes["transfer-syntax"]);
   }
 
   return true;
@@ -74,13 +113,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;
   }
 
@@ -88,36 +125,53 @@
   std::map<std::string, std::string> attributes;
   OrthancPlugins::ParseContentType(application, attributes, accept);
 
-  if (application == "application/json")
+  std::vector<std::string> applicationTokens;
+  Orthanc::Toolbox::TokenizeString(applicationTokens, application, ',');
+
+  for (size_t i = 0; i < applicationTokens.size(); i++)
   {
-    isXml = false;
-    return true;
+    std::string token = Orthanc::Toolbox::StripSpaces(applicationTokens[i]);
+    
+    if (token == "application/json" ||
+        token == "application/dicom+json" ||
+        token == "*/*")
+    {
+      return true;
+    }
   }
 
-  if (application != "multipart/related" &&
-      application != "*/*")
+  if (application != "multipart/related")
   {
-    OrthancPlugins::Configuration::LogError("This WADO-RS plugin cannot generate the following content type: " + accept);
-    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
     {
-      OrthancPlugins::Configuration::LogError("This WADO-RS plugin only supports application/json or "
-                                              "application/dicom+xml return types for metadata (" + accept + ")");
-      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())
   {
-    OrthancPlugins::Configuration::LogError("This WADO-RS plugin cannot change the transfer syntax to " + 
-                                            attributes["transfer-syntax"]);
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest,
+                                    "This WADO-RS plugin cannot change the transfer syntax to " + 
+                                    attributes["transfer-syntax"]);
   }
 
   return true;
@@ -141,8 +195,9 @@
   if (application != "multipart/related" &&
       application != "*/*")
   {
-    OrthancPlugins::Configuration::LogError("This WADO-RS plugin cannot generate the following bulk data type: " + accept);
-    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())
@@ -151,17 +206,17 @@
     Orthanc::Toolbox::ToLowerCase(s);
     if (s != "application/octet-stream")
     {
-      OrthancPlugins::Configuration::LogError("This WADO-RS plugin only supports application/octet-stream "
-                                              "return type for bulk data retrieval (" + accept + ")");
-      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())
   {
-    OrthancPlugins::Configuration::LogError("This WADO-RS plugin does not support Range retrieval, "
-                                            "it can only return entire bulk data object");
-    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;
@@ -169,12 +224,19 @@
 
 
 static void AnswerListOfDicomInstances(OrthancPluginRestOutput* output,
-                                       const std::string& resource)
+                                       Orthanc::ResourceType level,
+                                       const std::string& publicId)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  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", false))
+  if (!OrthancPlugins::RestApiGet(instances, GetResourceUri(level, publicId) + "/instances", false))
   {
     // Internal error
     OrthancPluginSendHttpStatusCode(context, output, 400);
@@ -183,78 +245,473 @@
 
   if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    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";
 
-    OrthancPlugins::MemoryBuffer dicom(context);
+    OrthancPlugins::MemoryBuffer dicom;
     if (dicom.RestApiGet(uri, false) &&
         OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
   }
 }
 
 
 
-static void AnswerMetadata(OrthancPluginRestOutput* output,
-                           const OrthancPluginHttpRequest* request,
-                           const std::string& resource,
-                           bool isInstance,
-                           bool isXml)
+namespace
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-  std::list<std::string> files;
-  if (isInstance)
+  class SetOfDicomInstances : public boost::noncopyable
   {
-    files.push_back(resource + "/file");
-  }
-  else
-  {
-    Json::Value instances;
-    if (!OrthancPlugins::RestApiGetJson(instances, context, resource + "/instances", false))
+  private:
+    std::vector<Orthanc::DicomMap*>  instances_;
+
+  public:
+    ~SetOfDicomInstances()
+    {
+      for (size_t i = 0; i < instances_.size(); i++)
+      {
+        assert(instances_[i] != NULL);
+        delete instances_[i];
+      }
+    }
+
+    size_t GetSize() const
     {
-      // Internal error
-      OrthancPluginSendHttpStatusCode(context, output, 400);
-      return;
+      return instances_.size();
+    }
+
+    bool ReadInstance(const std::string& publicId)
+    {
+      Json::Value dicomAsJson;
+      
+      if (OrthancPlugins::RestApiGet(dicomAsJson, "/instances/" + publicId + "/tags", false))
+      {
+        std::auto_ptr<Orthanc::DicomMap> instance(new Orthanc::DicomMap);
+        instance->FromDicomAsJson(dicomAsJson);
+        instances_.push_back(instance.release());
+        
+        return true;
+      }
+      else
+      {
+        return false;
+      }
     }
 
-    for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+    
+    void MinorityReport(Orthanc::DicomMap& target,
+                        const Orthanc::DicomTag& tag) const
     {
-      files.push_back("/instances/" + instances[i]["ID"].asString() + "/file");
+      typedef std::map<std::string, unsigned int>  Counters;
+
+      Counters counters;
+
+      for (size_t i = 0; i < instances_.size(); i++)
+      {
+        assert(instances_[i] != NULL);
+
+        std::string value;
+        if (instances_[i]->LookupStringValue(value, tag, false))
+        {
+          Counters::iterator found = counters.find(value);
+          if (found == counters.end())
+          {
+            counters[value] = 1;
+          }
+          else
+          {
+            found->second ++;
+          }
+        }
+      }
+
+      if (!counters.empty())
+      {
+        Counters::const_iterator current = counters.begin();
+          
+        std::string maxValue = current->first;
+        size_t maxCount = current->second;
+
+        ++current;
+
+        while (current != counters.end())
+        {
+          if (maxCount < current->second)
+          {
+            maxValue = current->first;
+            maxCount = current->second;
+          }
+            
+          ++current;
+        }
+
+        // Take the ceiling of the number of available instances
+        const size_t threshold = instances_.size() / 2 + 1;
+        if (maxCount >= threshold)
+        {
+          target.SetValue(tag, maxValue, false);
+        }
+      }
     }
-  }
+  };
 
-  const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
-  OrthancPlugins::DicomResults results(context, output, wadoBase, *dictionary_, isXml, true);
   
-  for (std::list<std::string>::const_iterator
-         it = files.begin(); it != files.end(); ++it)
+  class MainDicomTagsCache : public boost::noncopyable
   {
-    OrthancPlugins::MemoryBuffer content(context);
-    if (content.RestApiGet(*it, false))
+  private:
+    struct Info : public boost::noncopyable
+    {
+      Orthanc::DicomMap  dicom_;
+      std::string        parent_;
+    };
+    
+    typedef std::pair<std::string, Orthanc::ResourceType>  Index;
+    typedef std::map<Index, Info*>                         Content;
+
+    Content  content_;
+
+    static bool ReadResource(Orthanc::DicomMap& dicom,
+                             std::string& parent,
+                             OrthancPlugins::MetadataMode mode,
+                             const std::string& orthancId,
+                             Orthanc::ResourceType level)
     {
-      OrthancPlugins::ParsedDicomFile dicom(content);
-      results.Add(dicom.GetFile());
+      std::string uri;
+      std::string parentField;
+
+      switch (level)
+      {
+        case Orthanc::ResourceType_Study:
+          uri = "/studies/" + orthancId;
+          break;
+            
+        case Orthanc::ResourceType_Series:
+          uri = "/series/" + orthancId;
+          parentField = "ParentStudy";
+          break;
+            
+        case Orthanc::ResourceType_Instance:
+          uri = "/instances/" + orthancId;
+          parentField = "ParentSeries";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      Json::Value value;
+      if (!OrthancPlugins::RestApiGet(value, uri, false))
+      {
+        return false;
+      }
+         
+
+      if (value.type() != Json::objectValue ||
+          !value.isMember(MAIN_DICOM_TAGS))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      dicom.ParseMainDicomTags(value[MAIN_DICOM_TAGS], level);
+
+      if (level == Orthanc::ResourceType_Study)
+      {
+        if (value.isMember(PATIENT_MAIN_DICOM_TAGS))
+        {
+          dicom.ParseMainDicomTags(value[PATIENT_MAIN_DICOM_TAGS], Orthanc::ResourceType_Patient);
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+      }
+        
+      if (!parentField.empty())
+      {
+        if (value.isMember(parentField) &&
+            value[parentField].type() == Json::stringValue)
+        {
+          parent = value[parentField].asString();
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+      }
+
+      
+      if (mode == OrthancPlugins::MetadataMode_Extrapolate &&
+          (level == Orthanc::ResourceType_Series ||
+           level == Orthanc::ResourceType_Study))
+      {
+        std::set<Orthanc::DicomTag> tags;
+        OrthancPlugins::Configuration::GetExtrapolatedMetadataTags(tags, level);
+
+        if (!tags.empty())
+        {
+          /**
+           * Complete the series/study-level tags, with instance-level
+           * tags that are not considered as "main DICOM tags" in
+           * Orthanc, but that are necessary for Web viewers, and that
+           * are expected to be constant throughout all the instances of
+           * the study/series. To this end, we read up to "N" DICOM
+           * instances of this study/series from disk, and for the tags
+           * of interest, we look at whether there is a consensus in the
+           * value among these instances. Obviously, this is an
+           * approximation to improve performance.
+           **/
+
+          std::set<std::string> allInstances;
+
+          switch (level)
+          {
+            case Orthanc::ResourceType_Series:
+              if (!value.isMember(INSTANCES) ||
+                  value[INSTANCES].type() != Json::arrayValue)
+              {
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+              else
+              {
+                for (Json::Value::ArrayIndex i = 0; i < value[INSTANCES].size(); i++)
+                {
+                  if (value[INSTANCES][i].type() != Json::stringValue)
+                  {
+                    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+                  }
+                  else
+                  {
+                    allInstances.insert(value[INSTANCES][i].asString());
+                  }            
+                }
+              }
+              
+              break;
+
+            case Orthanc::ResourceType_Study:
+            {
+              Json::Value tmp;
+              if (OrthancPlugins::RestApiGet(tmp, "/studies/" + orthancId + "/instances", false))
+              {
+                if (tmp.type() != Json::arrayValue)
+                {
+                  throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+                }
+
+                for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++)
+                {
+                  if (tmp[i].type() != Json::objectValue ||
+                      !tmp[i].isMember("ID") ||
+                      tmp[i]["ID"].type() != Json::stringValue)
+                  {
+                    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+                  }
+                  else
+                  {
+                    allInstances.insert(tmp[i]["ID"].asString());
+                  }
+                }
+              }
+              
+              break;
+            }
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+
+          
+          // Select up to N random instances. The instances are
+          // implicitly selected randomly, as the public ID of an
+          // instance is a SHA-1 hash (whose domain is uniformly distributed)
+
+          static const size_t N = 3;
+          SetOfDicomInstances selectedInstances;
+
+          for (std::set<std::string>::const_iterator it = allInstances.begin();
+               selectedInstances.GetSize() < N && it != allInstances.end(); ++it)
+          {
+            selectedInstances.ReadInstance(*it);
+          }
+
+          for (std::set<Orthanc::DicomTag>::const_iterator
+                 it = tags.begin(); it != tags.end(); ++it)
+          {
+            selectedInstances.MinorityReport(dicom, *it);
+          }
+        }
+      }
+
+      return true;
     }
-  }
+    
+
+    bool Lookup(Orthanc::DicomMap& dicom,
+                std::string& parent,
+                OrthancPlugins::MetadataMode mode,
+                const std::string& orthancId,
+                Orthanc::ResourceType level)
+    {
+      Content::iterator found = content_.find(std::make_pair(orthancId, level));
+      
+      if (found == content_.end())
+      {
+        std::auto_ptr<Info> info(new Info);
+        if (!ReadResource(info->dicom_, info->parent_, mode, orthancId, level))
+        {
+          return false;
+        }
+
+        found = content_.insert(std::make_pair(std::make_pair(orthancId, level), info.release())).first;
+      }
+
+      assert(found != content_.end() &&
+             found->second != NULL);
+      dicom.Merge(found->second->dicom_);
+      parent = found->second->parent_;
 
-  results.Answer();
+      return true;
+    }
+
+
+  public:
+    ~MainDicomTagsCache()
+    {
+      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        delete it->second;
+      }
+    }
+
+
+    bool GetInstance(Orthanc::DicomMap& dicom,
+                     OrthancPlugins::MetadataMode mode,
+                     const std::string& orthancId)
+    {
+      std::string seriesId, studyId, nope;
+      
+      return (ReadResource(dicom, seriesId, mode, orthancId, Orthanc::ResourceType_Instance) &&
+              Lookup(dicom, studyId, mode, seriesId, Orthanc::ResourceType_Series) &&
+              Lookup(dicom, nope /* patient id is unused */, mode, studyId, Orthanc::ResourceType_Study));
+    }
+  };
 }
 
 
 
+static void WriteInstanceMetadata(OrthancPlugins::DicomWebFormatter::HttpWriter& writer,
+                                  OrthancPlugins::MetadataMode mode,
+                                  MainDicomTagsCache& cache,
+                                  const std::string& orthancId,
+                                  const std::string& studyInstanceUid,
+                                  const std::string& seriesInstanceUid,
+                                  const std::string& sopInstanceUid,
+                                  const std::string& wadoBase)
+{
+  assert(!orthancId.empty() &&
+         !studyInstanceUid.empty() &&
+         !seriesInstanceUid.empty() &&
+         !sopInstanceUid.empty() &&
+         !wadoBase.empty());
 
-static bool LocateStudy(OrthancPluginRestOutput* output,
-                        std::string& uri,
-                        const OrthancPluginHttpRequest* request)
+  const std::string bulkRoot = (wadoBase +
+                                "studies/" + studyInstanceUid +
+                                "/series/" + seriesInstanceUid + 
+                                "/instances/" + sopInstanceUid + "/bulk");
+
+  switch (mode)
+  {
+    case OrthancPlugins::MetadataMode_MainDicomTags:
+    case OrthancPlugins::MetadataMode_Extrapolate:
+    {
+      Orthanc::DicomMap dicom;
+      if (cache.GetInstance(dicom, mode, orthancId))
+      {
+        writer.AddOrthancMap(dicom);
+      }
+
+      break;
+    }
+
+    case OrthancPlugins::MetadataMode_Full:
+    {
+      // On a SSD drive, this version is twice slower than if using
+      // cache (see below)
+    
+      OrthancPlugins::MemoryBuffer dicom;
+      if (dicom.RestApiGet("/instances/" + orthancId + "/file", false))
+      {
+        writer.AddDicom(dicom.GetData(), dicom.GetSize(), bulkRoot);
+      }
+
+      break;
+    }
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+    
+  
+#if 0
+  /**
+   **/
+
+  // TODO - Have a global setting to enable/disable caching of DICOMweb
+
+  // TODO - Have a way to clear the "4444" attachments if Orthanc
+  // version changes => Store Orthanc core version in a prefix or in
+  // another attachment?
+    
+  OrthancPlugins::MemoryBuffer buffer;
+
+  if (writer.IsXml())
+  {
+    // DICOMweb XML is not cached
+    if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
+    {
+      writer.AddDicom(buffer.GetData(), buffer.GetSize(), bulkRoot);
+    }
+  }
+  else
+  {
+    if (buffer.RestApiGet("/instances/" + orthancId + "/attachments/4444/data", false))
+    {
+      writer.AddDicomWebSerializedJson(buffer.GetData(), buffer.GetSize());
+    }
+    else if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
+    {
+      // "Ignore binary mode" in DICOMweb conversion if caching is
+      // enabled, as the bulk root can change across executions
+
+      std::string dicomweb;
+      {
+        // TODO - Avoid a global mutex => Need to change Orthanc SDK
+        OrthancPlugins::DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
+        locker.Apply(dicomweb, OrthancPlugins::GetGlobalContext(),
+                     buffer.GetData(), buffer.GetSize(), false /* JSON */);
+      }
+
+      buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", dicomweb, false);
+      writer.AddDicomWebSerializedJson(dicomweb.c_str(), dicomweb.size());
+    }
+  }
+#endif
+}
+
+
+
+bool LocateStudy(OrthancPluginRestOutput* output,
+                 std::string& orthancId,
+                 std::string& studyInstanceUid,
+                 const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -262,31 +719,35 @@
     return false;
   }
 
-  std::string id;
+  studyInstanceUid = request->groups[0];
 
+  try
   {
-    char* tmp = OrthancPluginLookupStudy(context, request->groups[0]);
-    if (tmp == NULL)
+    OrthancPlugins::OrthancString tmp;
+    tmp.Assign(OrthancPluginLookupStudy(context, studyInstanceUid.c_str()));
+
+    if (tmp.GetContent() != NULL)
     {
-      OrthancPlugins::Configuration::LogError("Accessing an inexistent study with WADO-RS: " + std::string(request->groups[0]));
-      OrthancPluginSendHttpStatusCode(context, output, 404);
-      return false;
+      tmp.ToString(orthancId);
+      return true;
     }
+  }
+  catch (Orthanc::OrthancException&)
+  {
+  }
 
-    id.assign(tmp);
-    OrthancPluginFreeString(context, tmp);
-  }
-  
-  uri = "/studies/" + id;
-  return true;
+  throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
+                                  "Accessing an inexistent study with WADO-RS: " + studyInstanceUid);
 }
 
 
-static bool LocateSeries(OrthancPluginRestOutput* output,
-                         std::string& uri,
-                         const OrthancPluginHttpRequest* request)
+bool LocateSeries(OrthancPluginRestOutput* output,
+                  std::string& orthancId,
+                  std::string& studyInstanceUid,
+                  std::string& seriesInstanceUid,
+                  const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -294,46 +755,58 @@
     return false;
   }
 
-  std::string id;
+  studyInstanceUid = request->groups[0];
+  seriesInstanceUid = request->groups[1];
 
+  bool found = false;
+
+  try
   {
-    char* tmp = OrthancPluginLookupSeries(context, request->groups[1]);
-    if (tmp == NULL)
+    OrthancPlugins::OrthancString tmp;
+    tmp.Assign(OrthancPluginLookupSeries(context, seriesInstanceUid.c_str()));
+
+    if (tmp.GetContent() != NULL)
     {
-      OrthancPlugins::Configuration::LogError("Accessing an inexistent series with WADO-RS: " + std::string(request->groups[1]));
-      OrthancPluginSendHttpStatusCode(context, output, 404);
-      return false;
+      tmp.ToString(orthancId);
+      found = true;
     }
+  }
+  catch (Orthanc::OrthancException&)
+  {
+  }
 
-    id.assign(tmp);
-    OrthancPluginFreeString(context, tmp);
+  if (!found)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
+                                    "Accessing an inexistent series with WADO-RS: " + seriesInstanceUid);
   }
   
   Json::Value study;
-  if (!OrthancPlugins::RestApiGetJson(study, context, "/series/" + id + "/study", false))
+  if (!OrthancPlugins::RestApiGet(study, "/series/" + orthancId + "/study", false))
   {
     OrthancPluginSendHttpStatusCode(context, output, 404);
     return false;
   }
-
-  if (study["MainDicomTags"]["StudyInstanceUID"].asString() != std::string(request->groups[0]))
+  else if (study[MAIN_DICOM_TAGS]["StudyInstanceUID"].asString() != studyInstanceUid)
   {
-    OrthancPlugins::Configuration::LogError("No series " + std::string(request->groups[1]) + 
-                                            " in study " + std::string(request->groups[0]));
-    OrthancPluginSendHttpStatusCode(context, output, 404);
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, 
+                                    "No series " + seriesInstanceUid + " in study " + studyInstanceUid);
   }
-  
-  uri = "/series/" + id;
-  return true;
+  else
+  {
+    return true;
+  }
 }
 
 
 bool LocateInstance(OrthancPluginRestOutput* output,
-                    std::string& uri,
+                    std::string& orthancId,
+                    std::string& studyInstanceUid,
+                    std::string& seriesInstanceUid,
+                    std::string& sopInstanceUid,
                     const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -341,42 +814,42 @@
     return false;
   }
 
-  std::string id;
-
+  studyInstanceUid = request->groups[0];
+  seriesInstanceUid = request->groups[1];
+  sopInstanceUid = request->groups[2];
+  
   {
-    char* tmp = OrthancPluginLookupInstance(context, request->groups[2]);
-    if (tmp == NULL)
+    OrthancPlugins::OrthancString tmp;
+    tmp.Assign(OrthancPluginLookupInstance(context, sopInstanceUid.c_str()));
+
+    if (tmp.GetContent() == NULL)
     {
-      OrthancPlugins::Configuration::LogError("Accessing an inexistent instance with WADO-RS: " + 
-                                              std::string(request->groups[2]));
-      OrthancPluginSendHttpStatusCode(context, output, 404);
-      return false;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
+                                      "Accessing an inexistent instance with WADO-RS: " + sopInstanceUid);
     }
 
-    id.assign(tmp);
-    OrthancPluginFreeString(context, tmp);
+    tmp.ToString(orthancId);
   }
   
   Json::Value study, series;
-  if (!OrthancPlugins::RestApiGetJson(series, context, "/instances/" + id + "/series", false) ||
-      !OrthancPlugins::RestApiGetJson(study, context, "/instances/" + id + "/study", false))
+  if (!OrthancPlugins::RestApiGet(series, "/instances/" + orthancId + "/series", false) ||
+      !OrthancPlugins::RestApiGet(study, "/instances/" + orthancId + "/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]))
+  else if (study[MAIN_DICOM_TAGS]["StudyInstanceUID"].asString() != studyInstanceUid ||
+           series[MAIN_DICOM_TAGS]["SeriesInstanceUID"].asString() != seriesInstanceUid)
   {
-    OrthancPlugins::Configuration::LogError("No instance " + std::string(request->groups[2]) + 
-                                            " in study " + std::string(request->groups[0]) + " or " +
-                                            " in series " + std::string(request->groups[1]));
-    OrthancPluginSendHttpStatusCode(context, output, 404);
-    return false;
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
+                                    "Instance " + sopInstanceUid + 
+                                    " is not both in study " + studyInstanceUid +
+                                    " and in series " + seriesInstanceUid);
   }
-
-  uri = "/instances/" + id;
-  return true;
+  else
+  {
+    return true;
+  }
 }
 
 
@@ -386,14 +859,14 @@
 {
   if (!AcceptMultipartDicom(request))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::Configuration::GetContext(), output, 400 /* Bad request */);
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
   }
   else
   {
-    std::string uri;
-    if (LocateStudy(output, uri, request))
+    std::string orthancId, studyInstanceUid;
+    if (LocateStudy(output, orthancId, studyInstanceUid, request))
     {
-      AnswerListOfDicomInstances(output, uri);
+      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Study, orthancId);
     }
   }
 }
@@ -405,14 +878,14 @@
 {
   if (!AcceptMultipartDicom(request))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::Configuration::GetContext(), output, 400 /* Bad request */);
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
   }
   else
   {
-    std::string uri;
-    if (LocateSeries(output, uri, request))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid;
+    if (LocateSeries(output, orthancId, studyInstanceUid, seriesInstanceUid, request))
     {
-      AnswerListOfDicomInstances(output, uri);
+      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Series, orthancId);
     }
   }
 }
@@ -423,7 +896,7 @@
                            const char* url,
                            const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (!AcceptMultipartDicom(request))
   {
@@ -431,19 +904,19 @@
   }
   else
   {
-    std::string uri;
-    if (LocateInstance(output, uri, request))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
     {
       if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
 
-      OrthancPlugins::MemoryBuffer dicom(context);
-      if (dicom.RestApiGet(uri + "/file", false) &&
+      OrthancPlugins::MemoryBuffer dicom;
+      if (dicom.RestApiGet("/instances/" + orthancId + "/file", false) &&
           OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
     }
   }
@@ -451,6 +924,98 @@
 
 
 
+namespace
+{
+  class Identifier
+  {
+  private:
+    std::string  orthancId_;
+    std::string  dicomUid_;
+
+  public:
+    Identifier(const std::string& orthancId,
+               const std::string& dicomUid) :
+      orthancId_(orthancId),
+      dicomUid_(dicomUid)
+    {
+    }
+
+    const std::string& GetOrthancId() const
+    {
+      return orthancId_;
+    }
+
+    const std::string& GetDicomUid() const
+    {
+      return dicomUid_;
+    }
+  };
+}
+
+
+static void GetChildrenIdentifiers(std::list<Identifier>& target,
+                                   Orthanc::ResourceType level,
+                                   const std::string& orthancId)
+{
+  static const char* const ID = "ID";
+  static const char* const SERIES_INSTANCE_UID = "SeriesInstanceUID";
+  static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
+
+  target.clear();
+
+  const char* tag = NULL;
+  std::string uri;
+
+  switch (level)
+  {
+    case Orthanc::ResourceType_Study:
+      uri = "/studies/" + orthancId + "/series";
+      tag = SERIES_INSTANCE_UID;
+      break;
+       
+    case Orthanc::ResourceType_Series:
+      uri = "/series/" + orthancId + "/instances";
+      tag = SOP_INSTANCE_UID;
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+  assert(tag != NULL);
+  
+  Json::Value children;
+  if (OrthancPlugins::RestApiGet(children, uri, false))
+  {
+    if (children.type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < children.size(); i++)
+    {
+      if (children[i].type() != Json::objectValue ||
+          !children[i].isMember(ID) ||
+          !children[i].isMember(MAIN_DICOM_TAGS) ||
+          children[i][ID].type() != Json::stringValue ||
+          children[i][MAIN_DICOM_TAGS].type() != Json::objectValue ||
+          !children[i][MAIN_DICOM_TAGS].isMember(tag) ||
+          children[i][MAIN_DICOM_TAGS][tag].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+      else
+      {
+        target.push_back(Identifier(children[i][ID].asString(),
+                                    children[i][MAIN_DICOM_TAGS][tag].asString()));
+                                    
+      }
+    }
+  }  
+}
+
+
+
 void RetrieveStudyMetadata(OrthancPluginRestOutput* output,
                            const char* url,
                            const OrthancPluginHttpRequest* request)
@@ -458,14 +1023,36 @@
   bool isXml;
   if (!AcceptMetadata(request, isXml))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::Configuration::GetContext(), output, 400 /* Bad request */);
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
   }
   else
   {
-    std::string uri;
-    if (LocateStudy(output, uri, request))
+    const OrthancPlugins::MetadataMode mode =
+      OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Study);
+    
+    MainDicomTagsCache cache;
+
+    std::string studyOrthancId, studyInstanceUid;
+    if (LocateStudy(output, studyOrthancId, studyInstanceUid, request))
     {
-      AnswerMetadata(output, request, uri, false, isXml);
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+
+      std::list<Identifier> series;
+      GetChildrenIdentifiers(series, Orthanc::ResourceType_Study, studyOrthancId);
+
+      for (std::list<Identifier>::const_iterator a = series.begin(); a != series.end(); ++a)
+      {
+        std::list<Identifier> instances;
+        GetChildrenIdentifiers(instances, Orthanc::ResourceType_Series, a->GetOrthancId());
+
+        for (std::list<Identifier>::const_iterator b = instances.begin(); b != instances.end(); ++b)
+        {
+          WriteInstanceMetadata(writer, mode, cache, b->GetOrthancId(), studyInstanceUid, a->GetDicomUid(),
+                                b->GetDicomUid(), OrthancPlugins::Configuration::GetBaseUrl(request));
+        }
+      }
+
+      writer.Send();
     }
   }
 }
@@ -475,17 +1062,35 @@
                             const char* url,
                             const OrthancPluginHttpRequest* request)
 {
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+  
   bool isXml;
   if (!AcceptMetadata(request, isXml))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::Configuration::GetContext(), output, 400 /* Bad request */);
+    OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
   }
   else
   {
-    std::string uri;
-    if (LocateSeries(output, uri, request))
+    const OrthancPlugins::MetadataMode mode =
+      OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Series);
+    
+    MainDicomTagsCache cache;
+
+    std::string seriesOrthancId, studyInstanceUid, seriesInstanceUid;
+    if (LocateSeries(output, seriesOrthancId, studyInstanceUid, seriesInstanceUid, request))
     {
-      AnswerMetadata(output, request, uri, false, isXml);
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+      
+      std::list<Identifier> instances;
+      GetChildrenIdentifiers(instances, Orthanc::ResourceType_Series, seriesOrthancId);
+
+      for (std::list<Identifier>::const_iterator a = instances.begin(); a != instances.end(); ++a)
+      {
+        WriteInstanceMetadata(writer, mode, cache, a->GetOrthancId(), studyInstanceUid, seriesInstanceUid,
+                              a->GetDicomUid(), OrthancPlugins::Configuration::GetBaseUrl(request));
+      }
+
+      writer.Send();
     }
   }
 }
@@ -498,88 +1103,21 @@
   bool isXml;
   if (!AcceptMetadata(request, isXml))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::Configuration::GetContext(), output, 400 /* Bad request */);
+    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
   }
   else
   {
-    std::string uri;
-    if (LocateInstance(output, uri, request))
-    {
-      AnswerMetadata(output, request, uri, true, isXml);
-    }
-  }
-}
-
-
-
-static uint32_t Hex2Dec(char c)
-{
-  return (c >= '0' && c <= '9') ? c - '0' : c - 'a' + 10;
-}
+    MainDicomTagsCache cache;
 
-
-static bool ParseBulkTag(gdcm::Tag& tag,
-                         const std::string& s)
-{
-  if (s.size() != 8)
-  {
-    return false;
-  }
-
-  for (size_t i = 0; i < 8; i++)
-  {
-    if ((s[i] < '0' || s[i] > '9') &&
-        (s[i] < 'a' || s[i] > 'f'))
+    std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
     {
-      return false;
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml);
+      WriteInstanceMetadata(writer, OrthancPlugins::MetadataMode_Full, cache, orthancId, studyInstanceUid,
+                            seriesInstanceUid, sopInstanceUid, OrthancPlugins::Configuration::GetBaseUrl(request));
+      writer.Send();      
     }
   }
-
-  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;
-}
-
-
-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;
-  }
-
-  const gdcm::DataElement& element = dataset.GetDataElement(tag);
-
-  if (position + 1 == path.size())
-  {
-    const gdcm::ByteValue* data = element.GetByteValue();
-    if (!data)
-    {
-      content.clear();
-    }
-    else
-    {
-      content.assign(data->GetPointer(), data->GetLength());
-    }
-
-    return true;
-  }
-
-  return false;
 }
 
 
@@ -587,7 +1125,7 @@
                       const char* url,
                       const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   if (!AcceptBulkData(request))
   {
@@ -595,29 +1133,95 @@
     return;
   }
 
-  std::string uri;
-  OrthancPlugins::MemoryBuffer content(context);
-  if (LocateInstance(output, uri, request) &&
-      content.RestApiGet(uri + "/file", false))
+  std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+  OrthancPlugins::MemoryBuffer content;
+  if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request) &&
+      content.RestApiGet("/instances/" + orthancId + "/file", false))
   {
-    OrthancPlugins::ParsedDicomFile dicom(content);
+    std::string bulk(request->groups[3]);
 
     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()))
+    Orthanc::Toolbox::TokenizeString(path, bulk, '/');
+
+    // Map the bulk data URI to the Orthanc "/instances/.../content/..." built-in URI
+    std::string orthanc = "/instances/" + orthancId + "/content";
+
+    Orthanc::DicomTag tmp(0, 0);
+    
+    if (path.size() == 1 &&
+        Orthanc::DicomTag::ParseHexadecimal(tmp, path[0].c_str()) &&
+        tmp == Orthanc::DICOM_TAG_PIXEL_DATA)
     {
-      if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/octet-stream") != 0 ||
-          OrthancPluginSendMultipartItem(context, output, result.c_str(), result.size()) != 0)
+      // 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();
+
+      Json::Value frames;
+      if (OrthancPlugins::RestApiGet(frames, orthanc, false))
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+        if (frames.type() != Json::arrayValue ||
+            OrthancPluginStartMultipartAnswer(context, output, "related", "application/octet-stream") != 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
+
+        for (Json::Value::ArrayIndex i = 0; i < frames.size(); i++)
+        {
+          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
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+      }      
     }
     else
     {
-      OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
-    }      
+      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);
+      }
+
+      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	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/WadoRs.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,22 @@
 #include "Configuration.h"
 
 
+bool LocateStudy(OrthancPluginRestOutput* output,
+                 std::string& uri,
+                 std::string& studyInstanceUid,
+                 const OrthancPluginHttpRequest* request);
+
+bool LocateSeries(OrthancPluginRestOutput* output,
+                  std::string& uri,
+                  std::string& studyInstanceUid,
+                  std::string& seriesInstanceUid,
+                  const OrthancPluginHttpRequest* request);
+
 bool LocateInstance(OrthancPluginRestOutput* output,
                     std::string& uri,
+                    std::string& studyInstanceUid,
+                    std::string& seriesInstanceUid,
+                    std::string& sopInstanceUid,
                     const OrthancPluginHttpRequest* request);
 
 void RetrieveDicomStudy(OrthancPluginRestOutput* output,
@@ -58,3 +73,19 @@
 void RetrieveFrames(OrthancPluginRestOutput* output,
                     const char* url,
                     const OrthancPluginHttpRequest* request);
+
+void RetrieveInstanceRendered(OrthancPluginRestOutput* output,
+                              const char* url,
+                              const OrthancPluginHttpRequest* request);
+
+void RetrieveFrameRendered(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request);
+
+void RetrieveSeriesRendered(OrthancPluginRestOutput* output,
+                            const char* url,
+                            const OrthancPluginHttpRequest* request);
+
+void RetrieveStudyRendered(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request);
--- a/Plugin/WadoRsRetrieveFrames.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/WadoRsRetrieveFrames.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,9 +21,10 @@
 
 #include "WadoRs.h"
 
-#include "../Orthanc/Core/Toolbox.h"
-#include "Dicom.h"
-#include "Plugin.h"
+#include "GdcmParsedDicomFile.h"
+
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
 
 #include <memory>
 #include <list>
@@ -47,8 +49,20 @@
 }
 
 
+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)
+
+
+static Orthanc::DicomTransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest* request,
+                                                        Orthanc::DicomTransferSyntax sourceTransferSyntax)
 {
   for (uint32_t i = 0; i < request->headersCount; i++)
   {
@@ -63,12 +77,12 @@
       if (tokens.size() == 0 ||
           tokens[0] == "*/*")
       {
-        return gdcm::TransferSyntax::ImplicitVRLittleEndian;
+        return Orthanc::DicomTransferSyntax_LittleEndianExplicit;
       }
 
       if (tokens[0] != "multipart/related")
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "expecting 'Accept: multipart/related' HTTP header");
       }
 
       std::string type("application/octet-stream");
@@ -81,17 +95,19 @@
 
         if (parsed.size() != 2)
         {
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+          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);
         }
       }
 
@@ -99,79 +115,142 @@
       {
         if (transferSyntax.empty())
         {
-          return gdcm::TransferSyntax(gdcm::TransferSyntax::ImplicitVRLittleEndian);
+          return Orthanc::DicomTransferSyntax(Orthanc::DicomTransferSyntax_LittleEndianExplicit);
+        }
+        else if (transferSyntax == "*")
+        {
+          // New in DICOMweb plugin 1.1.0
+          return sourceTransferSyntax;
         }
         else
         {
-          OrthancPlugins::Configuration::LogError("DICOMweb RetrieveFrames: Cannot specify a transfer syntax (" + 
-                                                  transferSyntax + ") for default Little Endian uncompressed pixel data");
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+          throw Orthanc::OrthancException(
+            Orthanc::ErrorCode_BadRequest,
+            "DICOMweb RetrieveFrames: Cannot specify a transfer syntax (" + 
+            transferSyntax + ") for default Little Endian uncompressed pixel data");
         }
       }
       else
       {
-        // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.5-1
+        /**
+         * 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 Orthanc::DicomTransferSyntax_JPEGProcess14SV1;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50")
+        {
+          return Orthanc::DicomTransferSyntax_JPEGProcess1;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51")
+        {
+          return Orthanc::DicomTransferSyntax_JPEGProcess2_4;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57")
+        {
+          return Orthanc::DicomTransferSyntax_JPEGProcess14;
+        }
+        else if (type == "image/x-dicom-rle" && (transferSyntax.empty() ||  // Default
+                                                 transferSyntax == "1.2.840.10008.1.2.5"))
+        {
+          return Orthanc::DicomTransferSyntax_RLELossless;
+        }
+        else if (type == "image/x-jls" && (transferSyntax.empty() ||  // Default
+                                           transferSyntax == "1.2.840.10008.1.2.4.80"))
+        {
+          return Orthanc::DicomTransferSyntax_JPEGLSLossless;
+        }
+        else if (type == "image/x-jls" && transferSyntax == "1.2.840.10008.1.2.4.81")
+        {
+          return Orthanc::DicomTransferSyntax_JPEGLSLossy;
+        }
+        else if (type == "image/jp2" && (transferSyntax.empty() ||  // Default
+                                         transferSyntax == "1.2.840.10008.1.2.4.90"))
+        {
+          return Orthanc::DicomTransferSyntax_JPEG2000LosslessOnly;
+        }
+        else if (type == "image/jp2" && transferSyntax == "1.2.840.10008.1.2.4.91")
+        {
+          return Orthanc::DicomTransferSyntax_JPEG2000;
+        }
+        else if (type == "image/jpx" && (transferSyntax.empty() ||  // Default
+                                         transferSyntax == "1.2.840.10008.1.2.4.92"))
+        {
+          return Orthanc::DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly;
+        }
+        else if (type == "image/jpx" && transferSyntax == "1.2.840.10008.1.2.4.93")
+        {
+          return Orthanc::DicomTransferSyntax_JPEG2000Multicomponent;
+        }
+
+
+        /**
+         * 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;
+          return Orthanc::DicomTransferSyntax_JPEGProcess1;
         }
         else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51")
         {
-          return gdcm::TransferSyntax::JPEGExtendedProcess2_4;
+          return Orthanc::DicomTransferSyntax_JPEGProcess2_4;
         }
         else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57")
         {
-          return gdcm::TransferSyntax::JPEGLosslessProcess14;
+          return Orthanc::DicomTransferSyntax_JPEGProcess14;
         }
         else if (type == "image/dicom+jpeg" && (transferSyntax.empty() ||
                                                 transferSyntax == "1.2.840.10008.1.2.4.70"))
         {
-          return gdcm::TransferSyntax::JPEGLosslessProcess14_1;
+          return Orthanc::DicomTransferSyntax_JPEGProcess14SV1;
         }
         else if (type == "image/dicom+rle" && (transferSyntax.empty() ||
                                                transferSyntax == "1.2.840.10008.1.2.5"))
         {
-          return gdcm::TransferSyntax::RLELossless;
+          return Orthanc::DicomTransferSyntax_RLELossless;
         }
         else if (type == "image/dicom+jpeg-ls" && (transferSyntax.empty() ||
                                                    transferSyntax == "1.2.840.10008.1.2.4.80"))
         {
-          return gdcm::TransferSyntax::JPEGLSLossless;
+          return Orthanc::DicomTransferSyntax_JPEGLSLossless;
         }
         else if (type == "image/dicom+jpeg-ls" && transferSyntax == "1.2.840.10008.1.2.4.81")
         {
-          return gdcm::TransferSyntax::JPEGLSNearLossless;
+          return Orthanc::DicomTransferSyntax_JPEGLSLossy;
         }
         else if (type == "image/dicom+jp2" && (transferSyntax.empty() ||
                                                transferSyntax == "1.2.840.10008.1.2.4.90"))
         {
-          return gdcm::TransferSyntax::JPEG2000Lossless;
+          return Orthanc::DicomTransferSyntax_JPEG2000LosslessOnly;
         }
         else if (type == "image/dicom+jp2" && transferSyntax == "1.2.840.10008.1.2.4.91")
         {
-          return gdcm::TransferSyntax::JPEG2000;
+          return Orthanc::DicomTransferSyntax_JPEG2000;
         }
         else if (type == "image/dicom+jpx" && (transferSyntax.empty() ||
                                                transferSyntax == "1.2.840.10008.1.2.4.92"))
         {
-          return gdcm::TransferSyntax::JPEG2000Part2Lossless;
+          return Orthanc::DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly;
         }
         else if (type == "image/dicom+jpx" && transferSyntax == "1.2.840.10008.1.2.4.93")
         {
-          return gdcm::TransferSyntax::JPEG2000Part2;
+          return Orthanc::DicomTransferSyntax_JPEG2000Multicomponent;
         }
-        else
-        {
-          OrthancPlugins::Configuration::LogError("DICOMweb RetrieveFrames: Transfer syntax \"" + 
-                                                  transferSyntax + "\" is incompatible with media type \"" + type + "\"");
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
-        }
+
+        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;
+  return Orthanc::DicomTransferSyntax_LittleEndianExplicit;
 }
 
 
@@ -198,8 +277,8 @@
     int frame = boost::lexical_cast<int>(tokens[i]);
     if (frame <= 0)
     {
-      OrthancPlugins::Configuration::LogError("Invalid frame number (must be > 0): " + tokens[i]);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Invalid frame number (must be > 0): " + tokens[i]);
     }
 
     frames.push_back(static_cast<unsigned int>(frame - 1));
@@ -208,123 +287,246 @@
 
 
 
-static const char* GetMimeType(const gdcm::TransferSyntax& syntax)
+static const char* GetMimeType(const Orthanc::DicomTransferSyntax& syntax)
 {
+  // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.1.1.8-3b
+  // http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part18/chapter_6.html#table_6.1.1.8-3b
+
   switch (syntax)
   {
-    case gdcm::TransferSyntax::ImplicitVRLittleEndian:
-      return "application/octet-stream";
+    case Orthanc::DicomTransferSyntax_LittleEndianImplicit:
+      // The "transfer-syntax" info was added in version 1.1 of the plugin
+      return "application/octet-stream; transfer-syntax=1.2.840.10008.1.2";
 
-    case gdcm::TransferSyntax::JPEGBaselineProcess1:
-      return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.50";
+    case Orthanc::DicomTransferSyntax_LittleEndianExplicit:
+      return "application/octet-stream; transfer-syntax=1.2.840.10008.1.2.1";
 
-    case gdcm::TransferSyntax::JPEGExtendedProcess2_4:
-      return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.51";
+    case Orthanc::DicomTransferSyntax_JPEGProcess1:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.50";
+
+    case Orthanc::DicomTransferSyntax_JPEGProcess2_4:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.51";
     
-    case gdcm::TransferSyntax::JPEGLosslessProcess14:
-      return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.57";
-
-    case gdcm::TransferSyntax::JPEGLosslessProcess14_1:
-      return "image/dicom+jpeg; transferSyntax=1.2.840.10008.1.2.4.70";
-    
-    case gdcm::TransferSyntax::RLELossless:
-      return "image/dicom+rle; transferSyntax=1.2.840.10008.1.2.5";
+    case Orthanc::DicomTransferSyntax_JPEGProcess14:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.57";
 
-    case gdcm::TransferSyntax::JPEGLSLossless:
-      return "image/dicom+jpeg-ls; transferSyntax=1.2.840.10008.1.2.4.80";
+    case Orthanc::DicomTransferSyntax_JPEGProcess14SV1:
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.70";
+    
+    case Orthanc::DicomTransferSyntax_RLELossless:
+      return "image/x-dicom-rle; transfer-syntax=1.2.840.10008.1.2.5";
 
-    case gdcm::TransferSyntax::JPEGLSNearLossless:
-      return "image/dicom+jpeg-ls; transfer-syntax=1.2.840.10008.1.2.4.81";
+    case Orthanc::DicomTransferSyntax_JPEGLSLossless:
+      return "image/x-jls; transfer-syntax=1.2.840.10008.1.2.4.80";
 
-    case gdcm::TransferSyntax::JPEG2000Lossless:
-      return "image/dicom+jp2; transferSyntax=1.2.840.10008.1.2.4.90";
+    case Orthanc::DicomTransferSyntax_JPEGLSLossy:
+      return "image/x-jls; transfer-syntax=1.2.840.10008.1.2.4.81";
 
-    case gdcm::TransferSyntax::JPEG2000:
-      return "image/dicom+jp2; transfer-syntax=1.2.840.10008.1.2.4.91";
+    case Orthanc::DicomTransferSyntax_JPEG2000LosslessOnly:
+      return "image/jp2; transfer-syntax=1.2.840.10008.1.2.4.90";
+
+    case Orthanc::DicomTransferSyntax_JPEG2000:
+      return "image/jp2; transfer-syntax=1.2.840.10008.1.2.4.91";
 
-    case gdcm::TransferSyntax::JPEG2000Part2Lossless:
-      return "image/dicom+jpx; transferSyntax=1.2.840.10008.1.2.4.92";
+    case Orthanc::DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+      return "image/jpx; transfer-syntax=1.2.840.10008.1.2.4.92";
 
-    case gdcm::TransferSyntax::JPEG2000Part2:
-      return "image/dicom+jpx; transfer-syntax=1.2.840.10008.1.2.4.93";
+    case Orthanc::DicomTransferSyntax_JPEG2000Multicomponent:
+      return "image/jpx; transfer-syntax=1.2.840.10008.1.2.4.93";
 
     default:
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 }
 
 
 
+static void ConvertYbrToRgb(uint8_t rgb[3],
+                            const uint8_t ybr[3])
+{
+  // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2
+  // https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
+    
+  const float Y  = ybr[0];
+  const float Cb = ybr[1];
+  const float Cr = ybr[2];
+
+  const float result[3] = {
+    Y                             + 1.402f    * (Cr - 128.0f),
+    Y - 0.344136f * (Cb - 128.0f) - 0.714136f * (Cr - 128.0f),
+    Y + 1.772f    * (Cb - 128.0f)
+  };
+
+  for (uint8_t i = 0; i < 3 ; i++)
+  {
+    if (result[i] < 0)
+    {
+      rgb[i] = 0;
+    }
+    else if (result[i] > 255)
+    {
+      rgb[i] = 255;
+    }
+    else
+    {
+      rgb[i] = static_cast<uint8_t>(result[i]);
+    }
+  }    
+}
+
+
 static void AnswerSingleFrame(OrthancPluginRestOutput* output,
                               const OrthancPluginHttpRequest* request,
-                              const OrthancPlugins::ParsedDicomFile& dicom,
+                              const OrthancPlugins::GdcmParsedDicomFile& dicom,
                               const char* frame,
                               size_t size,
-                              unsigned int frameIndex)
+                              unsigned int frameIndex,
+                              bool convertYbr)
 {
+  /**
+   * Fix the photometric interpretation, typically needed for some
+   * multiframe US images (as the one in issue 164). Also check out
+   * the "Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp" file in
+   * the source distribution of Orthanc, and Osimis issue WVB-319
+   * ("Some images are not loading in US_MF").
+   **/
+
+  std::vector<uint8_t> copied;  // Don't move this variable inside the
+                                // "if", as "frame" might point to it
+
+  if (convertYbr &&
+      size > 0)
+  {
+    copied.resize(size);
+    memcpy(&copied[0], frame, size);
+
+    uint8_t *p = &copied[0];
+    for (size_t i = 0; i < size / 3; i++)
+    {
+      uint8_t ybr[3], rgb[3];
+      ybr[0] = p[0];
+      ybr[1] = p[1];
+      ybr[2] = p[2];
+
+      ConvertYbrToRgb(rgb, ybr);
+      p[0] = rgb[0];
+      p[1] = rgb[1];
+      p[2] = rgb[2];
+
+      p += 3;
+    }
+
+    frame = reinterpret_cast<const char*>(&copied[0]);
+  }  
+
+
   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::Configuration::GetContext(), output, frame, size, 1, keys, values);
+  error = OrthancPluginSendMultipartItem2(OrthancPlugins::GetGlobalContext(), output, frame, size, 1, keys, values);
 #else
-  error = OrthancPluginSendMultipartItem(OrthancPlugins::Configuration::GetContext(), output, frame, size);
+  error = OrthancPluginSendMultipartItem(OrthancPlugins::GetGlobalContext(), output, frame, size);
 #endif
 
   if (error != OrthancPluginErrorCode_Success)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);      
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);      
   }
 }
 
 
-
 static bool AnswerFrames(OrthancPluginRestOutput* output,
                          const OrthancPluginHttpRequest* request,
-                         const OrthancPlugins::ParsedDicomFile& dicom,
-                         const gdcm::TransferSyntax& syntax,
+                         const OrthancPlugins::GdcmParsedDicomFile& dicom,
+                         const Orthanc::DicomTransferSyntax& syntax,
                          std::list<unsigned int>& frames)
 {
-  if (!dicom.GetDataSet().FindDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA))
+  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);
+  static const gdcm::Tag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+
+  if (!dicom.GetDataSet().FindDataElement(DICOM_TAG_PIXEL_DATA))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_IncompatibleImageFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
   }
 
-  const gdcm::DataElement& pixelData = dicom.GetDataSet().GetDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA);
+  const gdcm::DataElement& pixelData = dicom.GetDataSet().GetDataElement(DICOM_TAG_PIXEL_DATA);
   const gdcm::SequenceOfFragments* fragments = pixelData.GetSequenceOfFragments();
 
-  if (OrthancPluginStartMultipartAnswer(OrthancPlugins::Configuration::GetContext(), 
+  if (OrthancPluginStartMultipartAnswer(OrthancPlugins::GetGlobalContext(), 
                                         output, "related", GetMimeType(syntax)) != OrthancPluginErrorCode_Success)
   {
     return false;
   }
 
+  int samplesPerPixel;
+
+  if (!dicom.GetIntegerTag(samplesPerPixel, DICOM_TAG_SAMPLES_PER_PIXEL))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+  }
+  
+  bool convertYbr = false;
+    
+  {
+    std::string photometric;
+    if (samplesPerPixel == 3 &&
+        dicom.GetStringTag(photometric, DICOM_TAG_PHOTOMETRIC_INTERPRETATION, true) &&
+        photometric == "YBR_FULL" &&
+        // Only applicable to uncompressed transfer syntaxes
+        (syntax == Orthanc::DicomTransferSyntax_LittleEndianImplicit ||
+         syntax == Orthanc::DicomTransferSyntax_LittleEndianExplicit ||
+         syntax == Orthanc::DicomTransferSyntax_BigEndianExplicit))
+    {
+      convertYbr = true;
+    }
+  }  
+  
   if (fragments == NULL)
   {
     // Single-fragment image
 
     if (pixelData.GetByteValue() == NULL)
     {
-      OrthancPlugins::Configuration::LogError("Image was not properly decoded");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                      "Image was not properly decoded");
     }
 
     int width, height, bits;
 
-    if (!dicom.GetIntegerTag(height, *dictionary_, OrthancPlugins::DICOM_TAG_ROWS) ||
-        !dicom.GetIntegerTag(width, *dictionary_, OrthancPlugins::DICOM_TAG_COLUMNS) ||
-        !dicom.GetIntegerTag(bits, *dictionary_, OrthancPlugins::DICOM_TAG_BITS_ALLOCATED))
+    if (!dicom.GetIntegerTag(height, DICOM_TAG_ROWS) ||
+        !dicom.GetIntegerTag(width, DICOM_TAG_COLUMNS) ||
+        !dicom.GetIntegerTag(bits, DICOM_TAG_BITS_ALLOCATED))
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    size_t frameSize = height * width * bits * samplesPerPixel / 8;
+
+    if (frameSize == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
-    size_t frameSize = height * width * bits / 8;
+    /**
+     * The number of bytes in "pixelData" might not be divisible by
+     * "frameSize", because "pixelData" might contain one padding byte
+     * to have an even number of bytes.
+     * https://bitbucket.org/sjodogne/orthanc/issues/164/
+     **/
     
-    if (pixelData.GetByteValue()->GetLength() % frameSize != 0)
+    if (pixelData.GetByteValue()->GetLength() % frameSize != 0 &&
+        (/* allow one padding byte to be present */
+          pixelData.GetByteValue()->GetLength() % 2 == 0 &&
+          pixelData.GetByteValue()->GetLength() % frameSize != 1))
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
     }
 
     size_t framesCount = pixelData.GetByteValue()->GetLength() / frameSize;
@@ -346,14 +548,15 @@
     {
       if (*frame >= framesCount)
       {
-        OrthancPlugins::Configuration::LogError("Trying to access frame number " + boost::lexical_cast<std::string>(*frame + 1) + 
-                                                " of an image with " + boost::lexical_cast<std::string>(framesCount) + " frames");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+        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);
+        AnswerSingleFrame(output, request, dicom, p, frameSize, *frame, convertYbr);
       }
     }
   }
@@ -376,18 +579,20 @@
       if (*frame >= fragments->GetNumberOfFragments())
       {
         // TODO A frame is not a fragment, looks like a bug
-        OrthancPlugins::Configuration::LogError("Trying to access frame number " + 
-                                                boost::lexical_cast<std::string>(*frame + 1) + 
-                                                " of an image with " + 
-                                                boost::lexical_cast<std::string>(fragments->GetNumberOfFragments()) + 
-                                                " frames");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+        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);
+                          fragments->GetFragment(*frame).GetByteValue()->GetLength(),
+                          *frame, convertYbr);
       }
     }
   }
@@ -401,71 +606,69 @@
                     const char* url,
                     const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
-  gdcm::TransferSyntax targetSyntax(ParseTransferSyntax(request));
-
   std::list<unsigned int> frames;
   ParseFrameList(frames, request);
 
   Json::Value header;
-  std::string uri;
-  OrthancPlugins::MemoryBuffer content(context);
-  if (LocateInstance(output, uri, request) &&
-      content.RestApiGet(uri + "/file", false) &&
-      OrthancPlugins::RestApiGetJson(header, context, uri + "/header?simplify", false))
+  std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+  OrthancPlugins::MemoryBuffer content;
+  if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request) &&
+      content.RestApiGet("/instances/" + orthancId + "/file", false) &&
+      OrthancPlugins::RestApiGet(header, "/instances/" + orthancId + "/header?simplify", false))
   {
     {
-      std::string s = "DICOMweb RetrieveFrames on " + uri + ", frames: ";
+      std::string s = "DICOMweb RetrieveFrames on " + orthancId + ", frames: ";
       for (std::list<unsigned int>::const_iterator 
              frame = frames.begin(); frame != frames.end(); ++frame)
       {
         s += boost::lexical_cast<std::string>(*frame + 1) + " ";
       }
 
-      OrthancPlugins::Configuration::LogInfo(s);
+      OrthancPlugins::LogInfo(s);
     }
 
-    std::auto_ptr<OrthancPlugins::ParsedDicomFile> source;
-
-    gdcm::TransferSyntax sourceSyntax;
+    std::auto_ptr<OrthancPlugins::GdcmParsedDicomFile> source;
+    
+    Orthanc::DicomTransferSyntax sourceSyntax;
 
     if (header.type() == Json::objectValue &&
         header.isMember("TransferSyntaxUID"))
     {
-      sourceSyntax = gdcm::TransferSyntax::GetTSType(header["TransferSyntaxUID"].asCString());
+      std::string uid = header["TransferSyntaxUID"].asString();
+      if (!Orthanc::LookupTransferSyntax(sourceSyntax, uid))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Unknown transfer syntax: " + uid);
+      }
     }
     else
     {
-      source.reset(new OrthancPlugins::ParsedDicomFile(content));
-      sourceSyntax = source->GetFile().GetHeader().GetDataSetTransferSyntax();
+      source.reset(new OrthancPlugins::GdcmParsedDicomFile(content));
+      sourceSyntax = source->GetTransferSyntax();
     }
 
-    if (sourceSyntax == targetSyntax ||
-        (targetSyntax == gdcm::TransferSyntax::ImplicitVRLittleEndian &&
-         sourceSyntax == gdcm::TransferSyntax::ExplicitVRLittleEndian))
+    Orthanc::DicomTransferSyntax targetSyntax = ParseTransferSyntax(request, sourceSyntax);
+
+    if (sourceSyntax == targetSyntax)
     {
       // No need to change the transfer syntax
 
       if (source.get() == NULL)
       {
-        source.reset(new OrthancPlugins::ParsedDicomFile(content));
+        source.reset(new OrthancPlugins::GdcmParsedDicomFile(content));
       }
 
       AnswerFrames(output, request, *source, targetSyntax, frames);
     }
     else
     {
-      // Need to convert the transfer syntax
-
-      {
-        OrthancPlugins::Configuration::LogInfo("DICOMweb RetrieveFrames: Transcoding " + uri + 
-                                               " from transfer syntax " + std::string(sourceSyntax.GetString()) + 
-                                               " to " + std::string(targetSyntax.GetString()));
-      }
+      // Need to convert the transfer syntax (transcoding)
+      OrthancPlugins::LogInfo("DICOMweb RetrieveFrames: Transcoding instance " + orthancId + 
+                              " from transfer syntax " + Orthanc::GetTransferSyntaxUid(sourceSyntax) +
+                              " to " + Orthanc::GetTransferSyntaxUid(targetSyntax));
 
       gdcm::ImageChangeTransferSyntax change;
-      change.SetTransferSyntax(targetSyntax);
+      change.SetTransferSyntax(OrthancPlugins::GdcmParsedDicomFile::GetGdcmTransferSyntax(targetSyntax));
 
       // TODO Avoid this unnecessary memcpy by defining a stream over the MemoryBuffer
       std::string dicom(content.GetData(), content.GetData() + content.GetSize());
@@ -475,15 +678,15 @@
       reader.SetStream(stream);
       if (!reader.Read())
       {
-        OrthancPlugins::Configuration::LogError("Cannot decode the image");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Cannot decode the image");
       }
 
       change.SetInput(reader.GetImage());
       if (!change.Change())
       {
-        OrthancPlugins::Configuration::LogError("Cannot change the transfer syntax of the image");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                        "Cannot change the transfer syntax of the image");
       }
 
       gdcm::ImageWriter writer;
@@ -494,10 +697,10 @@
       writer.SetStream(ss);
       if (!writer.Write())
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotEnoughMemory);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
       }
 
-      OrthancPlugins::ParsedDicomFile transcoded(ss.str());
+      OrthancPlugins::GdcmParsedDicomFile transcoded(ss.str());
       AnswerFrames(output, request, transcoded, targetSyntax, frames);
     }
   }    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/WadoRsRetrieveRendered.cpp	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,987 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/ImageTraits.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+
+namespace
+{
+  enum WindowingMode
+  {
+    WindowingMode_WholeDynamics,
+    WindowingMode_Linear,
+    WindowingMode_LinearExact,
+    WindowingMode_Sigmoid
+  };
+
+  class RenderingParameters : public boost::noncopyable
+  {
+  private:
+    bool          hasViewport_;
+    bool          hasQuality_;
+    bool          hasWindowing_;
+    bool          hasVW_;
+    bool          hasVH_;
+    bool          hasSW_;
+    bool          hasSH_;
+    unsigned int  vw_;
+    unsigned int  vh_;
+    unsigned int  sx_;
+    unsigned int  sy_;
+    unsigned int  sw_;
+    unsigned int  sh_;
+    bool          flipX_;
+    bool          flipY_;
+    unsigned int  quality_;
+    float         windowCenter_;
+    float         windowWidth_;
+    WindowingMode windowingMode_;
+    float         rescaleSlope_;
+    float         rescaleIntercept_;
+
+    static bool GetIntegerValue(int& target,
+                                std::vector<std::string>& tokens,
+                                size_t index,
+                                bool allowNegative,
+                                bool allowFloat,
+                                const std::string& message)
+    {
+      if (index >= tokens.size() ||
+          tokens[index].empty())
+      {
+        return false;
+      }          
+      
+      try
+      {
+        if (allowFloat)
+        {
+          float value = boost::lexical_cast<float>(tokens[index]);
+          target = boost::math::iround(value);
+        }
+        else
+        {
+          target = boost::lexical_cast<int>(tokens[index]);
+        }
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Out-of-range value for " + message + ": " + tokens[index]);
+      }
+
+      if (!allowNegative && target < 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Negative values disallowed for " + message + ": " + tokens[index]);
+      }
+      
+      return true;
+    }
+    
+  public:
+    explicit RenderingParameters(const OrthancPluginHttpRequest* request) :
+      hasViewport_(false),
+      hasQuality_(false),
+      hasWindowing_(false),
+      hasVW_(false),
+      hasVH_(false),
+      hasSW_(false),
+      hasSH_(false),
+      vw_(0),
+      vh_(0),
+      sx_(0),
+      sy_(0),
+      sw_(0),
+      sh_(0),
+      flipX_(false),
+      flipY_(false),
+      quality_(90),   // Default quality for JPEG previews (the same as in Orthanc core)
+      windowCenter_(128),
+      windowWidth_(256),
+      windowingMode_(WindowingMode_WholeDynamics),
+      rescaleSlope_(1),
+      rescaleIntercept_(0)
+    {
+      static const std::string VIEWPORT("\"viewport\" in WADO-RS Retrieve Rendered Transaction");
+      static const std::string WINDOW("\"window\" in WADO-RS Retrieve Rendered Transaction");
+      
+      for (uint32_t i = 0; i < request->getCount; i++)
+      {
+        const std::string key = request->getKeys[i];
+        const std::string value = request->getValues[i];
+        
+        if (key == "viewport")
+        {
+          hasViewport_ = true;
+
+          std::vector<std::string> tokens;
+          Orthanc::Toolbox::TokenizeString(tokens, value, ',');
+          if (tokens.size() != 2 &&
+              tokens.size() != 6)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                            "The number arguments to " + VIEWPORT + " must be 2 or 6");
+          }
+
+          int tmp;
+
+          hasVW_ = GetIntegerValue(tmp, tokens, 0, false, false, VIEWPORT);
+          if (hasVW_)
+          {
+            assert(tmp >= 0);
+            vw_ = static_cast<unsigned int>(tmp);
+          }
+
+          hasVH_ = GetIntegerValue(tmp, tokens, 1, false, false, VIEWPORT);
+          if (hasVH_)
+          {
+            assert(tmp >= 0);
+            vh_ = static_cast<unsigned int>(tmp);
+          }
+
+          if (GetIntegerValue(tmp, tokens, 2, true, true, VIEWPORT))
+          {
+            sx_ = static_cast<unsigned int>(tmp < 0 ? -tmp : tmp);  // Take absolute value
+          }
+          else
+          {
+            sx_ = 0;  // Default is zero
+          }
+
+          if (GetIntegerValue(tmp, tokens, 3, true, true, VIEWPORT))
+          {
+            sy_ = static_cast<unsigned int>(tmp < 0 ? -tmp : tmp);  // Take absolute value
+          }
+          else
+          {
+            sy_ = 0;  // Default is zero
+          }
+
+          hasSW_ = GetIntegerValue(tmp, tokens, 4, true, true, VIEWPORT);
+          if (hasSW_)
+          {
+            sw_ = static_cast<unsigned int>(tmp < 0 ? -tmp : tmp);  // Take absolute value
+            flipX_ = (tmp < 0);
+          }
+
+          hasSH_ = GetIntegerValue(tmp, tokens, 5, true, true, VIEWPORT);
+          if (hasSH_)
+          {
+            sh_ = static_cast<unsigned int>(tmp < 0 ? -tmp : tmp);  // Take absolute value
+            flipY_ = (tmp < 0);
+          }
+        }
+        else if (key == "quality")
+        {
+          hasQuality_ = true;
+
+          bool ok = false;
+          int q;
+          try
+          {
+            q = boost::lexical_cast<int>(value);
+            ok = (q >= 1 && q <= 100);
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+          }
+
+          if (!ok)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                            "The value of \"quality\" in WADO-RS Retrieve Rendered Transaction "
+                                            "must be between 1 and 100, found: " + value);
+          }
+
+          quality_ = static_cast<unsigned int>(q);
+        }
+        else if (key == "window")
+        {
+          hasWindowing_ = true;
+
+          std::vector<std::string> tokens;
+          Orthanc::Toolbox::TokenizeString(tokens, value, ',');
+
+          if (tokens.size() != 3)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                            "The number arguments to " + WINDOW + " must be 3");
+          }
+
+          try
+          {
+            windowCenter_ = boost::lexical_cast<float>(tokens[0]);
+            windowWidth_ = boost::lexical_cast<float>(tokens[1]);
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                            "The first and second arguments to " + WINDOW + " must be floats: " + value);
+          }
+
+          if (tokens[2] == "linear")
+          {
+            windowingMode_ = WindowingMode_Linear;
+          }
+          else if (tokens[2] == "linear-exact")
+          {
+            windowingMode_ = WindowingMode_LinearExact;
+          }
+          else if (tokens[2] == "sigmoid")
+          {
+            windowingMode_ = WindowingMode_Sigmoid;
+          }
+          else
+          {
+            throw Orthanc::OrthancException(
+              Orthanc::ErrorCode_ParameterOutOfRange,
+              "The third argument to " + WINDOW + " must be linear, linear-exact or sigmoid: " + tokens[2]);
+          }
+        }
+      }
+    }
+
+
+    bool HasCustomization() const
+    {
+      return (hasViewport_ || hasQuality_ || hasWindowing_);
+    }
+
+    unsigned int GetTargetWidth(unsigned int sourceWidth) const
+    {
+      if (hasVW_)
+      {
+        return vw_;
+      }
+      else
+      {
+        return sourceWidth;
+      }
+    }
+
+    unsigned int GetTargetHeight(unsigned int sourceHeight) const
+    {
+      if (hasVH_)
+      {
+        return vh_;
+      }
+      else
+      {
+        return sourceHeight;
+      }
+    }
+
+    bool IsFlipX() const
+    {
+      return flipX_;
+    }
+
+    bool IsFlipY() const
+    {
+      return flipY_;
+    }    
+
+    void GetSourceRegion(Orthanc::ImageAccessor& region,
+                         const Orthanc::ImageAccessor& source) const
+    {
+      if (sx_ >= source.GetWidth() ||
+          sy_ >= source.GetHeight())
+      {
+        region.AssignEmpty(source.GetFormat());
+      }
+      else
+      {
+        unsigned int right = source.GetWidth();
+        if (hasSW_ &&
+            sx_ + sw_ < source.GetWidth())
+        {
+          right = sx_ + sw_;
+        }
+
+        unsigned int bottom = source.GetHeight();
+        if (hasSH_ &&
+            sy_ + sh_ < source.GetHeight())
+        {
+          bottom = sy_ + sh_;
+        }
+
+        assert(sx_ <= right &&
+               sy_ <= bottom &&
+               right <= source.GetWidth() &&
+               bottom <= source.GetHeight());
+        source.GetRegion(region, sx_, sy_, right - sx_, bottom - sy_);
+      }
+    }
+
+    unsigned int GetQuality() const
+    {
+      return quality_;
+    }
+
+    bool IsWindowing() const
+    {
+      return hasWindowing_;
+    }
+
+    float GetWindowCenter() const
+    {
+      return windowCenter_;
+    }
+
+    float GetWindowWidth() const
+    {
+      return windowWidth_;
+    }
+
+    WindowingMode GetWindowingMode() const
+    {
+      return windowingMode_;
+    }    
+
+    void SetRescaleSlope(float v) 
+    {
+      rescaleSlope_ = v;
+    }
+
+    float GetRescaleSlope() const
+    {
+      return rescaleSlope_;
+    }
+
+    void SetRescaleIntercept(float v) 
+    {
+      rescaleIntercept_ = v;
+    }
+
+    float GetRescaleIntercept() const
+    {
+      return rescaleIntercept_;
+    }
+  };
+}
+
+
+static Orthanc::PixelFormat Convert(OrthancPluginPixelFormat format)
+{
+  switch (format)
+  {
+    case OrthancPluginPixelFormat_BGRA32:
+      return Orthanc::PixelFormat_BGRA32;
+
+    case OrthancPluginPixelFormat_Float32:
+      return Orthanc::PixelFormat_Float32;
+
+    case OrthancPluginPixelFormat_Grayscale16:
+      return Orthanc::PixelFormat_Grayscale16;
+
+    case OrthancPluginPixelFormat_Grayscale32:
+      return Orthanc::PixelFormat_Grayscale32;
+
+    case OrthancPluginPixelFormat_Grayscale64:
+      return Orthanc::PixelFormat_Grayscale64;
+
+    case OrthancPluginPixelFormat_Grayscale8:
+      return Orthanc::PixelFormat_Grayscale8;
+
+    case OrthancPluginPixelFormat_RGB24:
+      return Orthanc::PixelFormat_RGB24;
+
+    case OrthancPluginPixelFormat_RGB48:
+      return Orthanc::PixelFormat_RGB48;
+
+    case OrthancPluginPixelFormat_RGBA32:
+      return Orthanc::PixelFormat_RGBA32;
+
+    case OrthancPluginPixelFormat_SignedGrayscale16:
+      return Orthanc::PixelFormat_SignedGrayscale16;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+}
+
+
+template <Orthanc::PixelFormat SourceFormat>
+static void ApplyWindowing(Orthanc::ImageAccessor& target,
+                           const Orthanc::ImageAccessor& source,
+                           float c,
+                           float w,
+                           WindowingMode mode,
+                           float rescaleSlope,
+                           float rescaleIntercept)
+{
+  assert(target.GetFormat() == Orthanc::PixelFormat_Grayscale8 &&
+         source.GetFormat() == SourceFormat);
+
+  if (source.GetWidth() != target.GetWidth() ||
+      source.GetHeight() != target.GetHeight())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+  }
+
+  const unsigned int width = source.GetWidth();
+  const unsigned int height = source.GetHeight();
+
+  const float ymin = 0;
+  const float ymax = 255;
+  
+
+  /**
+     
+     LINEAR:
+     http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part03/sect_C.11.2.html#sect_C.11.2.1.2.1
+
+     Python
+     ------
+
+     import sympy as sym
+     x, c, w, ymin, ymax = sym.symbols('x c w ymin ymax')
+
+     e = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax- ymin) + ymin
+     print(sym.simplify(sym.collect(sym.expand(e), [ x, ymin, ymax ])))
+
+     Result
+     ------
+
+     (x*(ymax - ymin) + ymax*(-c + 0.5*w) + ymin*(c + 0.5*w - 1.0))/(w - 1)
+
+   **/
+
+  const float linearXMin = (c - 0.5f - (w - 1.0f) / 2.0f);
+  const float linearXMax = (c - 0.5f + (w - 1.0f) / 2.0f);
+  const float linearYScaling = (ymax - ymin) / (w - 1.0f);
+  const float linearYOffset = (ymax * (-c + 0.5f * w) + ymin * (c + 0.5f * w - 1.0f)) / (w - 1.0f);
+
+
+  /**
+
+     LINEAR-EXACT:
+     http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part03/sect_C.11.2.html#sect_C.11.2.1.3.2
+
+     Python
+     ------
+
+     import sympy as sym
+     x, c, w, ymin, ymax = sym.symbols('x c w ymin ymax')
+
+     e = (x - c) / w * (ymax- ymin) + ymin
+     print(sym.simplify(sym.collect(sym.expand(e), [ x, ymin, ymax ])))
+
+     Result
+     ------
+
+     (-c*ymax + x*(ymax - ymin) + ymin*(c + w))/w
+
+   **/
+  const float exactXMin = (c - w / 2.0f);
+  const float exactXMax = (c + w / 2.0f);
+  const float exactYScaling = (ymax - ymin) / w;
+  const float exactYOffset = (-c * ymax + ymin * (c + w)) / w;
+       
+
+  float minValue = std::numeric_limits<float>::infinity();
+  float maxValue = -std::numeric_limits<float>::infinity();
+  float wholeDynamicsScale = 1;
+ 
+  if (mode == WindowingMode_WholeDynamics)
+  {
+    for (unsigned int y = 0; y < height; y++)
+    {
+      for (unsigned int x = 0; x < width; x++)
+      {
+        float a = Orthanc::ImageTraits<SourceFormat>::GetFloatPixel(source, x, y);
+        minValue = std::min(minValue, a);
+        maxValue = std::max(maxValue, a);
+      }
+    }
+
+    minValue = rescaleSlope * minValue + rescaleIntercept;
+    maxValue = rescaleSlope * maxValue + rescaleIntercept;
+    wholeDynamicsScale = 255.0f / (maxValue - minValue);
+  }
+  
+  
+  for (unsigned int y = 0; y < height; y++)
+  {
+    for (unsigned int x = 0; x < width; x++)
+    {
+      float a = Orthanc::ImageTraits<SourceFormat>::GetFloatPixel(source, x, y);
+      a = rescaleSlope * a + rescaleIntercept;
+
+      float b;
+
+      switch (mode)
+      {
+        case WindowingMode_WholeDynamics:
+          b = (a - minValue) * wholeDynamicsScale;
+          break;
+
+        case WindowingMode_Linear:
+        {
+          if (a <= linearXMin)
+          {
+            b = ymin;
+          }
+          else if (a > linearXMax)
+          {
+            b = ymax;
+          }
+          else
+          {
+            b = a * linearYScaling + linearYOffset;
+          }
+
+          break;
+        }
+
+        case WindowingMode_LinearExact:
+        {
+          if (a <= exactXMin)
+          {
+            b = ymin;
+          }
+          else if (a > exactXMax)
+          {
+            b = ymax;
+          }
+          else
+          {
+            b = a * exactYScaling + exactYOffset;
+          }
+
+          break;
+        }
+
+        case WindowingMode_Sigmoid:
+        {
+          // http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part03/sect_C.11.2.html#sect_C.11.2.1.3.1
+          b = ymax / (1.0f + expf(-4.0f * (a - c) / w));
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      Orthanc::ImageTraits<Orthanc::PixelFormat_Grayscale8>::SetFloatPixel(target, b, x, y);
+    }
+  }
+}
+
+
+static void ApplyRendering(Orthanc::ImageAccessor& target,
+                           const Orthanc::ImageAccessor& source,
+                           const RenderingParameters& parameters)
+{
+  Orthanc::ImageProcessing::Set(target, 0);
+
+  Orthanc::ImageAccessor region;
+  parameters.GetSourceRegion(region, source);
+  
+  Orthanc::Image scaled(target.GetFormat(), region.GetWidth(), region.GetHeight(), false);
+
+  if (scaled.GetWidth() == 0 ||
+      scaled.GetHeight() == 0)
+  {
+    return;
+  }  
+
+  switch (target.GetFormat())
+  {
+    case Orthanc::PixelFormat_RGB24:
+      // Windowing is not taken into consideration for color images
+      Orthanc::ImageProcessing::Convert(scaled, region);
+      break;
+
+    case Orthanc::PixelFormat_Grayscale8:
+    {
+      switch (source.GetFormat())
+      {
+        case Orthanc::PixelFormat_Grayscale16:
+          ApplyWindowing<Orthanc::PixelFormat_Grayscale16>(scaled, region, parameters.GetWindowCenter(),
+                                                           parameters.GetWindowWidth(),
+                                                           parameters.GetWindowingMode(),
+                                                           parameters.GetRescaleSlope(),
+                                                           parameters.GetRescaleIntercept());
+          break;
+
+        case Orthanc::PixelFormat_SignedGrayscale16:
+          ApplyWindowing<Orthanc::PixelFormat_SignedGrayscale16>(scaled, region, parameters.GetWindowCenter(),
+                                                                 parameters.GetWindowWidth(),
+                                                                 parameters.GetWindowingMode(),
+                                                                 parameters.GetRescaleSlope(),
+                                                                 parameters.GetRescaleIntercept());
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+          
+      break;
+    }
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+  if (parameters.IsFlipX())
+  {
+    Orthanc::ImageProcessing::FlipX(scaled);
+  }
+
+  if (parameters.IsFlipY())
+  {
+    Orthanc::ImageProcessing::FlipY(scaled);
+  }
+
+
+  // TODO => Replace what follows with a call to "Orthanc::ImageProcessing::FitSize()"
+
+  
+  // Preserve the aspect ratio
+  float cw = static_cast<float>(scaled.GetWidth());
+  float ch = static_cast<float>(scaled.GetHeight());
+  float r = std::min(
+    static_cast<float>(target.GetWidth()) / cw,
+    static_cast<float>(target.GetHeight()) / ch);
+
+  unsigned int sw = std::min(static_cast<unsigned int>(boost::math::iround(cw * r)), target.GetWidth());  
+  unsigned int sh = std::min(static_cast<unsigned int>(boost::math::iround(ch * r)), target.GetHeight());
+  Orthanc::Image resized(target.GetFormat(), sw, sh, false);
+  
+  //Orthanc::ImageProcessing::SmoothGaussian5x5(scaled);
+  Orthanc::ImageProcessing::Resize(resized, scaled);
+
+  assert(target.GetWidth() >= resized.GetWidth() &&
+         target.GetHeight() >= resized.GetHeight());
+  unsigned int offsetX = (target.GetWidth() - resized.GetWidth()) / 2;
+  unsigned int offsetY = (target.GetHeight() - resized.GetHeight()) / 2;
+
+  target.GetRegion(region, offsetX, offsetY, resized.GetWidth(), resized.GetHeight());
+  Orthanc::ImageProcessing::Copy(region, resized);
+}
+
+
+
+static void AnswerFrameRendered(OrthancPluginRestOutput* output,
+                                std::string instanceId,
+                                int frame,
+                                const OrthancPluginHttpRequest* request)
+{
+  static const char* const RESCALE_INTERCEPT = "0028,1052";
+  static const char* const RESCALE_SLOPE = "0028,1053";
+
+  Orthanc::MimeType mime = Orthanc::MimeType_Jpeg;  // This is the default in DICOMweb
+      
+  for (uint32_t i = 0; i < request->headersCount; i++)
+  {
+    if (boost::iequals(request->headersKeys[i], "Accept") &&
+        !boost::iequals(request->headersValues[i], "*/*"))
+    {
+      try
+      {
+        // TODO - Support conversion to GIF
+        
+        mime = Orthanc::StringToMimeType(request->headersValues[i]);
+        if (mime != Orthanc::MimeType_Png &&
+            mime != Orthanc::MimeType_Jpeg)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(ERROR) << "Unsupported MIME type in WADO-RS rendered frame: "
+                   << request->headersValues[i];
+        throw;
+      }
+    }
+  }
+
+  RenderingParameters parameters(request);
+      
+  OrthancPlugins::MemoryBuffer buffer;
+  bool badFrameError = false;
+
+
+  if (parameters.HasCustomization())
+  {
+    if (frame <= 0)
+    {
+      badFrameError = true;
+    }
+    else
+    {
+      buffer.GetDicomInstance(instanceId);
+
+      Json::Value tags;
+      buffer.DicomToJson(tags, OrthancPluginDicomToJsonFormat_Short, OrthancPluginDicomToJsonFlags_None, 255);
+          
+      if (tags.isMember(RESCALE_SLOPE) &&
+          tags[RESCALE_SLOPE].type() == Json::stringValue)
+      {
+        try
+        {
+          parameters.SetRescaleSlope
+            (boost::lexical_cast<float>
+             (Orthanc::Toolbox::StripSpaces(tags[RESCALE_SLOPE].asString())));
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+      }
+
+      if (tags.isMember(RESCALE_INTERCEPT) &&
+          tags[RESCALE_INTERCEPT].type() == Json::stringValue)
+      {
+        try
+        {
+          parameters.SetRescaleIntercept
+            (boost::lexical_cast<float>
+             (Orthanc::Toolbox::StripSpaces(tags[RESCALE_INTERCEPT].asString())));
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+      }
+
+      OrthancPlugins::OrthancImage dicom;
+      dicom.DecodeDicomImage(buffer.GetData(), buffer.GetSize(), static_cast<unsigned int>(frame - 1));
+
+      Orthanc::PixelFormat targetFormat;
+      OrthancPluginPixelFormat sdkFormat;
+      if (dicom.GetPixelFormat() == OrthancPluginPixelFormat_RGB24)
+      {
+        targetFormat = Orthanc::PixelFormat_RGB24;
+        sdkFormat = OrthancPluginPixelFormat_RGB24;
+      }
+      else
+      {
+        targetFormat = Orthanc::PixelFormat_Grayscale8;
+        sdkFormat = OrthancPluginPixelFormat_Grayscale8;
+      }
+
+      Orthanc::ImageAccessor source;
+      source.AssignReadOnly(Convert(dicom.GetPixelFormat()),
+                            dicom.GetWidth(), dicom.GetHeight(), dicom.GetPitch(), dicom.GetBuffer());
+          
+      Orthanc::Image target(targetFormat, parameters.GetTargetWidth(source.GetWidth()),
+                            parameters.GetTargetHeight(source.GetHeight()), false);
+
+      ApplyRendering(target, source, parameters);
+          
+      switch (mime)
+      {
+        case Orthanc::MimeType_Png:
+          OrthancPluginCompressAndAnswerPngImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat,
+                                                 target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer());
+          break;
+              
+        case Orthanc::MimeType_Jpeg:
+          OrthancPluginCompressAndAnswerJpegImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat,
+                                                  target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer(),
+                                                  parameters.GetQuality());
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+  else
+  {
+    // No customization of the rendering. Return the default
+    // preview of Orthanc.
+    std::map<std::string, std::string> headers;
+    headers["Accept"] = Orthanc::EnumerationToString(mime);
+
+    // In DICOMweb, the "frame" parameter is in the range [1..N],
+    // whereas Orthanc uses range [0..N-1], hence the "-1" below.
+    if (buffer.RestApiGet("/instances/" + instanceId + "/frames/" +
+                          boost::lexical_cast<std::string>(frame - 1) + "/preview", headers, false))
+    {
+      OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, buffer.GetData(),
+                                buffer.GetSize(), Orthanc::EnumerationToString(mime));
+    }
+    else
+    {
+      badFrameError = true;
+    }
+  }
+
+  if (badFrameError)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                    "Inexistent frame index in this image: " + boost::lexical_cast<std::string>(frame));
+  }
+}
+
+
+static void AnswerFrameRendered(OrthancPluginRestOutput* output,
+                                int frame,
+                                const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+  }
+  else
+  {
+    std::string instanceId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+    if (LocateInstance(output, instanceId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
+    {
+      AnswerFrameRendered(output, instanceId, frame, request);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, "Inexistent instance");
+    }
+  }
+}
+
+
+void RetrieveInstanceRendered(OrthancPluginRestOutput* output,
+                              const char* url,
+                              const OrthancPluginHttpRequest* request)
+{
+  assert(request->groupsCount == 3);
+  AnswerFrameRendered(output, 1 /* first frame */, request);
+}
+
+
+void RetrieveFrameRendered(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request)
+{
+  assert(request->groupsCount == 4);
+  const char* frame = request->groups[3];
+
+  AnswerFrameRendered(output, boost::lexical_cast<int>(frame), request);
+}
+
+
+void RetrieveSeriesRendered(OrthancPluginRestOutput* output,
+                            const char* url,
+                            const OrthancPluginHttpRequest* request)
+{
+  static const char* const INSTANCES = "Instances";
+  
+  assert(request->groupsCount == 2);
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+  }
+  else
+  {
+    std::string orthancId, studyInstanceUid, seriesInstanceUid;
+    if (LocateSeries(output, orthancId, studyInstanceUid, seriesInstanceUid, request))
+    {
+      Json::Value series;
+      if (OrthancPlugins::RestApiGet(series, "/series/" + orthancId, false) &&
+          series.type() == Json::objectValue &&
+          series.isMember(INSTANCES) &&
+          series[INSTANCES].type() == Json::arrayValue &&
+          series[INSTANCES].size() > 0)
+      {
+        std::set<std::string> ids;
+        for (Json::Value::ArrayIndex i = 0; i < series[INSTANCES].size(); i++)
+        {
+          if (series[INSTANCES][i].type() != Json::stringValue)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+          else
+          {
+            ids.insert(series[INSTANCES][i].asString());
+          }
+        }
+
+        // Retrieve the first instance in alphanumeric order, in order
+        // to always return the same instance
+        std::string instanceId = *ids.begin();
+        AnswerFrameRendered(output, instanceId, 1 /* first frame */, request);
+        return;  // Success
+      }
+    }
+
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, "Inexistent series");
+  }
+}
+
+
+void RetrieveStudyRendered(OrthancPluginRestOutput* output,
+                           const char* url,
+                           const OrthancPluginHttpRequest* request)
+{
+  static const char* const ID = "ID";
+  
+  assert(request->groupsCount == 1);
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
+  }
+  else
+  {
+    std::string orthancId, studyInstanceUid;
+    if (LocateStudy(output, orthancId, studyInstanceUid, request))
+    {
+      Json::Value instances;
+      if (OrthancPlugins::RestApiGet(instances, "/studies/" + orthancId + "/instances", false) &&
+          instances.type() == Json::arrayValue &&
+          instances.size() > 0)
+      {
+        std::set<std::string> ids;
+        for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+        {
+          if (instances[i].type() != Json::objectValue ||
+              !instances[i].isMember(ID) ||
+              instances[i][ID].type() != Json::stringValue)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+          else
+          {
+            ids.insert(instances[i][ID].asString());
+          }
+        }
+
+        // Retrieve the first instance in alphanumeric order, in order
+        // to always return the same instance
+        std::string instanceId = *ids.begin();
+        AnswerFrameRendered(output, instanceId, 1 /* first frame */, request);
+        return;  // Success
+      }
+    }
+
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, "Inexistent study");
+  }
+}
--- a/Plugin/WadoUri.cpp	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/WadoUri.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,10 +20,11 @@
 
 
 #include "WadoUri.h"
-#include "Plugin.h"
 
 #include "Configuration.h"
 
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
 #include <string>
 
 
@@ -30,7 +32,7 @@
                                        char* (*func) (OrthancPluginContext*, const char*),
                                        const std::string& dicom)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   char* tmp = func(context, dicom.c_str());
 
@@ -47,12 +49,10 @@
 }
 
 
-static bool LocateInstance(std::string& instance,
-                           std::string& contentType,
-                           const OrthancPluginHttpRequest* request)
+static bool LocateInstanceWadoUri(std::string& instance,
+                                  std::string& contentType,
+                                  const OrthancPluginHttpRequest* request)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
   std::string requestType, studyUid, seriesUid, objectUid;
 
   for (uint32_t i = 0; i < request->getCount; i++)
@@ -84,19 +84,19 @@
 
   if (requestType != "WADO")
   {
-    OrthancPlugins::Configuration::LogError("WADO-URI: Invalid requestType: \"" + requestType + "\"");
+    OrthancPlugins::LogError("WADO-URI: Invalid requestType: \"" + requestType + "\"");
     return false;
   }
 
   if (objectUid.empty())
   {
-    OrthancPlugins::Configuration::LogError("WADO-URI: No SOPInstanceUID provided");
+    OrthancPlugins::LogError("WADO-URI: No SOPInstanceUID provided");
     return false;
   }
 
   if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
   {
-    OrthancPlugins::Configuration::LogError("WADO-URI: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"");
+    OrthancPlugins::LogError("WADO-URI: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"");
     return false;
   }
 
@@ -110,16 +110,16 @@
     std::string series;
     if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
     {
-      OrthancPlugins::Configuration::LogError("WADO-URI: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"");
+      OrthancPlugins::LogError("WADO-URI: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"");
       return false;
     }
     else
     {
       Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context, "/instances/" + instance + "/series", false) ||
+      if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/series", false) ||
           info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
       {
-        OrthancPlugins::Configuration::LogError("WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid);
+        OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid);
         return false;
       }
     }
@@ -130,16 +130,16 @@
     std::string study;
     if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
     {
-      OrthancPlugins::Configuration::LogError("WADO-URI: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"");
+      OrthancPlugins::LogError("WADO-URI: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"");
       return false;
     }
     else
     {
       Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context, "/instances/" + instance + "/study", false) ||
+      if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/study", false) ||
           info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
       {
-        OrthancPlugins::Configuration::LogError("WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid);
+        OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid);
         return false;
       }
     }
@@ -152,11 +152,11 @@
 static void AnswerDicom(OrthancPluginRestOutput* output,
                         const std::string& instance)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
   std::string uri = "/instances/" + instance + "/file";
 
-  OrthancPlugins::MemoryBuffer dicom(context);
+  OrthancPlugins::MemoryBuffer dicom;
   if (dicom.RestApiGet(uri, false))
   {
     OrthancPluginAnswerBuffer(context, output, 
@@ -164,8 +164,8 @@
   }
   else
   {
-    OrthancPlugins::Configuration::LogError("WADO-URI: Unable to retrieve DICOM file from " + uri);
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin,
+                                    "WADO-URI: Unable to retrieve DICOM file from " + uri);
   }
 }
 
@@ -181,7 +181,7 @@
   }
   else
   {
-    OrthancPlugins::Configuration::LogError("WADO-URI: Unable to generate a preview image for " + uri);
+    OrthancPlugins::LogError("WADO-URI: Unable to generate a preview image for " + uri);
     return false;
   }
 }
@@ -190,9 +190,9 @@
 static void AnswerPngPreview(OrthancPluginRestOutput* output,
                              const std::string& instance)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
 
-  OrthancPlugins::MemoryBuffer png(context);
+  OrthancPlugins::MemoryBuffer png;
   if (RetrievePngPreview(png, instance))
   {
     OrthancPluginAnswerBuffer(context, output, 
@@ -200,7 +200,7 @@
   }
   else
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
   }
 }
 
@@ -208,16 +208,14 @@
 static void AnswerJpegPreview(OrthancPluginRestOutput* output,
                               const std::string& instance)
 {
-  OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
-
   // Retrieve the preview in the PNG format
-  OrthancPlugins::MemoryBuffer png(context);
+  OrthancPlugins::MemoryBuffer png;
   if (!RetrievePngPreview(png, instance))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
   }
   
-  OrthancPlugins::OrthancImage image(context);
+  OrthancPlugins::OrthancImage image;
   image.UncompressPngImage(png.GetData(), png.GetSize());
   image.AnswerJpegImage(output, 90 /* quality */);
 }
@@ -229,15 +227,15 @@
 {
   if (request->method != OrthancPluginHttpMethod_Get)
   {
-    OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "GET");
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
     return;
   }
 
   std::string instance;
   std::string contentType = "image/jpg";  // By default, JPEG image will be returned
-  if (!LocateInstance(instance, contentType, request))
+  if (!LocateInstanceWadoUri(instance, contentType, request))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownResource);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
   }
 
   if (contentType == "application/dicom")
@@ -255,7 +253,8 @@
   }
   else
   {
-    OrthancPlugins::Configuration::LogError("WADO-URI: Unsupported content type: \"" + contentType + "\"");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+    throw Orthanc::OrthancException(
+      Orthanc::ErrorCode_BadRequest,
+      "WADO-URI: Unsupported content type: \"" + contentType + "\"");
   }
 }
--- a/Plugin/WadoUri.h	Fri Jul 15 12:01:19 2016 +0200
+++ b/Plugin/WadoUri.h	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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/README	Fri Jul 15 12:01:19 2016 +0200
+++ b/README	Tue May 26 11:05:10 2020 +0200
@@ -35,9 +35,8 @@
 Installation and usage
 ----------------------
 
-Build instructions can be found in "./Resources/BuildInstructions.txt".
-
-Usage instructions can be found in "./Usage.txt".
+Build and usage instructions are available in the Orthanc Book:
+http://book.orthanc-server.com/plugins/dicomweb.html
 
 
 Licensing: AGPL
@@ -53,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	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/BuildInstructions.txt	Tue May 26 11:05:10 2020 +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	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/CMake/GdcmConfiguration.cmake	Tue May 26 11:05:10 2020 +0200
@@ -1,6 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 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-3.0.4.tar.gz"
+    URL_MD5 "f12dbded708356d5fa0b5ed37ccdb66e"
+    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,13 +101,13 @@
     ${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}
     )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/JavaScriptLibraries.cmake	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,177 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 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/>.
+
+
+set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dicom-web")
+
+DownloadPackage(
+  "da0189f7c33bf9f652ea65401e0a3dc9"
+  "${BASE_URL}/bootstrap-4.3.1.zip"
+  "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1")
+
+DownloadPackage(
+  "8242afdc5bd44105d9dc9e6535315484"
+  "${BASE_URL}/vuejs-2.6.10.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10")
+
+DownloadPackage(
+  "3e2b4e1522661f7fcf8ad49cb933296c"
+  "${BASE_URL}/axios-0.19.0.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0")
+
+DownloadPackage(
+  "a6145901f233f7d54165d8ade779082e"
+  "${BASE_URL}/Font-Awesome-4.7.0.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0")
+
+
+set(BOOTSTRAP_VUE_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-vue-2.0.0-rc.24)
+
+if (BUILD_BOOTSTRAP_VUE OR
+    BUILD_BABEL_POLYFILL)
+  find_program(NPM_EXECUTABLE npm)
+  if (${NPM_EXECUTABLE} MATCHES "NPM_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'npm' standard command-line tool")
+  endif()
+endif()
+
+if (BUILD_BOOTSTRAP_VUE)
+  DownloadPackage(
+    "36ab31495ab94162e159619532e8def5"
+    "${BASE_URL}/bootstrap-vue-2.0.0-rc.24.tar.gz"
+    "${BOOTSTRAP_VUE_SOURCES_DIR}")
+
+  if (NOT IS_DIRECTORY "${BOOTSTRAP_VUE_SOURCES_DIR}/node_modules")
+    execute_process(
+      COMMAND ${NPM_EXECUTABLE} install
+      WORKING_DIRECTORY ${BOOTSTRAP_VUE_SOURCES_DIR}
+      RESULT_VARIABLE Failure
+      OUTPUT_QUIET
+      )
+    
+    if (Failure)
+      message(FATAL_ERROR "Error while running 'npm install' on Bootstrap-Vue")
+    endif()
+  endif()
+
+  if (NOT IS_DIRECTORY "${BOOTSTRAP_VUE_SOURCES_DIR}/dist")
+    execute_process(
+      COMMAND ${NPM_EXECUTABLE} run build
+      WORKING_DIRECTORY ${BOOTSTRAP_VUE_SOURCES_DIR}
+      RESULT_VARIABLE Failure
+      OUTPUT_QUIET
+      )
+    
+    if (Failure)
+      message(FATAL_ERROR "Error while running 'npm build' on Bootstrap-Vue")
+    endif()
+  endif()
+
+else()
+
+  ##
+  ## Generation of the precompiled Bootstrap-Vue package:
+  ##
+  ## Possibility 1 (build from sources):
+  ##  $ cmake -DBUILD_BOOTSTRAP_VUE=ON .
+  ##  $ tar cvfz bootstrap-vue-2.0.0-rc.24-dist.tar.gz bootstrap-vue-2.0.0-rc.24/dist/
+  ##
+  ## Possibility 2 (download from CDN):
+  ##  $ mkdir /tmp/i && cd /tmp/i
+  ##  $ wget -r --no-parent https://unpkg.com/bootstrap-vue@2.0.0-rc.24/dist/
+  ##  $ mv unpkg.com/bootstrap-vue@2.0.0-rc.24/ bootstrap-vue-2.0.0-rc.24
+  ##  $ rm bootstrap-vue-2.0.0-rc.24/dist/index.html
+  ##  $ tar cvfz bootstrap-vue-2.0.0-rc.24-dist.tar.gz bootstrap-vue-2.0.0-rc.24/dist/
+
+  DownloadPackage(
+    "ba0e67b1f0b4ce64e072b42b17f6c578"
+    "${BASE_URL}/bootstrap-vue-2.0.0-rc.24-dist.tar.gz"
+    "${BOOTSTRAP_VUE_SOURCES_DIR}")
+
+endif()
+
+
+if (BUILD_BABEL_POLYFILL)
+  set(BABEL_POLYFILL_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}/node_modules/babel-polyfill/dist)
+
+  if (NOT IS_DIRECTORY "${BABEL_POLYFILL_SOURCES_DIR}")
+    execute_process(
+      COMMAND ${NPM_EXECUTABLE} install babel-polyfill
+      WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      OUTPUT_QUIET
+      )
+    
+    if (Failure)
+      message(FATAL_ERROR "Error while running 'npm install' on Bootstrap-Vue")
+    endif()
+  endif()
+else()
+
+  ## curl -L https://unpkg.com/babel-polyfill@6.26.0/dist/polyfill.min.js | gzip > babel-polyfill-6.26.0.min.js.gz
+
+  set(BABEL_POLYFILL_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR})
+  DownloadCompressedFile(
+    "49f7bad4176d715ce145e75c903988ef"
+    "${BASE_URL}/babel-polyfill-6.26.0.min.js.gz"
+    "${CMAKE_CURRENT_BINARY_DIR}/polyfill.min.js")
+
+endif()
+
+
+set(JAVASCRIPT_LIBS_DIR  ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs)
+file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR})
+
+file(COPY
+  ${BABEL_POLYFILL_SOURCES_DIR}/polyfill.min.js
+  ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.js
+  ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.js.map
+  ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.map
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10/dist/vue.min.js
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/js
+  )
+
+file(COPY
+  ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.css
+  ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.css.map
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/css/font-awesome.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css.map
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/css
+  )
+
+file(COPY
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/FontAwesome.otf
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.eot
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.svg
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.ttf
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.woff
+  ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.woff2
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/fonts
+  )
+
+file(COPY
+  ${ORTHANC_ROOT}/Resources/OrthancLogo.png
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/img
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/GenerateTransferSyntaxes.mustache	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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/>.
+ **/
+
+// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py"
+
+namespace OrthancPlugins
+{
+  gdcm::TransferSyntax GdcmParsedDicomFile::GetGdcmTransferSyntax(Orthanc::DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      {{#Syntaxes}}
+      {{#GDCM}}
+      case Orthanc::DicomTransferSyntax_{{Value}}:
+        return {{GDCM}};
+
+      {{/GDCM}}
+      {{/Syntaxes}}
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  Orthanc::DicomTransferSyntax GdcmParsedDicomFile::GetOrthancTransferSyntax(gdcm::TransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      {{#Syntaxes}}
+      {{#GDCM}}
+      case {{GDCM}}:
+        return Orthanc::DicomTransferSyntax_{{Value}};
+
+      {{/GDCM}}
+      {{/Syntaxes}}
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/GenerateTransferSyntaxes.py	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,50 @@
+#!/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-2020 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/>.
+
+
+import json
+import os
+import pystache
+
+ORTHANC_ROOT = '/home/jodogne/Subversion/orthanc/'
+BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+
+
+with open(os.path.join(ORTHANC_ROOT, 'Resources', 'DicomTransferSyntaxes.json'), 'r') as f:
+    SYNTAXES = json.loads(f.read())
+
+
+with open(os.path.join(BASE, 'Plugin', 'GdcmParsedDicomFile_TransferSyntaxes.impl.h'), 'w') as b:
+    with open(os.path.join(BASE, 'Resources', 'GenerateTransferSyntaxes.mustache'), 'r') as a:
+        b.write(pystache.render(a.read(), {
+            'Syntaxes' : SYNTAXES
+        }))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/MinorityReport.cpp	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,51 @@
+#if 0
+          /**
+           * TODO - Decide which tags are safe (i.e. what is supposed to
+           * be constant?)
+           **/
+          
+          // Those tags are necessary for "DicomImageInformation" in
+          // the Orthanc core (for Stone)
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_BITS_ALLOCATED);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_BITS_STORED);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_COLUMNS);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_HIGH_BIT);
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES);  // => Already in main DICOM tags
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_ROWS);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL);
+
+          // Those tags are necessary for "DicomInstanceParameters" in Stone
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_SOP_CLASS_UID);
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_WINDOW_CENTER);  // varies over each instance
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH);  // varies over each instance
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR);  // TODO => probably unsafe!
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER);  // TODO => probably unsafe!
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_SLICE_THICKNESS);
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT);  // => Already in main DICOM tags
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_RESCALE_SLOPE);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_DOSE_GRID_SCALING);  // TODO => probably unsafe!
+
+          // SeriesMetadataLoader
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_SOP_INSTANCE_UID);  // => Already in main DICOM tags
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID);  // => Already in main DICOM tags
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);  // => Already in main DICOM tags
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE);  // => Meaningless at series level
+
+          // SeriesOrderedFrames
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_INSTANCE_NUMBER);  // => Already in main DICOM tags
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_IMAGE_INDEX);  // => Already in main DICOM tags
+
+          // SeriesFramesLoader
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE); => throws "Exception while invoking plugin service 23: Internal error" in Orthanc 
+          //instances.MinorityReport(dicom, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE);  // varies over each instance
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_REFERENCED_FILE_ID);
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_PATIENT_ID);
+
+          // GeometryToolbox
+          instances.MinorityReport(dicom, Orthanc::DICOM_TAG_PIXEL_SPACING);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/DownloadOrthancFramework.cmake	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,371 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 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")
+      set(ORTHANC_FRAMEWORK_MAJOR 999)
+      set(ORTHANC_FRAMEWORK_MINOR 999)
+      set(ORTHANC_FRAMEWORK_REVISION 999)
+
+    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")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.7")
+        set(ORTHANC_FRAMEWORK_MD5 "e1b76f01116d9b5d4ac8cc39980560e3")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.8")
+        set(ORTHANC_FRAMEWORK_MD5 "82323e8c49a667f658a3639ea4dbc336")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.6.0")
+        set(ORTHANC_FRAMEWORK_MD5 "eab428d6e53f61e847fa360bb17ebe25")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.6.1")
+        set(ORTHANC_FRAMEWORK_MD5 "3971f5de96ba71dc9d3f3690afeaa7c0")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.0")
+        set(ORTHANC_FRAMEWORK_MD5 "ce5f689e852b01d3672bd3d2f952a5ef")
+
+      # Below this point are development snapshots that were used to
+      # release some plugin, before an official release of the Orthanc
+      # framework was available. Here is the command to be used to
+      # generate a proper archive:
+      #
+      #   $ hg archive /tmp/Orthanc-`hg id -i | sed 's/\+//'`.tar.gz
+      #
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df")
+        # DICOMweb 1.1 (framework pre-1.6.0)
+        set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645")
+      endif()
+    endif()
+  endif()
+else()
+  message("Using the Orthanc framework from a path of the filesystem. Assuming mainline version.")
+  set(ORTHANC_FRAMEWORK_MAJOR 999)
+  set(ORTHANC_FRAMEWORK_MINOR 999)
+  set(ORTHANC_FRAMEWORK_REVISION 999)
+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://hg.orthanc-server.com/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	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,79 @@
+#
+# Full build, as used on the BuildBot CIS:
+#
+#   $ 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 -DENABLE_PKCS11=ON -G Ninja
+#
+# Or, more lightweight version (without libp11 and ICU):
+#
+#   $ 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 -G Ninja
+#
+
+INCLUDE(CMakeForceCompiler)
+
+SET(LSB_PATH $ENV{LSB_PATH} CACHE STRING "")
+SET(LSB_CC $ENV{LSB_CC} CACHE STRING "")
+SET(LSB_CXX $ENV{LSB_CXX} CACHE STRING "")
+SET(LSB_TARGET_VERSION "4.0" CACHE STRING "")
+
+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)
+
+if (${CMAKE_VERSION} VERSION_LESS "3.6.0") 
+  CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+else()
+  SET(CMAKE_CXX_COMPILER ${LSB_PATH}/bin/lsbc++)
+endif()
+
+# 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	Tue May 26 11:05:10 2020 +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	Tue May 26 11:05:10 2020 +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	Tue May 26 11:05:10 2020 +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	Tue May 26 11:05:10 2020 +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	Tue May 26 11:05:10 2020 +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-2020 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
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Sdk-1.5.7/orthanc/OrthancCPlugin.h	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,7260 @@
+/**
+ * \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().
+ *    - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback().
+ * -# <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)
+#elif __GNUC__ >= 4
+#  define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default")))
+#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  7
+
+
+#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://bitbucket.org/sjodogne/orthanc/raw/default/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_BadGeometry = 38    /*!< Geometry error encountered in Stone */,
+    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_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
+
+    _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 void*             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,
+    _OrthancPluginService_ChunkedHttpClient = 34,   /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_GetTagName = 35,   /* New in Orthanc 1.5.7 */
+    
+    /* 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,
+    _OrthancPluginService_RegisterChunkedRestCallback = 1012,  /* New in Orthanc 1.5.7 */
+
+    /* 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 void*                 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 void*                 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 void*                 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 void*                 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 void*                 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 void*                 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.
+   * @ingroup Toolbox
+   **/
+  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.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const void*                 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.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const void*                 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.
+   * @ingroup Toolbox
+   **/
+  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 void*                 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()
+   * @ingroup Toolbox
+   **/
+  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 void*                 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 suboperation.
+   * @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 void*                 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 void*                 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;
+    }
+  }
+  
+
+
+  /**
+   * @brief Callback executed when a HTTP header is received during a chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, as soon as it
+   * receives one HTTP header from the answer of the remote HTTP
+   * server.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param answer The user payload, as provided by the calling plugin.
+   * @param key The key of the HTTP header.
+   * @param value The value of the HTTP header.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) (
+    void* answer,
+    const char* key,
+    const char* value);
+
+
+  /**
+   * @brief Callback executed when an answer chunk is received during a chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, as soon as it
+   * receives one data chunk from the answer of the remote HTTP
+   * server.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param answer The user payload, as provided by the calling plugin.
+   * @param data The content of the data chunk.
+   * @param size The size of the data chunk.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) (
+    void* answer,
+    const void* data,
+    uint32_t size);
+  
+
+  /**
+   * @brief Callback to know whether the request body is entirely read during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. The plugin must answer "1" as
+   * soon as the body is entirely read: The "request" data structure
+   * must act as an iterator.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return "1" if the body is over, or "0" if there is still data to be read.
+   * @ingroup Toolbox
+   **/
+  typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request);
+
+
+  /**
+   * @brief Callback to advance in the request body during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. This function asks the plugin
+   * to advance to the next chunk of data of the request body: The
+   * "request" data structure must act as an iterator.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request);
+
+
+  /**
+   * @brief Callback to read the current chunk of the request body during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. The plugin must provide the
+   * content of the current chunk of data of the request body.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return The content of the current request chunk.
+   * @ingroup Toolbox
+   **/
+  typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request);
+
+
+  /**
+   * @brief Callback to read the size of the current request chunk during a chunked transfer 
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP client during a chunked HTTP transfer, while reading
+   * the body of a POST or PUT request. The plugin must provide the
+   * size of the current chunk of data of the request body.
+   *
+   * @see OrthancPluginChunkedHttpClient()
+   * @param request The user payload, as provided by the calling plugin.
+   * @return The size of the current request chunk.
+   * @ingroup Toolbox
+   **/
+  typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request);
+
+  
+  typedef struct
+  {
+    void*                                          answer;
+    OrthancPluginChunkedClientAnswerAddChunk       answerAddChunk;
+    OrthancPluginChunkedClientAnswerAddHeader      answerAddHeader;
+    uint16_t*                                      httpStatus;
+    OrthancPluginHttpMethod                        method;
+    const char*                                    url;
+    uint32_t                                       headersCount;
+    const char* const*                             headersKeys;
+    const char* const*                             headersValues;
+    void*                                          request;
+    OrthancPluginChunkedClientRequestIsDone        requestIsDone;
+    OrthancPluginChunkedClientRequestGetChunkData  requestChunkData;
+    OrthancPluginChunkedClientRequestGetChunkSize  requestChunkSize;
+    OrthancPluginChunkedClientRequestNext          requestNext;
+    const char*                                    username;
+    const char*                                    password;
+    uint32_t                                       timeout;
+    const char*                                    certificateFile;
+    const char*                                    certificateKeyFile;
+    const char*                                    certificateKeyPassword;
+    uint8_t                                        pkcs11;
+  } _OrthancPluginChunkedHttpClient;
+
+  
+  /**
+   * @brief Issue a HTTP call, using chunked HTTP transfers.
+   * 
+   * Make a HTTP call to the given URL using chunked HTTP
+   * transfers. The request body is provided as an iterator over data
+   * chunks. The answer is provided as a sequence of function calls
+   * with the individual HTTP headers and answer chunks.
+   * 
+   * Contrarily to OrthancPluginHttpClient() that entirely stores the
+   * request body and the answer body in memory buffers, this function
+   * uses chunked HTTP transfers. This results in a lower memory
+   * consumption. Pay attention to the fact that Orthanc servers with
+   * version <= 1.5.6 do not support chunked transfers: You must use
+   * OrthancPluginHttpClient() if contacting such older servers.
+   *
+   * 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 answer The user payload for the answer body. It will be provided to the callbacks for the answer.
+   * @param answerAddChunk Callback function to report a data chunk from the answer body.
+   * @param answerAddHeader Callback function to report an HTTP header sent by the remote server.
+   * @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 request The user payload containing the request body, and acting as an iterator.
+   * It will be provided to the callbacks for the request.
+   * @param requestIsDone Callback function to tell whether the request body is entirely read.
+   * @param requestChunkData Callback function to get the content of the current data chunk of the request body.
+   * @param requestChunkSize Callback function to get the size of the current data chunk of the request body.
+   * @param requestNext Callback function to advance to the next data chunk of the request body.
+   * @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 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 OrthancPluginHttpClient()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginChunkedHttpClient(
+    OrthancPluginContext*                          context,
+    void*                                          answer,
+    OrthancPluginChunkedClientAnswerAddChunk       answerAddChunk,
+    OrthancPluginChunkedClientAnswerAddHeader      answerAddHeader,
+    uint16_t*                                      httpStatus,
+    OrthancPluginHttpMethod                        method,
+    const char*                                    url,
+    uint32_t                                       headersCount,
+    const char* const*                             headersKeys,
+    const char* const*                             headersValues,
+    void*                                          request,
+    OrthancPluginChunkedClientRequestIsDone        requestIsDone,
+    OrthancPluginChunkedClientRequestGetChunkData  requestChunkData,
+    OrthancPluginChunkedClientRequestGetChunkSize  requestChunkSize,
+    OrthancPluginChunkedClientRequestNext          requestNext,
+    const char*                                    username,
+    const char*                                    password,
+    uint32_t                                       timeout,
+    const char*                                    certificateFile,
+    const char*                                    certificateKeyFile,
+    const char*                                    certificateKeyPassword,
+    uint8_t                                        pkcs11)
+  {
+    _OrthancPluginChunkedHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    /* In common with OrthancPluginHttpClient() */
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+    params.certificateFile = certificateFile;
+    params.certificateKeyFile = certificateKeyFile;
+    params.certificateKeyPassword = certificateKeyPassword;
+    params.pkcs11 = pkcs11;
+
+    /* For chunked body/answer */
+    params.answer = answer;
+    params.answerAddChunk = answerAddChunk;
+    params.answerAddHeader = answerAddHeader;
+    params.request = request;
+    params.requestIsDone = requestIsDone;
+    params.requestChunkData = requestChunkData;
+    params.requestChunkSize = requestChunkSize;
+    params.requestNext = requestNext;
+
+    return context->InvokeService(context, _OrthancPluginService_ChunkedHttpClient, &params);
+  }
+
+
+
+  /**
+   * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader;
+
+
+
+  /**
+   * @brief Callback to create a reader to handle incoming chunked HTTP transfers.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This
+   * callback is only invoked if the HTTP method is POST or PUT. The
+   * callback must create an user-specific "reader" object that will
+   * be fed with the body of the incoming body.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader Memory location that must be filled with the newly-created reader.
+   * @param url The URI that is accessed.
+   * @param request The body of the HTTP request. Note that "body" and "bodySize" are not used.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderFactory) (
+    OrthancPluginServerChunkedRequestReader**  reader,
+    const char*                                url,
+    const OrthancPluginHttpRequest*            request);
+
+  
+  /**
+   * @brief Callback invoked whenever a new data chunk is available during a chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This callback
+   * is invoked as soon as a new data chunk is available for the request body.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
+   * @param data The content of the data chunk.
+   * @param size The size of the data chunk.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) (
+    OrthancPluginServerChunkedRequestReader* reader,
+    const void*                              data,
+    uint32_t                                 size);
+    
+
+  /**
+   * @brief Callback invoked whenever the request body is entirely received.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This
+   * callback is invoked as soon as the full body of the HTTP request
+   * is available. The plugin can then send its answer thanks to the
+   * provided "output" object.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
+   * @param output The HTTP connection to the client application.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) (
+    OrthancPluginServerChunkedRequestReader* reader,
+    OrthancPluginRestOutput*                 output);
+    
+
+  /**
+   * @brief Callback invoked to release the resources associated with an incoming HTTP chunked transfer.
+   *
+   * Signature of a callback function that is called by Orthanc acting
+   * as a HTTP server that supports chunked HTTP transfers. This
+   * callback is invoked to release all the resources allocated by the
+   * given reader. Note that this function might be invoked even if
+   * the entire body was not read, to deal with client error or
+   * disconnection.
+   * 
+   * @see OrthancPluginRegisterChunkedRestCallback()
+   * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
+   **/
+  typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) (
+    OrthancPluginServerChunkedRequestReader* reader);
+  
+  typedef struct
+  {
+    const char*                                      pathRegularExpression;
+    OrthancPluginRestCallback                        getHandler;
+    OrthancPluginServerChunkedRequestReaderFactory   postHandler;
+    OrthancPluginRestCallback                        deleteHandler;
+    OrthancPluginServerChunkedRequestReaderFactory   putHandler;
+    OrthancPluginServerChunkedRequestReaderAddChunk  addChunk;
+    OrthancPluginServerChunkedRequestReaderExecute   execute;
+    OrthancPluginServerChunkedRequestReaderFinalize  finalize;
+  } _OrthancPluginChunkedRestCallback;
+
+
+  /**
+   * @brief Register a REST callback to handle chunked HTTP transfers.
+   *
+   * 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 callbacks
+   * will NOT be invoked in mutual exclusion, so 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 getHandler The callback function to handle REST calls using the GET HTTP method.
+   * @param postHandler The callback function to handle REST calls using the GET POST method.
+   * @param deleteHandler The callback function to handle REST calls using the GET DELETE method.
+   * @param putHandler The callback function to handle REST calls using the GET PUT method.
+   * @param addChunk The callback invoked when a new chunk is available for the request body of a POST or PUT call.
+   * @param execute The callback invoked once the entire body of a POST or PUT call is read.
+   * @param finalize The callback invoked to release the resources associated with a POST or PUT 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 OrthancPluginRegisterChunkedRestCallback(
+    OrthancPluginContext*                            context,
+    const char*                                      pathRegularExpression,
+    OrthancPluginRestCallback                        getHandler,
+    OrthancPluginServerChunkedRequestReaderFactory   postHandler,
+    OrthancPluginRestCallback                        deleteHandler,
+    OrthancPluginServerChunkedRequestReaderFactory   putHandler,
+    OrthancPluginServerChunkedRequestReaderAddChunk  addChunk,
+    OrthancPluginServerChunkedRequestReaderExecute   execute,
+    OrthancPluginServerChunkedRequestReaderFinalize  finalize)
+  {
+    _OrthancPluginChunkedRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.getHandler = getHandler;
+    params.postHandler = postHandler;
+    params.deleteHandler = deleteHandler;
+    params.putHandler = putHandler;
+    params.addChunk = addChunk;
+    params.execute = execute;
+    params.finalize = finalize;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, &params);
+  }
+
+
+
+
+
+  typedef struct
+  {
+    char**       result;
+    uint16_t     group;
+    uint16_t     element;
+    const char*  privateCreator;
+  } _OrthancPluginGetTagName;
+
+  /**
+   * @brief Returns the symbolic name of a DICOM tag.
+   *
+   * This function makes a lookup to the dictionary of DICOM tags that
+   * are known to Orthanc, and returns the symbolic name of a DICOM tag.
+   *
+   * @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 privateCreator For private tags, the name of the private creator (can be NULL).
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetTagName(
+    OrthancPluginContext*  context,
+    uint16_t               group,
+    uint16_t               element,
+    const char*            privateCreator)
+  {
+    char* result;
+
+    _OrthancPluginGetTagName params;
+    params.result = &result;
+    params.group = group;
+    params.element = element;
+    params.privateCreator = privateCreator;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetTagName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- a/Resources/Samples/JavaScript/qido-rs.js	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/Samples/JavaScript/qido-rs.js	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/Samples/JavaScript/stow-rs.js	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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	Tue May 26 11:05:10 2020 +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	Tue May 26 11:05:10 2020 +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;
+	}
+  } 
+}
--- a/Resources/Samples/Python/SendStow.py	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/Samples/Python/SendStow.py	Tue May 26 11:05:10 2020 +0200
@@ -3,6 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 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
@@ -45,9 +46,11 @@
 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 += f.read()
+            body += content
             body += bytearray('\r\n', 'ascii')
     except:
         print('Ignoring directory %s' % sys.argv[i])
@@ -55,11 +58,31 @@
 # Closing boundary
 body += bytearray('--%s--' % boundary, 'ascii')
 
-# Do the HTTP POST request to the STOW-RS server
-r = requests.post(URL, data=body, headers= {
+headers = {
     'Content-Type' : 'multipart/related; type=application/dicom; boundary=%s' % boundary,
     'Accept' : 'application/json',
-})
+    }
+
+# Do the HTTP POST request to the STOW-RS server
+if False:
+    # Don't use chunked transfer (this code was in use in DICOMweb plugin <= 0.6)
+    r = requests.post(URL, data=body, headers=headers)
+else:
+    # Use chunked transfer
+    # https://2.python-requests.org/en/master/user/advanced/#chunk-encoded-requests
+    def gen():
+        chunkSize = 1024 * 1024
+
+        l = len(body) / chunkSize
+        for i in range(l):
+            pos = i * chunkSize
+            yield body[pos : pos + chunkSize]
+
+        if len(body) % chunkSize != 0:
+            yield body[l * chunkSize :]
+
+    r = requests.post(URL, data=gen(), headers=headers)
+
 
 j = json.loads(r.text)
 
--- a/Resources/Samples/Python/WadoRetrieveStudy.py	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/Samples/Python/WadoRetrieveStudy.py	Tue May 26 11:05:10 2020 +0200
@@ -3,6 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 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/SyncOrthancFolder.py	Fri Jul 15 12:01:19 2016 +0200
+++ b/Resources/SyncOrthancFolder.py	Tue May 26 11:05:10 2020 +0200
@@ -9,54 +9,24 @@
 import os
 import stat
 import urllib2
-import uuid
 
-TARGET = os.path.join(os.path.dirname(__file__), '..', 'Orthanc')
-PLUGIN_SDK_VERSION = '1.1.0'
-REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+PLUGIN_SDK_VERSION = '1.5.7'
+REPOSITORY = 'https://hg.orthanc-server.com/orthanc/raw-file'
 
 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',
-    'Core/WebServiceParameters.cpp',
-    'Core/WebServiceParameters.h',
-    'Plugins/Samples/Common/ExportedSymbols.list',
-    'Plugins/Samples/Common/OrthancPluginCppWrapper.h',
-    'Plugins/Samples/Common/OrthancPluginCppWrapper.cpp',
-    '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]
@@ -68,37 +38,26 @@
     except:
         pass
 
-    url = '%s/%s/%s?force=%s' % (REPOSITORY, branch, source, uuid.uuid4())
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
 
     with open(target, 'w') as f:
-        try:
-            f.write(urllib2.urlopen(url).read())
-        except:
-            print('Cannot download %s' % url)
-            raise
+        f.write(urllib2.urlopen(url).read())
 
 
 commands = []
 
 for f in FILES:
-    commands.append([ 'default', f, f ])
+    commands.append([ 'default',
+                      os.path.join('Resources', f),
+                      f ])
 
 for f in SDK:
-    if PLUGIN_SDK_VERSION == 'mainline':
-        branch = 'default'
-    else:
-        branch = 'Orthanc-%s' % PLUGIN_SDK_VERSION
-
-    commands.append([ branch, 
-                      '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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/holy-build-box-compile.sh	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+##
+## This script compiles cross-distribution Linux binaries thanks to
+## Holy Build Box: https://github.com/phusion/holy-build-box
+##
+## The ideal solution would be to use Linux Standard Base
+## (LSB). Unfortunately, the LSB C++ compiler is a pre-4.8 gcc that
+## does not feature full C++11 capabilities, which prevents compiling
+## GDCM >= 3.0.
+##
+
+set -ex
+
+if [ "$1" != "Debug" -a "$1" != "Release" ]; then
+    echo "Please provide build type: Debug or Release"
+    exit -1
+fi
+
+if [ -t 1 ]; then
+    # TTY is available => use interactive mode
+    DOCKER_FLAGS='-i'
+fi
+
+ROOT_DIR=`dirname $(readlink -f $0)`/..
+
+mkdir -p ${ROOT_DIR}/holy-build-box
+
+docker run -t ${DOCKER_FLAGS} --rm \
+    --user $(id -u):$(id -g) \
+    -v ${ROOT_DIR}:/source:ro \
+    -v ${ROOT_DIR}/holy-build-box:/target:rw \
+    phusion/holy-build-box-64:2.0.1 \
+    bash /source/Resources/holy-build-box-internal.sh $1
+
+ls -lR ${ROOT_DIR}/holy-build-box/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/holy-build-box-internal.sh	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,52 @@
+#!/bin/bash
+set -e
+
+# Activate Holy Build Box environment.
+source /hbb_exe/activate
+
+set -x
+
+
+# Download Mercurial to use the mainline of Orthanc framework
+MERCURIAL_VERSION=5.3
+curl https://www.mercurial-scm.org/release/mercurial-${MERCURIAL_VERSION}.tar.gz > /tmp/mercurial.tar.gz
+cd /tmp
+tar xvf mercurial.tar.gz
+export PATH=$PATH:/tmp/mercurial-${MERCURIAL_VERSION}
+
+
+mkdir /tmp/build
+cd /tmp/build
+
+# Holy Build Box defines LDFLAGS as "-L/hbb_exe/lib
+# -static-libstdc++". The "-L/hbb_exe/lib" option results in linking
+# errors "undefined reference" to `std::__once_callable',
+# 'std::__once_call' and '__once_proxy'.
+export LDFLAGS=-static-libstdc++
+unset LDPATHFLAGS
+unset SHLIB_LDFLAGS
+unset LD_LIBRARY_PATH
+unset LIBRARY_PATH
+
+mkdir /tmp/source-writeable
+
+cp -r /source/CMakeLists.txt /tmp/source-writeable/
+cp -r /source/Plugin /tmp/source-writeable/
+cp -r /source/Resources /tmp/source-writeable/
+cp -r /source/UnitTestsSources /tmp/source-writeable/
+cp -r /source/WebApplication /tmp/source-writeable/
+
+cmake /tmp/source-writeable \
+      -DCMAKE_BUILD_TYPE=$1 -DSTATIC_BUILD=ON \
+      -DORTHANC_SDK_VERSION=1.5.7 \
+      -DORTHANC_FRAMEWORK_SOURCE=hg \
+      -DORTHANC_FRAMEWORK_VERSION=mainline \
+      -DCMAKE_INSTALL_PREFIX=/target 
+
+make -j`nproc`
+
+if [ "$1" == "Release" ]; then
+    strip ./libOrthancDicomWeb.so
+fi
+
+make install
--- a/Status.txt	Fri Jul 15 12:01:19 2016 +0200
+++ b/Status.txt	Tue May 26 11:05:10 2020 +0200
@@ -1,5 +1,9 @@
-Reference: http://medical.nema.org/medical/dicom/current/output/html/part18.html
+Reference: http://dicom.nema.org/MEDICAL/dicom/2019a/output/html/part18.html
 
+If you need some missing feature as an industrial player, please
+consider hiring the development team from Osimis by filling the
+dedicated form on the Orthanc Web site:
+https://www.orthanc-server.com/orthanc-pro.php
 
 
 =======================================
@@ -82,6 +86,30 @@
 
 
 
+===========================================
+6.5.8 WADO-RS / RetrieveRenderedTransaction
+===========================================
+
+http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part18/sect_6.5.8.html
+
+Supported
+---------
+
+* Single-frame and multi-frame retrieval
+* JPEG and PNG output
+* "quality" parameter
+* "viewport" parameter
+* "window" parameter
+
+Not supported
+-------------
+
+* GIF output
+* The following "Retrieve Rendered Query Parameters" (table 6.5.8-2):
+  annotation, charset, iccprofile
+
+
+
 ===========
 6.6 STOW-RS
 ===========
@@ -120,3 +148,35 @@
 
 * Flag "fuzzymatching"
 * Header "Cache-control"
+
+
+
+===================
+6.8 RS Capabilities
+===================
+
+Not supported.
+
+
+
+===================
+6.9 UPS-RS Worklist
+===================
+
+Not supported.
+
+
+
+==========================================================
+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	Fri Jul 15 12:01:19 2016 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue May 26 11:05:10 2020 +0200
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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,15 +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;
@@ -40,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());  
--- a/Usage.txt	Fri Jul 15 12:01:19 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-=============
-Configuration
-=============
-
-(1) You must change the Orthanc configuration file to tell Orthanc
-    where it can find the DICOMweb plugin. This is done by properly
-    modifying the "Plugins" configuration option of Orthanc. For
-    instance, in Linux:
-
-{
-  ...
-  "Plugins" : [
-    "/home/user/OrthancDicomWeb/Build/libOrthancDicomWeb.so"
-  ]
-  ...
-}
-
-    Or in Windows:
-
-{
-  ...
-  "Plugins" : [
-    "c:/Temp/OrthancDicomWeb.dll"
-  ]
-  ...
-}
-
-    Note that the DICOMweb server will share all the parameters of the
-    Orthanc HTTP server, notably wrt. authentication and HTTPS
-    encryption. For this reason, you will most probably have to enable
-    the remote access to the Orthanc HTTP server:
-
-{
-  ...
-  "RemoteAccessEnabled" : true
-  ...
-}
-
-
-(2) There are several configuration options that can be set to
-    fine-tune the Orthanc DICOMweb server. Here is the full list of
-    the available options, all of them must be grouped inside the
-    "DicomWeb" section of the Orthanc configuration file:
-
-{
-  ...
-  "DicomWeb" : {
-    "Enable" : true,          // Whether DICOMweb support is enabled
-    "Root" : "/dicom-web/",   // Root URI of the DICOMweb API (for QIDO-RS, STOW-RS and WADO-RS)
-    "EnableWado" : true,      // Whether WADO-URI (previously known as WADO) support is enabled
-    "WadoRoot" : "/wado",     // Root URI of the WADO-URI (aka. WADO) API
-    "Host" : "localhost",     // Hard-codes the name of the host for subsequent WADO-RS requests
-    "Ssl" : false,            // Whether HTTPS should be used for subsequent WADO-RS requests
-    "StowMaxInstances" : 10,  // For STOW-RS client, the maximum number of instances in one single HTTP query (0 = no limit)
-    "StowMaxSize" : 10        // For STOW-RS client, the maximum size of the body in one single HTTP query (in MB, 0 = no limit)
-  }
-  ...
-}
-
-
-(3) If you want to connect Orthanc as a client to remote DICOMweb
-    servers (cf. below), you need to modify the configuration file so
-    as to define each of them in the option "DicomWeb.Servers".  The
-    syntax is identical to the "OrthancPeers" parameters.
-
-    In the most simple case, here is how to instruct Orthanc about the
-    existence of a password-less DICOMweb server that will be refered
-    to as "sample" in Orthanc:
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : [ "http://192.168.1.1/dicom-web/" ]
-    }
-  }
-  ...
-}
-
-    You are of course free to add as many DICOMweb servers as you
-    need. If the DICOMweb server is protected by a password (with HTTP
-    Basic access authentication):
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : [ "http://192.168.1.1/dicom-web/", "username", "password" ]
-    }
-  }
-  ...
-}
-
-    If the DICOMweb server is protected with HTTPS client
-    authentication, you must provide your client certificate (in the
-    PEM format), your client private key (in the PEM format), together
-    with the password protecting the private key:
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : {
-        "Url" : "http://192.168.1.1/dicom-web/", 
-        "CertificateFile" : "client.crt",
-        "CertificateKeyFile" : "client.key",
-        "CertificateKeyPassword" : "password"
-      }
-    }
-  }
-  ...
-}
-
-    Finally, it is also possible to use client authentication with
-    hardware security modules and smart cards through PKCS#11 (this
-    feature is only available is the core of Orthanc was compiled with
-    the "-DENABLE_PKCS11=ON" option in CMake, and if the Orthanc
-    configuration file has a proper "Pkcs11" section):
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : {
-        "Url" : "http://192.168.1.1/dicom-web/", 
-        "Pkcs11" : true
-      }
-    }
-  }
-  ...
-}
-
-    Important remark: When querying a DICOMweb server, Orthanc will
-    automatically use the global configuration options "HttpProxy",
-    "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and
-    "Pkcs11". Make sure to adapt them if need be.
-
-
-
-=================================
-Querying a remote DICOMweb server
-=================================
-
-Listing the available servers
------------------------------
-
-The list of the remote DICOMweb servers that are known to the DICOMweb
-plugin can be obtained as follows:
-
-# curl http://localhost:8042/dicom-web/servers/
-[ "sample" ]
-
-Here, a single server called "sample" is configured.
-
-
-Making a call to QIDO-RS or WADO-RS
------------------------------------
-
-In Orthanc, the URI "/{dicom-web}/servers/{name}/get" allows to make a
-HTTP GET call against a DICOMweb server. This can be used to issue a
-QIDO-RS or WADO-RS command. Orthanc will take care of properly
-encoding the URL and authenticating the client.
-
-For instance, here is a sample QIDO-RS search to query all the
-studies (using a bash command-line):
-
-# curl http://localhost:8042/dicom-web/servers/sample/get -d @- << EOF
-{
-  "Uri" : "/studies"
-}
-EOF
-
-You do not have to specify the base URL of the remote DICOMweb server,
-as it is encoded in the configuration file.
-
-The result of the command above is a multipart "application/dicom+xml"
-document.  It is possible to request a more human-friendly JSON answer
-by adding the "Accept" HTTP header. Here is how to search for a given
-patient name, while requesting a JSON answer and pretty-printing
-through the "json_pp" command-line tool:
-
-# curl http://localhost:8042/dicom-web/servers/sample/get -d @- << EOF | json_pp 
-{
-  "Uri" : "/studies",
-  "HttpHeaders" : {
-    "Accept" : "application/json"
-  },
-  "Arguments" : {
-    "00100010" : "*JODOGNE*"
-  }
-}
-EOF
-
-Note how all the GET arguments must be specified in the "Arguments"
-field. Orthanc will take care of properly encoding it to a URL.
-
-An user-friendly reference of the features available in QIDO-RS and
-WADO-RS can be found at http://dicomweb.hcintegrations.ca/#/home
-
-
-Sending DICOM resources to a STOW-RS server
--------------------------------------------
-
-STOW-RS allows to send local DICOM resources to a remote DICOMweb
-server. In Orthanc, the STOW-RS client primitive is available at URI
-"/{dicom-web}/servers/{name}/stow". Here is a sample call:
-
-# curl http://localhost:8042/dicom-web/servers/sample/stow -X POST -d @- << EOF
-{
-  "Resources" : [
-    "6ca4c9f3-5e895cb3-4d82c6da-09e060fe-9c59f228"
-  ]
-}
-EOF
-
-Note that this primitive takes as its input a list of Orthanc
-identifiers corresponding to the resources (patients, studies, series
-and/or instances) to be exported:
-https://orthanc.chu.ulg.ac.be/book/faq/orthanc-ids.html
-
-Remark 1: Additional HTTP headers can be added with an optional
-"HttpHeaders" argument, as for QIDO-RS and WADO-RS. This might be
-useful e.g. for cookie-based session management.
-
-Remark 2: One call to this "/stow" primitive will possibly result in
-several HTTP requests to the DICOMweb server, in order to limit the
-size of the HTTP messages. The configuration options
-"DicomWeb.StowMaxInstances" and "DicomWeb.StowMaxSize" can be used to
-tune this behavior (set both options to 0 to send one single request).
-
-
-Retrieving DICOM resources from a WADO-RS server
-------------------------------------------------
-
-Once DICOM resources of interest have been identified through a
-QIDO-RS call to a remote DICOMweb server (cf. above), it is
-interesting to download them locally with a WADO-RS call. You could do
-it manually with a second call to the
-"/{dicom-web}/servers/{name}/get" URI, but Orthanc provides another
-primitive "/retrieve" to automate this process.
-
-Here is how you would download one study, one series and one instance
-whose StudyInstanceUID (0020,000d), SeriesInstanceUID (0020,000e) are
-SOPInstanceUID (0008,0018) have been identified through a former
-QIDO-RS call:
-
-# curl http://localhost:8042/dicom-web/servers/sample/retrieve -X POST -d @- << EOF
-{
-  "Resources" : [
-    {
-      "Study" : "1.3.51.0.1.1.192.168.29.133.1688840.1688819"
-    },
-    {
-      "Study" : "1.3.51.0.1.1.192.168.29.133.1681753.1681732",
-      "Series" : "1.3.12.2.1107.5.2.33.37097.2012041613040617636372171.0.0.0"
-    },
-    {
-      "Study" : "1.3.51.0.1.1.192.168.29.133.1681753.1681732",
-      "Series" : "1.3.12.2.1107.5.2.33.37097.2012041612474981424569674.0.0.0",
-      "Instance" : "1.3.12.2.1107.5.2.33.37097.2012041612485540185869716"
-    }
-  ]
-}
-EOF
-
-Orthanc will reply with the list of the Orthanc identifiers of all the
-DICOM instances that were downloaded from the remote server.
-
-Remark 1: Contrarily to the "/stow" URI that uses Orthanc identifiers,
-the "/retrieve" URI uses DICOM identifiers.
-
-Remark 2: The "HttpArguments" is also available.
-
-
-
-=======
-Samples
-=======
-
-Samples of how to call DICOMweb services from standalone applications
-can be found in the following folders:
-
-- In Python: see ./Resources/Samples/Python/
-- In JavaScript: see ./Resources/Samples/Python/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/app.js	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,398 @@
+var DICOM_TAG_ACCESSION_NUMBER = '00080050';
+var DICOM_TAG_MODALITY = '00080060';
+var DICOM_TAG_PATIENT_ID = '00100020';
+var DICOM_TAG_PATIENT_NAME = '00100010';
+var DICOM_TAG_SERIES_DESCRIPTION = '0008103E';
+var DICOM_TAG_SERIES_INSTANCE_UID = '0020000E';
+var DICOM_TAG_SOP_INSTANCE_UID = '00080018';
+var DICOM_TAG_STUDY_DATE = '00080020';
+var DICOM_TAG_STUDY_ID = '00200010';
+var DICOM_TAG_STUDY_INSTANCE_UID = '0020000D';
+var MAX_RESULTS = 100;
+
+/**
+ * This is a minimal 1x1 PNG image with white background, as generated by:
+ *   $ convert -size 1x1 -define png:include-chunk=none xc:white png:- | base64 -w 0
+ **/
+var DEFAULT_PREVIEW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg==';
+
+var app = new Vue({
+  el: '#app',
+  computed: {
+    studiesCount() {
+      return this.studies.length
+    },
+    seriesCount() {
+      return this.series.length
+    }
+  },
+  data: {
+    orthancApiRoot: '../../../',
+    previewFailure: true,
+    preview: DEFAULT_PREVIEW,
+    showTruncatedStudies: false,
+    showNoServer: false,
+    showStudies: false,
+    showSeries: false,
+    maxResults: MAX_RESULTS,
+    currentPage: 0,
+    perPage: 10,
+    servers: [ ],
+    serversInfo: { },
+    activeServer: '',
+    lookup: { },
+    studies: [ ],
+    currentStudy: null,
+    jobId: '',
+    jobLevel: '',
+    jobUri: '',
+    jobDetails: '',
+    studiesFields: [
+      {
+        key: DICOM_TAG_PATIENT_ID + '.Value',
+        label: 'Patient ID',
+        sortable: true
+      },
+      {
+        key: DICOM_TAG_PATIENT_NAME + '.Value',
+        label: 'Patient name',
+        sortable: true
+      },
+      {
+        key: DICOM_TAG_ACCESSION_NUMBER + '.Value',
+        label: 'Accession number',
+        sortable: true
+      },
+      {
+        key: DICOM_TAG_STUDY_DATE + '.Value',
+        label: 'Study date',
+        sortable: true
+      },
+      {
+        key: 'operations',
+        label: ''
+      }
+    ],
+    studyToDelete: null,
+    studyTags: [ ],    
+    studyTagsFields: [
+      {
+        key: 'Tag',
+        sortable: true
+      },
+      {
+        key: 'Name',
+        label: 'Description',
+        sortable: true
+      },
+      {
+        key: 'Value',
+        sortable: true
+      }
+    ],
+    series: [ ],
+    seriesFields: [
+      {
+        key: DICOM_TAG_SERIES_DESCRIPTION + '.Value',
+        label: 'Series description',
+        sortable: true
+      },
+      {
+        key: DICOM_TAG_MODALITY + '.Value',
+        label: 'Modality',
+        sortable: true
+      },
+      {
+        key: 'operations',
+        label: ''
+      }
+    ],
+    seriesToDelete: null,
+    seriesTags: [ ],    
+    seriesTagsFields: [
+      {
+        key: 'Tag',
+        sortable: true
+      },
+      {
+        key: 'Name',
+        label: 'Description',
+        sortable: true
+      },
+      {
+        key: 'Value',
+        sortable: true
+      }
+    ],
+    scrollToSeries: false,
+    scrollToStudies: false
+  },
+  mounted: () => {
+    axios
+      .get('../../servers?expand')
+      .then(response => {
+        app.serversInfo = response.data;
+        app.servers = Object.keys(response.data).map(i => i);
+        app.Clear();
+      });
+    axios
+      .get('../../info')
+      .then(response => {
+        app.orthancApiRoot = response.data.OrthancApiRoot;
+        if (!app.orthancApiRoot.endsWith('/')) {
+          app.orthancApiRoot += '/';
+        }
+        app.orthancApiRoot += '../../';  // To be at the same level as "info"
+      });
+  },
+  methods: {
+    /**
+     * Toolbox
+     **/
+
+    ScrollToRef: function(refName) {
+      var element = app.$refs[refName];
+      window.scrollTo(0, element.offsetTop);
+    },
+    ShowErrorModal: function() {
+      app.$refs['modal-error'].show();
+    },
+    RefreshJobDetails: function() {
+      axios
+        .get(app.jobUri)
+        .then(response => {
+          app.jobDetails = response.data;
+        })
+        .catch(response => {
+          app.jobDetails = 'Job details are not available';
+        })
+    },
+
+    
+    /**
+     * Studies
+     **/
+
+    SetStudies: function(response) {
+      if (response.data.length > app.maxResults) {
+        app.showTruncatedStudies = true;
+        app.studies = response.data.splice(0, app.maxResults);
+      } else {
+        app.showTruncatedStudies = false;
+        app.studies = response.data;
+      }
+      app.showStudies = true;
+      app.showSeries = false;
+      app.studyToDelete = null;
+      app.scrollToStudies = true;
+    },
+    ExecuteLookup: function() {
+      var args = { 
+        'fuzzymatching' : 'true',
+        'limit' : (app.maxResults + 1).toString()
+      };
+
+      if ('patientName' in app.lookup) {
+        args[DICOM_TAG_PATIENT_NAME] = app.lookup.patientName;
+      }
+
+      if ('patientID' in app.lookup) {
+        args[DICOM_TAG_PATIENT_ID] = app.lookup.patientID;
+      }
+
+      if ('studyDate' in app.lookup) {
+        args[DICOM_TAG_STUDY_DATE] = app.lookup.studyDate;
+      }
+
+      if ('accessionNumber' in app.lookup) {
+        args[DICOM_TAG_ACCESSION_NUMBER] = app.lookup.accessionNumber;
+      }
+
+      app.activeServer = app.lookup.server;
+      axios
+        .post('../../servers/' + app.activeServer + '/qido', {
+          'Uri' : '/studies',
+          'Arguments' : args,
+        })
+        .then(app.SetStudies)
+        .catch(response => {
+          app.showStudies = false;
+          app.showSeries = false;
+          app.ShowErrorModal();
+        });
+    },
+    Clear: function() {
+      app.lookup = {};
+      currentStudy = null;
+      app.showSeries = false;
+      app.showStudies = false;
+      if (app.servers.length == 0) {
+        app.showNoServer = true;
+      } else {
+        app.showNoServer = false;
+        app.lookup.server = app.servers[0];
+      }
+    },
+    OnLookup: function(event) {
+      event.preventDefault();
+      app.ExecuteLookup();
+    },
+    OnReset: function(event) {
+      event.preventDefault();
+      app.Clear();
+    },
+    OpenStudyDetails: function(study) {
+      app.studyTags = Object.keys(study).map(i => {
+        var item = study[i];
+        item['Tag'] = i;
+        return item;
+      });
+      
+      app.$refs['study-details'].show();
+    },
+    RetrieveStudy: function(study) {
+      var base = '../../servers/';
+      axios
+        .post(base + app.activeServer + '/wado', {
+          'Uri' : '/studies/' + study[DICOM_TAG_STUDY_INSTANCE_UID].Value
+        })
+        .then(response => {
+          app.jobLevel = 'study';
+          app.jobId = response.data.ID;
+          app.jobUri = base + response.data.Path;
+          app.$refs['retrieve-job'].show();
+          app.RefreshJobDetails();
+        });
+    },
+    ConfirmDeleteStudy: function(study) {
+      app.studyToDelete = study;
+      app.$bvModal.show('study-delete-confirm');
+    },
+    ExecuteDeleteStudy: function(study) {
+      axios
+        .post('../../servers/' + app.activeServer + '/delete', {
+          'Level': 'Study',
+          'StudyInstanceUID': app.studyToDelete[DICOM_TAG_STUDY_INSTANCE_UID].Value
+        })
+        .then(app.ExecuteLookup)
+        .catch(app.ShowErrorModal)
+    },
+
+    
+    /**
+     * Series
+     **/
+
+    LoadSeriesOfCurrentStudy: function() {
+      axios
+        .post('../../servers/' + app.activeServer + '/qido', {
+          'Uri' : '/studies/' + app.currentStudy + '/series'
+        })
+        .then(response => {
+          if (response.data.length > 0) {
+            app.series = response.data;
+            app.showSeries = true;
+            app.seriesToDelete = null;
+            app.scrollToSeries = true;
+          } else {
+            // No more series, so no more study, so re-lookup
+            app.ExecuteLookup();
+          }
+        })
+        .catch(app.ShowErrorModal);
+    }, 
+    OpenSeries: function(series) {
+      app.currentStudy = series[DICOM_TAG_STUDY_INSTANCE_UID].Value;
+      app.LoadSeriesOfCurrentStudy();
+    },
+    OpenSeriesDetails: function(series) {
+      app.seriesTags = Object.keys(series).map(i => {
+        var item = series[i];
+        item['Tag'] = i;
+        return item;
+      });
+      
+      app.$refs['series-details'].show();
+    },
+    RetrieveSeries: function(series) {
+      var base = '../../servers/';
+      axios
+        .post(base + app.activeServer + '/wado', {
+          'Uri' : ('/studies/' + app.currentStudy + 
+                   '/series/' + series[DICOM_TAG_SERIES_INSTANCE_UID].Value)
+        })
+        .then(response => {
+          app.jobLevel = 'series';
+          app.jobId = response.data.ID;
+          app.jobUri = base + response.data.Path;
+          app.$refs['retrieve-job'].show();
+          app.RefreshJobDetails();
+        });
+    },
+    OpenSeriesPreview: function(series) {
+      axios
+        .post('../../servers/' + app.activeServer + '/get', {
+          'Uri' : ('/studies/' + app.currentStudy + '/series/' + 
+                   series[DICOM_TAG_SERIES_INSTANCE_UID].Value + '/instances')
+        })
+        .then(response => {
+          var instance = response.data[Math.floor(response.data.length / 2)];
+
+          axios
+            .post('../../servers/' + app.activeServer + '/get', {
+              'Uri' : ('/studies/' + app.currentStudy + '/series/' + 
+                       series[DICOM_TAG_SERIES_INSTANCE_UID].Value + '/instances/' +
+                       instance[DICOM_TAG_SOP_INSTANCE_UID].Value + '/rendered')
+            }, {
+              responseType: 'arraybuffer'
+            })
+            .then(response => {
+              // https://github.com/axios/axios/issues/513
+              var image = btoa(new Uint8Array(response.data)
+                               .reduce((data, byte) => data + String.fromCharCode(byte), ''));
+              app.preview = ("data:" + 
+                             response.headers['content-type'].toLowerCase() + 
+                             ";base64," + image);
+              app.previewFailure = false;
+            })
+            .catch(response => {
+              app.previewFailure = true;
+            })
+              .finally(function() {
+                app.$refs['series-preview'].show();
+              })
+        })
+    },
+    ConfirmDeleteSeries: function(series) {
+      app.seriesToDelete = series;
+      app.$bvModal.show('series-delete-confirm');
+    },
+    ExecuteDeleteSeries: function(series) {
+      axios
+        .post('../../servers/' + app.activeServer + '/delete', {
+          'Level': 'Series',
+          'StudyInstanceUID': app.currentStudy,
+          'SeriesInstanceUID': app.seriesToDelete[DICOM_TAG_SERIES_INSTANCE_UID].Value
+        })
+        .then(app.LoadSeriesOfCurrentStudy)
+        .catch(app.ShowErrorModal)
+    }
+  },
+
+  updated: function () {
+    this.$nextTick(function () {
+      // Code that will run only after the
+      // entire view has been re-rendered
+
+      if (app.scrollToStudies) {
+        app.scrollToStudies = false;
+        app.ScrollToRef('studies-top');
+      }
+
+      if (app.scrollToSeries) {
+        app.scrollToSeries = false;
+        app.ScrollToRef('series-top');
+      }
+    })
+  }
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/index.html	Tue May 26 11:05:10 2020 +0200
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+    <title>Orthanc - DICOMweb client</title>
+
+    <!-- Add Bootstrap and Bootstrap-Vue CSS to the <head> section -->
+    <link type="text/css" rel="stylesheet" href="../libs/css/bootstrap.min.css"/>
+    <link type="text/css" rel="stylesheet" href="../libs/css/bootstrap-vue.min.css"/>
+    <link type="text/css" rel="stylesheet" href="../libs/css/font-awesome.min.css"/>
+    
+    <script src="../libs/js/polyfill.min.js"></script>
+   
+    <!-- CSS style to truncate long text in tables, provided they have
+         class "table-layout:fixed;" or attribute ":fixed=true" -->
+    <style>
+      table td { 
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      }
+    </style>
+    
+  </head>
+  <body>
+    <div class="container" id="app">
+      <p style="height:1em"></p>
+
+      <div class="jumbotron">
+        <div class="row">
+          <div class="col-sm-8">
+            <h1 class="display-4">DICOMweb client</h1>
+            <p class="lead">
+              This is a simple client interface to the DICOMweb
+              servers that are configured in Orthanc. From this page,
+              you can search the content of remote DICOMweb servers
+              (QIDO-RS), then locally retrieve the DICOM
+              studies/series of interest
+              (WADO-RS). <a :href="orthancApiRoot"
+              target="_blank">Orthanc Explorer</a> can be used to send
+              DICOM resources to remote DICOMweb servers (STOW-RS).
+            </p>
+            <p>
+              <a class="btn btn-primary btn-lg"
+                 href="https://book.orthanc-server.com/plugins/dicomweb.html"
+                 target="_blank" role="button">Open documentation</a>
+              <a class="btn btn-primary btn-lg"
+                 :href="orthancApiRoot"
+                 target="_blank" role="button">Open Orthanc Explorer</a>
+            </p>
+          </div>
+          <div class="col-sm-4">
+            <a href="http://www.orthanc-server.com/" target="_blank">
+              <img class="img-fluid" alt="Orthanc" src="../libs/img/OrthancLogo.png" />
+            </a>
+          </div>
+        </div>
+      </div>
+
+
+      <b-modal ref="modal-error" size="xl" ok-only="true">
+        <template slot="modal-title">
+          Connection error
+        </template>
+        <div class="d-block">
+          <p>
+            There was an error connecting to "{{ activeServer }}" server.
+          </p>
+        </div>
+      </b-modal>
+
+      
+      <!-- LOOKUP -->
+
+      <div class="row">
+        <b-alert variant="danger" dismissible v-model="showNoServer">
+          No DICOMweb server is configured!
+        </b-alert>
+        <b-form style="width:100%;padding:5px;">
+          <b-form-group label="DICOMweb server:" label-cols-sm="4" label-cols-lg="3">
+            <b-form-select v-model="lookup.server" :options="servers"></b-form-select>
+          </b-form-group>
+          <b-form-group label="Patient ID:" label-cols-sm="4" label-cols-lg="3">
+            <b-form-input v-model="lookup.patientID"></b-form-input>
+          </b-form-group>
+          <b-form-group label="Patient name:" label-cols-sm="4" label-cols-lg="3">
+            <b-form-input v-model="lookup.patientName"></b-form-input>
+          </b-form-group>
+          <b-form-group label="Accession number:" label-cols-sm="4" label-cols-lg="3">
+            <b-form-input v-model="lookup.accessionNumber"></b-form-input>
+          </b-form-group>
+          <b-form-group label="Study date:" label-cols-sm="4" label-cols-lg="3">
+            <b-form-input v-model="lookup.studyDate"></b-form-input>
+          </b-form-group>
+          <p class="pull-right">
+          <b-button type="submit" variant="success" @click="OnLookup" 
+                    size="lg">Do lookup</b-button>
+          <b-button type="reset" variant="outline-danger" @click="OnReset" 
+                    size="lg">Reset</b-button>
+          </p>
+        </b-form>
+      </div>
+
+
+      <!-- STUDIES -->
+
+      <hr v-show="showStudies" ref="studies-top" />
+      <div class="row" v-show="showStudies">
+        <h1>Studies</h1>
+      </div>
+      <div class="row" v-show="showStudies">
+        <b-alert variant="warning" dismissible v-model="showTruncatedStudies">
+          More than {{ maxResults }} matching studies, results have been truncated!
+        </b-alert>
+      </div>
+      <div class="row" v-show="showStudies">
+        <b-pagination v-model="currentPage" :per-page="perPage" :total-rows="studiesCount"></b-pagination>
+        <b-table striped hover :current-page="currentPage" :per-page="perPage"
+                 :items="studies" :fields="studiesFields" :fixed="false">
+          <template slot="operations" slot-scope="data">
+            <b-button @click="OpenSeries(data.item)" title="Open series">
+              <i class="fa fa-folder-open"></i>
+            </b-button>
+            <b-button @click="OpenStudyDetails(data.item)" title="Open tags">
+              <i class="fa fa-address-card"></i>
+            </b-button>
+            <b-button @click="RetrieveStudy(data.item)" title="Retrieve study using WADO-RS">
+              <i class="fa fa-cloud-download"></i>
+            </b-button>
+            <b-button @click="ConfirmDeleteStudy(data.item)"
+                      v-if="serversInfo[activeServer].HasDelete == '1'" title="Delete remote study">
+              <i class="fa fa-trash"></i>
+            </b-button>
+          </template>
+        </b-table>
+
+        <b-modal ref="study-details" size="xl" ok-only="true">
+          <template slot="modal-title">
+            Details of study
+          </template>
+          <div class="d-block text-center">
+            <b-table striped :items="studyTags" :fields="studyTagsFields" :fixed="true">
+            </b-table>
+          </div>
+        </b-modal>
+
+        <b-modal id="study-delete-confirm" size="xl" @ok="ExecuteDeleteStudy">
+          <template slot="modal-title">
+            Confirm deletion
+          </template>
+          <div class="d-block">
+            <p>
+              Are you sure you want to remove this study from the remote server?
+            </p>
+            <p>
+              Patient name: {{ studyToDelete && studyToDelete['00100010'] && studyToDelete['00100010'].Value }}
+            </p>
+          </div>
+        </b-modal>
+      </div>
+
+
+      <!-- SERIES -->
+
+      <hr  v-show="showSeries" ref="series-top" />
+      <div class="row" v-show="showSeries">
+        <h1>Series</h1>
+      </div>
+      <div class="row" v-show="showSeries">
+        <b-table striped hover :items="series" :fields="seriesFields" :fixed="false">
+          <template slot="operations" slot-scope="data">
+            <b-button @click="OpenSeriesPreview(data.item)" title="Preview">
+              <i class="fa fa-eye"></i>
+            </b-button>
+            <b-button @click="OpenSeriesDetails(data.item)" title="Open tags">
+              <i class="fa fa-address-card"></i>
+            </b-button>
+            <b-button @click="RetrieveSeries(data.item)" title="Retrieve series using WADO-RS">
+              <i class="fa fa-cloud-download"></i>
+            </b-button>
+            <b-button @click="ConfirmDeleteSeries(data.item)"
+                      v-if="serversInfo[activeServer].HasDelete" title="Delete remote series">
+              <i class="fa fa-trash"></i>
+            </b-button>
+          </template>
+        </b-table>
+
+        <b-modal ref="series-details" size="xl" ok-only="true">
+          <template slot="modal-title">
+            Details of series
+          </template>
+          <div class="d-block text-center">
+            <b-table striped :items="seriesTags" :fields="seriesTagsFields" :fixed="true">
+            </b-table>
+          </div>
+        </b-modal>
+
+        <b-modal ref="series-preview" size="xl" ok-only="true">
+          <template slot="modal-title">
+            Preview of series
+          </template>
+          <div class="d-block text-center">
+            <b-alert variant="danger" v-model="previewFailure">
+              The remote DICOMweb server cannot generate a preview for this image.
+            </b-alert>
+            <b-img v-if="!previewFailure" :src="preview" fluid alt=""></b-img>
+          </div>
+        </b-modal>
+
+        <b-modal id="series-delete-confirm" size="xl" @ok="ExecuteDeleteSeries">
+          <template slot="modal-title">
+            Confirm deletion
+          </template>
+          <div class="d-block">
+            <p>
+              Are you sure you want to remove this series from the remote server?
+            </p>
+            <p>
+              Series description: {{ seriesToDelete && seriesToDelete['0008103E'] && seriesToDelete['0008103E'].Value }}
+            </p>
+          </div>
+        </b-modal>
+      </div>
+
+
+      <b-modal ref="retrieve-job" size="xl" ok-only="true">
+        <template slot="modal-title">
+          Retrieving {{ jobLevel }}
+        </template>
+        <div class="d-block">
+          <p>
+            Orthanc is now running a background job to retrieve the
+            {{ jobLevel }} from remote server "{{ activeServer }}" using
+            WADO-RS.
+          </p>
+          <p>
+            Job ID: <tt>{{ jobId }}</tt>
+          </p>
+          <p>
+            Job details:
+          </p>
+          <pre>{{ jobDetails }}</pre>
+          <p>
+            <b-button variant="success" @click="RefreshJobDetails()">Refresh job details</b-button>
+          </p>
+        </div>
+      </b-modal>
+
+
+      <p style="height:5em"></p>
+    </div>
+
+    <!-- Add Vue and Bootstrap-Vue JS just before the closing </body> tag -->
+    <script src="../libs/js/vue.min.js"></script>
+    <script src="../libs/js/bootstrap-vue.min.js"></script>
+    <script src="../libs/js/axios.min.js"></script>
+    <script type="text/javascript" src="app.js"></script>
+  </body>
+</html>