changeset 1538:d1806b4e4839

moving OrthancStone/Samples/ as Applications/Samples/
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Aug 2020 13:24:38 +0200
parents de8cf5859e84
children 7b7aaeee3773
files Applications/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Applications/Resources/Orthanc/Plugins/OrthancPluginException.h Applications/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Applications/Resources/Orthanc/Plugins/VersionScriptPlugins.map Applications/Resources/Orthanc/README.txt Applications/Resources/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h Applications/Resources/SyncOrthancFolder.py Applications/Samples/Common/RtViewerApp.cpp Applications/Samples/Common/RtViewerApp.h Applications/Samples/Common/RtViewerView.cpp Applications/Samples/Common/RtViewerView.h Applications/Samples/Common/SampleHelpers.h Applications/Samples/README.md Applications/Samples/RtViewerPlugin/CMakeLists.txt Applications/Samples/RtViewerPlugin/OrthancExplorer.js Applications/Samples/RtViewerPlugin/Plugin.cpp Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map Applications/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h Applications/Samples/Sdl/BoostExtendedConfiguration.cmake Applications/Samples/Sdl/CMakeLists.txt Applications/Samples/Sdl/RtViewer/CMakeLists.txt Applications/Samples/Sdl/RtViewer/CMakeSettings.json Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp Applications/Samples/Sdl/SdlHelpers.h Applications/Samples/Sdl/SingleFrameViewer/CMakeLists.txt Applications/Samples/Sdl/SingleFrameViewer/CMakeSettings.json Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Applications/Samples/Sdl/Utilities.cmake Applications/Samples/WebAssembly/CMakeLists.txt Applications/Samples/WebAssembly/NOTES.txt Applications/Samples/WebAssembly/RtViewer/CMakeLists.txt Applications/Samples/WebAssembly/RtViewer/OBSOLETE.cpp Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Applications/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js Applications/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js Applications/Samples/WebAssembly/RtViewer/index.html Applications/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Applications/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Applications/Samples/WebAssembly/SingleFrameViewer/index.html Applications/Samples/WebAssembly/docker-build.sh Applications/Samples/WebAssembly/docker-internal.sh Applications/Samples/build-wasm-samples.sh Applications/StoneWebViewer/COPYING Applications/StoneWebViewer/Plugin/CMakeLists.txt Applications/StoneWebViewer/Plugin/OrthancExplorer.js Applications/StoneWebViewer/Plugin/Plugin.cpp Applications/StoneWebViewer/Resources/GenerateImages.py Applications/StoneWebViewer/Resources/NOTES.txt Applications/StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Applications/StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map Applications/StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h Applications/StoneWebViewer/Resources/Styles/_button.scss Applications/StoneWebViewer/Resources/Styles/_exitButton.scss Applications/StoneWebViewer/Resources/Styles/_helpers.scss Applications/StoneWebViewer/Resources/Styles/_layout.scss Applications/StoneWebViewer/Resources/Styles/_notice.scss Applications/StoneWebViewer/Resources/Styles/_print.scss Applications/StoneWebViewer/Resources/Styles/_selectionActionlist.scss Applications/StoneWebViewer/Resources/Styles/_serieslist.scss Applications/StoneWebViewer/Resources/Styles/_studyInformationBreadcrumb.scss Applications/StoneWebViewer/Resources/Styles/_studyIsland.scss Applications/StoneWebViewer/Resources/Styles/_toolbar.scss Applications/StoneWebViewer/Resources/Styles/_variable.scss Applications/StoneWebViewer/Resources/Styles/_video.scss Applications/StoneWebViewer/Resources/Styles/styles.scss Applications/StoneWebViewer/Resources/Styles/tb-group.scss Applications/StoneWebViewer/Resources/Styles/webviewer.components.scss Applications/StoneWebViewer/Resources/Styles/webviewer.main.scss Applications/StoneWebViewer/Resources/Styles/wv-disclaimer.scss Applications/StoneWebViewer/Resources/Styles/wv-loadingbar.scss Applications/StoneWebViewer/Resources/Styles/wv-overlay.scss Applications/StoneWebViewer/Resources/Styles/wv-pdf-viewer.scss Applications/StoneWebViewer/Resources/Styles/wv-splitpane.scss Applications/StoneWebViewer/Resources/Styles/wv-timeline-controls.scss Applications/StoneWebViewer/Resources/Styles/wv-timeline.scss Applications/StoneWebViewer/WebApplication/app.css Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebApplication/img/grid1x1.png Applications/StoneWebViewer/WebApplication/img/grid1x2.png Applications/StoneWebViewer/WebApplication/img/grid2x1.png Applications/StoneWebViewer/WebApplication/img/grid2x2.png Applications/StoneWebViewer/WebApplication/img/loading.gif Applications/StoneWebViewer/WebApplication/index.html Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Applications/StoneWebViewer/WebAssembly/NOTES.txt Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Applications/StoneWebViewer/WebAssembly/docker-build.sh Applications/StoneWebViewer/WebAssembly/docker-internal.sh OrthancStone/Docs/Conventions.txt OrthancStone/Resources/Conventions.txt OrthancStone/Samples/Common/RtViewerApp.cpp OrthancStone/Samples/Common/RtViewerApp.h OrthancStone/Samples/Common/RtViewerView.cpp OrthancStone/Samples/Common/RtViewerView.h OrthancStone/Samples/Common/SampleHelpers.h OrthancStone/Samples/README.md OrthancStone/Samples/RtViewerPlugin/CMakeLists.txt OrthancStone/Samples/RtViewerPlugin/OrthancExplorer.js OrthancStone/Samples/RtViewerPlugin/Plugin.cpp OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map OrthancStone/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h OrthancStone/Samples/Sdl/BoostExtendedConfiguration.cmake OrthancStone/Samples/Sdl/CMakeLists.txt OrthancStone/Samples/Sdl/RtViewer/CMakeLists.txt OrthancStone/Samples/Sdl/RtViewer/CMakeSettings.json OrthancStone/Samples/Sdl/RtViewer/RtViewerSdl.cpp OrthancStone/Samples/Sdl/SdlHelpers.h OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeLists.txt OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeSettings.json OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h OrthancStone/Samples/Sdl/Utilities.cmake OrthancStone/Samples/WebAssembly/CMakeLists.txt OrthancStone/Samples/WebAssembly/NOTES.txt OrthancStone/Samples/WebAssembly/RtViewer/CMakeLists.txt OrthancStone/Samples/WebAssembly/RtViewer/OBSOLETE.cpp OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js OrthancStone/Samples/WebAssembly/RtViewer/index.html OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h OrthancStone/Samples/WebAssembly/SingleFrameViewer/index.html OrthancStone/Samples/WebAssembly/docker-build.sh OrthancStone/Samples/WebAssembly/docker-internal.sh OrthancStone/Samples/build-wasm-samples.sh StoneWebViewer/COPYING StoneWebViewer/Plugin/CMakeLists.txt StoneWebViewer/Plugin/OrthancExplorer.js StoneWebViewer/Plugin/Plugin.cpp StoneWebViewer/Resources/GenerateImages.py StoneWebViewer/Resources/NOTES.txt StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h StoneWebViewer/Resources/Styles/_button.scss StoneWebViewer/Resources/Styles/_exitButton.scss StoneWebViewer/Resources/Styles/_helpers.scss StoneWebViewer/Resources/Styles/_layout.scss StoneWebViewer/Resources/Styles/_notice.scss StoneWebViewer/Resources/Styles/_print.scss StoneWebViewer/Resources/Styles/_selectionActionlist.scss StoneWebViewer/Resources/Styles/_serieslist.scss StoneWebViewer/Resources/Styles/_studyInformationBreadcrumb.scss StoneWebViewer/Resources/Styles/_studyIsland.scss StoneWebViewer/Resources/Styles/_toolbar.scss StoneWebViewer/Resources/Styles/_variable.scss StoneWebViewer/Resources/Styles/_video.scss StoneWebViewer/Resources/Styles/styles.scss StoneWebViewer/Resources/Styles/tb-group.scss StoneWebViewer/Resources/Styles/webviewer.components.scss StoneWebViewer/Resources/Styles/webviewer.main.scss StoneWebViewer/Resources/Styles/wv-disclaimer.scss StoneWebViewer/Resources/Styles/wv-loadingbar.scss StoneWebViewer/Resources/Styles/wv-overlay.scss StoneWebViewer/Resources/Styles/wv-pdf-viewer.scss StoneWebViewer/Resources/Styles/wv-splitpane.scss StoneWebViewer/Resources/Styles/wv-timeline-controls.scss StoneWebViewer/Resources/Styles/wv-timeline.scss StoneWebViewer/WebApplication/app.css StoneWebViewer/WebApplication/app.js StoneWebViewer/WebApplication/img/grid1x1.png StoneWebViewer/WebApplication/img/grid1x2.png StoneWebViewer/WebApplication/img/grid2x1.png StoneWebViewer/WebApplication/img/grid2x2.png StoneWebViewer/WebApplication/img/loading.gif StoneWebViewer/WebApplication/index.html StoneWebViewer/WebAssembly/CMakeLists.txt StoneWebViewer/WebAssembly/NOTES.txt StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py StoneWebViewer/WebAssembly/StoneWebViewer.cpp StoneWebViewer/WebAssembly/docker-build.sh StoneWebViewer/WebAssembly/docker-internal.sh
diffstat 201 files changed, 48528 insertions(+), 38972 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,3383 @@
+/**
+ * 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.
+ * 
+ * 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 <boost/algorithm/string/predicate.hpp>
+#include <boost/move/unique_ptr.hpp>
+#include <boost/thread.hpp>
+#include <json/reader.h>
+#include <json/writer.h>
+
+
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
+#endif
+
+
+namespace OrthancPlugins
+{
+  static OrthancPluginContext* globalContext_ = NULL;
+
+
+  void SetGlobalContext(OrthancPluginContext* context)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+    else if (globalContext_ == NULL)
+    {
+      globalContext_ = context;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+  }
+
+
+  bool HasGlobalContext()
+  {
+    return globalContext_ != NULL;
+  }
+
+
+  OrthancPluginContext* GetGlobalContext()
+  {
+    if (globalContext_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      return globalContext_;
+    }
+  }
+
+
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (code == OrthancPluginErrorCode_UnknownResource ||
+             code == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  MemoryBuffer::MemoryBuffer()
+  {
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  MemoryBuffer::MemoryBuffer(const void* buffer,
+                             size_t size)
+  {
+    uint32_t s = static_cast<uint32_t>(size);
+    if (static_cast<size_t>(s) != size)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
+             OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else
+    {
+      memcpy(buffer_.data, buffer, size);
+    }
+  }
+#endif
+
+
+  void MemoryBuffer::Clear()
+  {
+    if (buffer_.data != NULL)
+    {
+      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &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::Swap(MemoryBuffer& other)
+  {
+    std::swap(buffer_.data, other.buffer_.data);
+    std::swap(buffer_.size, other.buffer_.size);
+  }
+
+
+  OrthancPluginMemoryBuffer MemoryBuffer::Release()
+  {
+    OrthancPluginMemoryBuffer result = buffer_;
+
+    buffer_.data = NULL;
+    buffer_.size = 0;
+
+    return result;
+  }
+
+
+  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)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
+
+    Json::Reader reader;
+    if (!reader.parse(tmp, tmp + buffer_.size, target))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+  }
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                const std::map<std::string, std::string>& httpHeaders,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    std::vector<const char*> headersKeys;
+    std::vector<const char*> headersValues;
+    
+    for (std::map<std::string, std::string>::const_iterator
+           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
+    {
+      headersKeys.push_back(it->first.c_str());
+      headersValues.push_back(it->second.c_str());
+    }
+
+    return CheckHttp(OrthancPluginRestApiGet2(
+                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
+                       (headersKeys.empty() ? NULL : &headersKeys[0]),
+                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
+  }
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const void* body,
+                                 size_t bodySize,
+                                 bool applyPlugins)
+  {
+    Clear();
+    
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const void* body,
+                                size_t bodySize,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const Json::Value& body,
+                                bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(uri, writer.write(body), applyPlugins);
+  }
+
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
+  }
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 const OrthancImage& pixelData,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
+  }
+
+
+  void MemoryBuffer::ReadFile(const std::string& path)
+  {
+    Clear();
+    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+  {
+    Clear();
+    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
+  }
+
+
+  void OrthancString::Assign(char* str)
+  {
+    Clear();
+
+    if (str != NULL)
+    {
+      str_ = str;
+    }
+  }
+
+
+  void OrthancString::Clear()
+  {
+    if (str_ != NULL)
+    {
+      OrthancPluginFreeString(GetGlobalContext(), 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)
+    {
+      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Reader reader;
+    if (!reader.parse(str_, target))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void MemoryBuffer::DicomToJson(Json::Value& target,
+                                 OrthancPluginDicomToJsonFormat format,
+                                 OrthancPluginDicomToJsonFlags flags,
+                                 uint32_t maxStringLength)
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginDicomBufferToJson
+               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
+    str.ToJson(target);
+  }
+
+
+  bool MemoryBuffer::HttpGet(const std::string& url,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPost(const std::string& url,
+                              const std::string& body,
+                              const std::string& username,
+                              const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
+                                           body.c_str(), body.size(),
+                                           username.empty() ? NULL : username.c_str(),
+                                           password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPut(const std::string& url,
+                             const std::string& body,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
+                                          body.empty() ? NULL : body.c_str(),
+                                          body.size(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
+  {
+    Clear();
+    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
+  }
+
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password)
+  {
+    OrthancPluginErrorCode error = OrthancPluginHttpDelete
+      (GetGlobalContext(), url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void LogError(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogError(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogWarning(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogInfo(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void OrthancConfiguration::LoadConfiguration()
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
+
+    if (str.GetContent() == NULL)
+    {
+      LogError("Cannot access the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    str.ToJson(configuration_);
+
+    if (configuration_.type() != Json::objectValue)
+    {
+      LogError("Unable to read the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+    
+
+  OrthancConfiguration::OrthancConfiguration()
+  {
+    LoadConfiguration();
+  }
+
+
+  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
+  {
+    if (loadConfiguration)
+    {
+      LoadConfiguration();
+    }
+    else
+    {
+      configuration_ = Json::objectValue;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetPath(const std::string& key) const
+  {
+    if (path_.empty())
+    {
+      return key;
+    }
+    else
+    {
+      return path_ + "." + key;
+    }
+  }
+
+
+  bool OrthancConfiguration::IsSection(const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    return (configuration_.isMember(key) &&
+            configuration_[key].type() == Json::objectValue);
+  }
+
+
+  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
+                                        const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.path_ = GetPath(key);
+
+    if (!configuration_.isMember(key))
+    {
+      target.configuration_ = Json::objectValue;
+    }
+    else
+    {
+      if (configuration_[key].type() != Json::objectValue)
+      {
+        LogError("The configuration section \"" + target.path_ +
+                 "\" is not an associative array as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(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)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                        const std::string& key) const
+  {
+    int tmp;
+    if (!LookupIntegerValue(tmp, key))
+    {
+      return false;
+    }
+
+    if (tmp < 0)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a positive integer as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a Boolean as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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 = static_cast<float>(configuration_[key].asInt());
+        return true;
+
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
+
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
+                                                 const std::string& key,
+                                                 bool allowSingleString) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::arrayValue:
+      {
+        bool ok = true;
+
+        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+        {
+          if (configuration_[key][i].type() == Json::stringValue)
+          {
+            target.push_back(configuration_[key][i].asString());
+          }
+          else
+          {
+            ok = false;
+          }
+        }
+
+        if (ok)
+        {
+          return true;
+        }
+
+        break;
+      }
+
+      case Json::stringValue:
+        if (allowSingleString)
+        {
+          target.push_back(configuration_[key].asString());
+          return true;
+        }
+
+        break;
+
+      default:
+        break;
+    }
+
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a list of strings as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+
+  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
+                                                const std::string& key,
+                                                bool allowSingleString) const
+  {
+    std::list<std::string> lst;
+
+    if (LookupListOfStrings(lst, key, allowSingleString))
+    {
+      target.clear();
+
+      for (std::list<std::string>::const_iterator
+             it = lst.begin(); it != lst.end(); ++it)
+      {
+        target.insert(*it);
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  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 OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
+                                           const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return;
+    }
+
+    if (configuration_[key].type() != Json::objectValue)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    Json::Value::Members members = configuration_[key].getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& value = configuration_[key][members[i]];
+
+      if (value.type() == Json::stringValue)
+      {
+        target[members[i]] = value.asString();
+      }
+      else
+      {
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not a dictionary mapping strings to strings");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+      }
+    }
+  }
+
+
+  void OrthancImage::Clear()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(GetGlobalContext(), image_);
+      image_ = NULL;
+    }
+  }
+
+
+  void OrthancImage::CheckImageAvailable() const
+  {
+    if (image_ == NULL)
+    {
+      LogError("Trying to access a NULL image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage() :
+    image_(NULL)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
+    image_(image)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height)
+  {
+    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height,
+                             uint32_t                  pitch,
+                             void*                     buffer)
+  {
+    image_ = OrthancPluginCreateImageAccessor
+      (GetGlobalContext(), format, width, height, pitch, buffer);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image accessor");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+  void OrthancImage::UncompressPngImage(const void* data,
+                                        size_t size)
+  {
+    Clear();
+
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a PNG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::UncompressJpegImage(const void* data,
+                                         size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a JPEG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::DecodeDicomImage(const void* data,
+                                      size_t size,
+                                      unsigned int frame)
+  {
+    Clear();
+    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a DICOM image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetWidth() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetHeight() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetPitch() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
+  }
+
+
+  void* OrthancImage::GetBuffer() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
+  }
+
+
+  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                       uint8_t quality) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
+                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+  }
+
+
+  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                     uint8_t quality) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
+                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+  }
+
+
+  OrthancPluginImage* OrthancImage::Release()
+  {
+    CheckImageAvailable();
+    OrthancPluginImage* tmp = image_;
+    image_ = NULL;
+    return tmp;
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
+    matcher_(NULL),
+    worklist_(worklist)
+  {
+    if (worklist_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void FindMatcher::SetupDicom(const void*  query,
+                               uint32_t     size)
+  {
+    worklist_ = NULL;
+
+    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
+    if (matcher_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  FindMatcher::~FindMatcher()
+  {
+    // The "worklist_" field
+
+    if (matcher_ != NULL)
+    {
+      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
+    }
+  }
+
+
+
+  bool FindMatcher::IsMatch(const void*  dicom,
+                            uint32_t     size) const
+  {
+    int32_t result;
+
+    if (matcher_ != NULL)
+    {
+      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
+    }
+    else if (worklist_ != NULL)
+    {
+      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (result == 0)
+    {
+      return false;
+    }
+    else if (result == 1)
+    {
+      return true;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output
+    )
+  {
+    Json::StyledWriter writer;
+    std::string bodyString = writer.write(value);
+
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
+  }
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output
+    )
+  {
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
+  }
+
+  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
+  {
+    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
+  }
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
+  {
+    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        result.assign(answer.GetData(), answer.GetSize());
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(result, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(result, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins)
+  {
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision)
+  {
+    LogError("Your version of the Orthanc core (" +
+             std::string(GetGlobalContext()->orthancVersion) +
+             ") is too old to run this plugin (version " +
+             boost::lexical_cast<std::string>(major) + "." +
+             boost::lexical_cast<std::string>(minor) + "." +
+             boost::lexical_cast<std::string>(revision) +
+             " is required)");
+  }
+
+
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (!HasGlobalContext())
+    {
+      LogError("Bad Orthanc context in the plugin");
+      return false;
+    }
+
+    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
+    {
+      // Assume compatibility with the mainline
+      return true;
+    }
+
+    // Parse the version of the Orthanc core
+    int aa, bb, cc;
+    if (
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
+    {
+      return false;
+    }
+
+    unsigned int a = static_cast<unsigned int>(aa);
+    unsigned int b = static_cast<unsigned int>(bb);
+    unsigned int c = static_cast<unsigned int>(cc);
+
+    // Check the major version number
+
+    if (a > major)
+    {
+      return true;
+    }
+
+    if (a < major)
+    {
+      return false;
+    }
+
+
+    // Check the minor version number
+    assert(a == major);
+
+    if (b > minor)
+    {
+      return true;
+    }
+
+    if (b < minor)
+    {
+      return false;
+    }
+
+    // Check the patch level version number
+    assert(a == major && b == minor);
+
+    if (c >= revision)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path)
+  {
+    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
+
+    if (mime == NULL)
+    {
+      // Should never happen, just for safety
+      return "application/octet-stream";
+    }
+    else
+    {
+      return mime;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
+  {
+    size_t index;
+    if (LookupName(index, name))
+    {
+      return index;
+    }
+    else
+    {
+      LogError("Inexistent peer: " + name);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
+    }
+  }
+
+
+  OrthancPeers::OrthancPeers() :
+    peers_(NULL),
+    timeout_(0)
+  {
+    peers_ = OrthancPluginGetPeers(GetGlobalContext());
+
+    if (peers_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+
+    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
+
+    for (uint32_t i = 0; i < count; i++)
+    {
+      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
+      if (name == NULL)
+      {
+        OrthancPluginFreePeers(GetGlobalContext(), peers_);
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+
+      index_[name] = i;
+    }
+  }
+
+
+  OrthancPeers::~OrthancPeers()
+  {
+    if (peers_ != NULL)
+    {
+      OrthancPluginFreePeers(GetGlobalContext(), peers_);
+    }
+  }
+
+
+  bool OrthancPeers::LookupName(size_t& target,
+                                const std::string& name) const
+  {
+    Index::const_iterator found = index_.find(name);
+
+    if (found == index_.end())
+    {
+      return false;
+    }
+    else
+    {
+      target = found->second;
+      return true;
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerName(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
+  {
+    return GetPeerUrl(GetPeerIndex(name));
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        size_t index,
+                                        const std::string& key) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
+      if (s == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        value.assign(s);
+        return true;
+      }
+    }
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        const std::string& peer,
+                                        const std::string& key) const
+  {
+    return LookupUserProperty(value, GetPeerIndex(peer), key);
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoGet(target, index, uri));
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, index, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, name, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPost(target, index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, index, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, name, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(size_t index,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(const std::string& name,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPut(index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoDelete(size_t index,
+                              const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoDelete(const std::string& name,
+                              const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoDelete(index, uri));
+  }
+#endif
+
+
+
+
+
+  /******************************************************************
+   ** JOBS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  void OrthancJob::CallbackFinalize(void* job)
+  {
+    if (job != NULL)
+    {
+      delete reinterpret_cast<OrthancJob*>(job);
+    }
+  }
+
+
+  float OrthancJob::CallbackGetProgress(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->progress_;
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetContent(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetSerialized(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
+
+      if (tmp.hasSerialized_)
+      {
+        return tmp.serialized_.c_str();
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->Step();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+    catch (...)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
+                                                  OrthancPluginJobStopReason reason)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Reset();
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  void OrthancJob::ClearContent()
+  {
+    Json::Value empty = Json::objectValue;
+    UpdateContent(empty);
+  }
+
+
+  void OrthancJob::UpdateContent(const Json::Value& content)
+  {
+    if (content.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      Json::FastWriter writer;
+      content_ = writer.write(content);
+    }
+  }
+
+
+  void OrthancJob::ClearSerialized()
+  {
+    hasSerialized_ = false;
+    serialized_.clear();
+  }
+
+
+  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
+  {
+    if (serialized.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      Json::FastWriter writer;
+      serialized_ = writer.write(serialized);
+      hasSerialized_ = true;
+    }
+  }
+
+
+  void OrthancJob::UpdateProgress(float progress)
+  {
+    if (progress < 0 ||
+        progress > 1)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    progress_ = progress;
+  }
+
+
+  OrthancJob::OrthancJob(const std::string& jobType) :
+    jobType_(jobType),
+    progress_(0)
+  {
+    ClearContent();
+    ClearSerialized();
+  }
+
+
+  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
+      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+      CallbackStep, CallbackStop, CallbackReset);
+
+    if (orthanc == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      return orthanc;
+    }
+  }
+
+
+  std::string OrthancJob::Submit(OrthancJob* job,
+                                 int priority)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = Create(job);
+
+    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
+
+    if (id == NULL)
+    {
+      LogError("Plugin cannot submit job");
+      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      std::string tmp(id);
+      tmp.assign(id);
+      OrthancPluginFreeString(GetGlobalContext(), id);
+
+      return tmp;
+    }
+  }
+
+
+  void OrthancJob::SubmitAndWait(Json::Value& result,
+                                 OrthancJob* job /* takes ownership */,
+                                 int priority)
+  {
+    std::string id = Submit(job, priority);
+
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+      Json::Value status;
+      if (!RestApiGet(status, "/jobs/" + id, false) ||
+          !status.isMember("State") ||
+          status["State"].type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
+      }
+
+      const std::string state = status["State"].asString();
+      if (state == "Success")
+      {
+        if (status.isMember("Content"))
+        {
+          result = status["Content"];
+        }
+        else
+        {
+          result = Json::objectValue;
+        }
+
+        return;
+      }
+      else if (state == "Running")
+      {
+        continue;
+      }
+      else if (!status.isMember("ErrorCode") ||
+               status["ErrorCode"].type() != Json::intValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
+      }
+      else
+      {
+        if (!status.isMember("ErrorDescription") ||
+            status["ErrorDescription"].type() != Json::stringValue)
+        {
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
+        }
+        else
+        {
+#if HAS_ORTHANC_EXCEPTION == 1
+          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
+                                          status["ErrorDescription"].asString());
+#else
+          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
+#endif
+        }
+      }
+    }
+  }
+
+
+  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                         const Json::Value& body,
+                                         OrthancJob* job)
+  {
+    static const char* KEY_SYNCHRONOUS = "Synchronous";
+    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
+    static const char* KEY_PRIORITY = "Priority";
+
+    boost::movelib::unique_ptr<OrthancJob> protection(job);
+  
+    if (body.type() != Json::objectValue)
+    {
+#if HAS_ORTHANC_EXCEPTION == 1
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Expected a JSON object in the body");
+#else
+      LogError("Expected a JSON object in the body");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+    }
+
+    bool synchronous = true;
+  
+    if (body.isMember(KEY_SYNCHRONOUS))
+    {
+      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = body[KEY_SYNCHRONOUS].asBool();
+      }
+    }
+
+    if (body.isMember(KEY_ASYNCHRONOUS))
+    {
+      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
+      }
+    }
+
+    int priority = 0;
+
+    if (body.isMember(KEY_PRIORITY))
+    {
+      if (body[KEY_PRIORITY].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_PRIORITY) +
+                                        "\" must be an integer");
+#else
+        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        priority = !body[KEY_PRIORITY].asInt();
+      }
+    }
+  
+    Json::Value result;
+
+    if (synchronous)
+    {
+      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
+    }
+    else
+    {
+      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
+
+      result = Json::objectValue;
+      result["ID"] = id;
+      result["Path"] = "/jobs/" + id;
+    }
+
+    std::string s = result.toStyledString();
+    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
+                              s.size(), "application/json");
+  }
+
+#endif
+
+
+
+
+  /******************************************************************
+   ** METRICS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  MetricsTimer::MetricsTimer(const char* name) :
+    name_(name)
+  {
+    start_ = boost::posix_time::microsec_clock::universal_time();
+  }
+  
+  MetricsTimer::~MetricsTimer()
+  {
+    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
+    const boost::posix_time::time_duration diff = stop - start_;
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
+                                 OrthancPluginMetricsType_Timer);
+  }
+#endif
+
+
+
+
+  /******************************************************************
+   ** HTTP CLIENT
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient::RequestBodyWrapper : public boost::noncopyable
+  {
+  private:
+    static RequestBodyWrapper& GetObject(void* body)
+    {
+      assert(body != NULL);
+      return *reinterpret_cast<RequestBodyWrapper*>(body);
+    }
+
+    IRequestBody&  body_;
+    bool           done_;
+    std::string    chunk_;
+
+  public:
+    RequestBodyWrapper(IRequestBody& body) :
+      body_(body),
+      done_(false)
+    {
+    }      
+
+    static uint8_t IsDone(void* body)
+    {
+      return GetObject(body).done_;
+    }
+    
+    static const void* GetChunkData(void* body)
+    {
+      return GetObject(body).chunk_.c_str();
+    }
+    
+    static uint32_t GetChunkSize(void* body)
+    {
+      return static_cast<uint32_t>(GetObject(body).chunk_.size());
+    }
+
+    static OrthancPluginErrorCode Next(void* body)
+    {
+      RequestBodyWrapper& that = GetObject(body);
+        
+      if (that.done_)
+      {
+        return OrthancPluginErrorCode_BadSequenceOfCalls;
+      }
+      else
+      {
+        try
+        {
+          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
+          return OrthancPluginErrorCode_Success;
+        }
+        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+        {
+          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+        }
+        catch (...)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+      }
+    }    
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
+                                                        const char* key,
+                                                        const char* value)
+  {
+    assert(answer != NULL && key != NULL && value != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
+                                                       const void* data,
+                                                       uint32_t size)
+  {
+    assert(answer != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+  HttpClient::HttpClient() :
+    httpStatus_(0),
+    method_(OrthancPluginHttpMethod_Get),
+    timeout_(0),
+    pkcs11_(false),
+    chunkedBody_(NULL),
+    allowChunkedTransfers_(true)
+  {
+  }
+
+
+  void HttpClient::AddHeaders(const HttpHeaders& headers)
+  {
+    for (HttpHeaders::const_iterator it = headers.begin();
+         it != headers.end(); ++it)
+    {
+      headers_[it->first] = it->second;
+    }
+  }
+
+  
+  void HttpClient::SetCredentials(const std::string& username,
+                                  const std::string& password)
+  {
+    username_ = username;
+    password_ = password;
+  }
+
+  
+  void HttpClient::ClearCredentials()
+  {
+    username_.empty();
+    password_.empty();
+  }
+
+
+  void HttpClient::SetCertificate(const std::string& certificateFile,
+                                  const std::string& keyFile,
+                                  const std::string& keyPassword)
+  {
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = keyFile;
+    certificateKeyPassword_ = keyPassword;
+  }
+
+  
+  void HttpClient::ClearCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+  void HttpClient::ClearBody()
+  {
+    fullBody_.clear();
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SwapBody(std::string& body)
+  {
+    fullBody_.swap(body);
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(const std::string& body)
+  {
+    fullBody_ = body;
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(IRequestBody& body)
+  {
+    fullBody_.clear();
+    chunkedBody_ = &body;
+  }
+
+
+  namespace
+  {
+    class HeadersWrapper : public boost::noncopyable
+    {
+    private:
+      std::vector<const char*>  headersKeys_;
+      std::vector<const char*>  headersValues_;
+
+    public:
+      HeadersWrapper(const HttpClient::HttpHeaders& headers)
+      {
+        headersKeys_.reserve(headers.size());
+        headersValues_.reserve(headers.size());
+
+        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+        {
+          headersKeys_.push_back(it->first.c_str());
+          headersValues_.push_back(it->second.c_str());
+        }
+      }
+
+      void AddStaticString(const char* key,
+                           const char* value)
+      {
+        headersKeys_.push_back(key);
+        headersValues_.push_back(value);
+      }
+
+      uint32_t GetCount() const
+      {
+        return headersKeys_.size();
+      }
+
+      const char* const* GetKeys() const
+      {
+        return headersKeys_.empty() ? NULL : &headersKeys_[0];
+      }
+
+      const char* const* GetValues() const
+      {
+        return headersValues_.empty() ? NULL : &headersValues_[0];
+      }
+    };
+
+
+    class MemoryRequestBody : public HttpClient::IRequestBody
+    {
+    private:
+      std::string  body_;
+      bool         done_;
+
+    public:
+      MemoryRequestBody(const std::string& body) :
+        body_(body),
+        done_(false)
+      {
+        if (body_.empty())
+        {
+          done_ = true;
+        }
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk)
+      {
+        if (done_)
+        {
+          return false;
+        }
+        else
+        {
+          chunk.swap(body_);
+          done_ = true;
+          return true;
+        }
+      }
+    };
+
+
+    // This class mimics Orthanc::ChunkedBuffer
+    class ChunkedBuffer : public boost::noncopyable
+    {
+    private:
+      typedef std::list<std::string*>  Content;
+
+      Content  content_;
+      size_t   size_;
+
+    public:
+      ChunkedBuffer() :
+        size_(0)
+      {
+      }
+
+      ~ChunkedBuffer()
+      {
+        Clear();
+      }
+
+      void Clear()
+      {
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          delete *it;
+        }
+
+        content_.clear();
+      }
+
+      void Flatten(std::string& target) const
+      {
+        target.resize(size_);
+
+        size_t pos = 0;
+
+        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          size_t s = (*it)->size();
+
+          if (s != 0)
+          {
+            memcpy(&target[pos], (*it)->c_str(), s);
+            pos += s;
+          }
+        }
+
+        assert(size_ == 0 ||
+               pos == target.size());
+      }
+
+      void AddChunk(const void* data,
+                    size_t size)
+      {
+        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
+        size_ += size;
+      }
+
+      void AddChunk(const std::string& chunk)
+      {
+        content_.push_back(new std::string(chunk));
+        size_ += chunk.size();
+      }
+    };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    class MemoryAnswer : public HttpClient::IAnswer
+    {
+    private:
+      HttpClient::HttpHeaders  headers_;
+      ChunkedBuffer            body_;
+
+    public:
+      const HttpClient::HttpHeaders& GetHeaders() const
+      {
+        return headers_;
+      }
+
+      const ChunkedBuffer& GetBody() const
+      {
+        return body_;
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value)
+      {
+        headers_[key] = value;
+      }
+
+      virtual void AddChunk(const void* data,
+                            size_t size)
+      {
+        body_.AddChunk(data, size);
+      }
+    };
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
+                                     IAnswer& answer,
+                                     IRequestBody& body) const
+  {
+    HeadersWrapper h(headers_);
+
+    if (method_ == OrthancPluginHttpMethod_Post ||
+        method_ == OrthancPluginHttpMethod_Put)
+    {
+      // Automatically set the "Transfer-Encoding" header if absent
+      bool found = false;
+
+      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        if (boost::iequals(it->first, "Transfer-Encoding"))
+        {
+          found = true;
+          break;
+        }
+      }
+
+      if (!found)
+      {
+        h.AddStaticString("Transfer-Encoding", "chunked");
+      }
+    }
+
+    RequestBodyWrapper request(body);
+        
+    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
+      GetGlobalContext(),
+      &answer,
+      AnswerAddChunkCallback,
+      AnswerAddHeaderCallback,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      h.GetCount(),
+      h.GetKeys(),
+      h.GetValues(),
+      &request,
+      RequestBodyWrapper::IsDone,
+      RequestBodyWrapper::GetChunkData,
+      RequestBodyWrapper::GetChunkSize,
+      RequestBodyWrapper::Next,
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+#endif    
+
+
+  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
+                                        HttpHeaders& answerHeaders,
+                                        std::string& answerBody,
+                                        const std::string& body) const
+  {
+    HeadersWrapper headers(headers_);
+
+    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
+
+    OrthancPluginErrorCode error = OrthancPluginHttpClient(
+      GetGlobalContext(),
+      *answerBodyBuffer,
+      *answerHeadersBuffer,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      headers.GetCount(),
+      headers.GetKeys(),
+      headers.GetValues(),
+      body.empty() ? NULL : body.c_str(),
+      body.size(),
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+
+    Json::Value v;
+    answerHeadersBuffer.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    answerHeaders.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        answerHeaders[members[i]] = h.asString();
+      }
+    }
+
+    answerBodyBuffer.ToString(answerBody);
+  }
+
+
+  void HttpClient::Execute(IAnswer& answer)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      if (chunkedBody_ != NULL)
+      {
+        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
+      }
+      else
+      {
+        MemoryRequestBody wrapper(fullBody_);
+        ExecuteWithStream(httpStatus_, answer, wrapper);
+      }
+
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the answer body are sent at once)
+
+    HttpHeaders answerHeaders;
+    std::string answerBody;
+    Execute(answerHeaders, answerBody);
+
+    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+         it != answerHeaders.end(); ++it)
+    {
+      answer.AddHeader(it->first, it->second);      
+    }
+
+    if (!answerBody.empty())
+    {
+      answer.AddChunk(answerBody.c_str(), answerBody.size());
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           std::string& answerBody /* out */)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      MemoryAnswer answer;
+      Execute(answer);
+      answerHeaders = answer.GetHeaders();
+      answer.GetBody().Flatten(answerBody);
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the request body are sent at once)
+
+    if (chunkedBody_ != NULL)
+    {
+      ChunkedBuffer buffer;
+      
+      std::string chunk;
+      while (chunkedBody_->ReadNextChunk(chunk))
+      {
+        buffer.AddChunk(chunk);
+      }
+
+      std::string body;
+      buffer.Flatten(body);
+
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
+    }
+    else
+    {
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           Json::Value& answerBody /* out */)
+  {
+    std::string body;
+    Execute(answerHeaders, body);
+    
+    Json::Reader reader;
+    if (!reader.parse(body, answerBody))
+    {
+      LogError("Cannot convert HTTP answer body to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void HttpClient::Execute()
+  {
+    HttpHeaders answerHeaders;
+    std::string body;
+    Execute(answerHeaders, body);
+  }
+
+#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
+
+
+
+
+
+  /******************************************************************
+   ** CHUNKED HTTP SERVER
+   ******************************************************************/
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request)
+    {
+    }
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request)
+    {
+      return NULL;
+    }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader)
+    {
+      if (reader != NULL)
+      {
+        delete reinterpret_cast<IChunkedRequestReader*>(reader);
+      }
+    }
+
+#else
+    
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback         GetHandler,
+                                                    ChunkedRestCallback  PostHandler,
+                                                    RestCallback         DeleteHandler,
+                                                    ChunkedRestCallback  PutHandler)
+    {
+      try
+      {
+        std::string allowed;
+
+        if (GetHandler != Internals::NullRestCallback)
+        {
+          allowed += "GET";
+        }
+
+        if (PostHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "POST";
+        }
+
+        if (DeleteHandler != Internals::NullRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "DELETE";
+        }
+
+        if (PutHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "PUT";
+        }
+      
+        switch (request->method)
+        {
+          case OrthancPluginHttpMethod_Get:
+            if (GetHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              GetHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Post:
+            if (PostHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Delete:
+            if (DeleteHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              DeleteHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Put:
+            if (PutHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          default:
+            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* rawHandler,
+    const char* sopClassUid,
+    const char* sopInstanceUid)
+  {
+    assert(target != NULL &&
+           rawHandler != NULL);
+      
+    try
+    {
+      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+      *target = handler.Lookup(sopClassUid, sopInstanceUid);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
+  {
+    assert(rawHandler != NULL);
+    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#else
+  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance::DicomInstance(const void* buffer,
+                               size_t size) :
+    toFree_(true),
+    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
+  {
+    if (instance_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+  }
+#endif
+
+
+  DicomInstance::~DicomInstance()
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    if (toFree_ &&
+        instance_ != NULL)
+    {
+      OrthancPluginFreeDicomInstance(
+        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
+    }
+#endif
+  }
+
+  
+  std::string DicomInstance::GetRemoteAet() const
+  {
+    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return std::string(s);
+    }
+  }
+
+
+  void DicomInstance::GetJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+  
+
+  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  std::string DicomInstance::GetTransferSyntaxUid() const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
+
+    std::string result;
+    s.ToString(result);
+    return result;
+  }
+#endif
+
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  bool DicomInstance::HasPixelData() const
+  {
+    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
+    if (result < 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  void DicomInstance::GetRawFrame(std::string& target,
+                                  unsigned int frameIndex) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
+      GetGlobalContext(), *buffer, instance_, frameIndex);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
+  {
+    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
+      GetGlobalContext(), instance_, frameIndex);
+
+    if (image == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return new OrthancImage(image);
+    }
+  }
+#endif  
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void DicomInstance::Serialize(std::string& target) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
+      GetGlobalContext(), *buffer, instance_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+  
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance* DicomInstance::Transcode(const void* buffer,
+                                          size_t size,
+                                          const std::string& transferSyntax)
+  {
+    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
+      GetGlobalContext(), buffer, size, transferSyntax.c_str());
+
+    if (instance == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
+      result->toFree_ = true;
+      return result.release();
+    }
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,1228 @@
+/**
+ * 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.
+ * 
+ * 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 "OrthancPluginException.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <boost/noncopyable.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <json/value.h>
+#include <vector>
+#include <list>
+#include <set>
+#include <map>
+
+
+
+/**
+ * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
+ * backward compatibility with Orthanc SDK <= 1.3.0.
+ * 
+ *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
+ *
+ **/
+#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
+
+
+#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
+#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
+  (ORTHANC_VERSION_MAJOR > major ||                                     \
+   (ORTHANC_VERSION_MAJOR == major &&                                   \
+    (ORTHANC_VERSION_MINOR > minor ||                                   \
+     (ORTHANC_VERSION_MINOR == minor &&                                 \
+      ORTHANC_VERSION_REVISION >= revision))))
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
+#  define HAS_ORTHANC_PLUGIN_PEERS  1
+#  define HAS_ORTHANC_PLUGIN_JOB    1
+#else
+#  define HAS_ORTHANC_PLUGIN_PEERS  0
+#  define HAS_ORTHANC_PLUGIN_JOB    0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
+#  define HAS_ORTHANC_PLUGIN_METRICS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_METRICS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
+#else
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
+#endif
+
+
+
+namespace OrthancPlugins
+{
+  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
+                                const char* url,
+                                const OrthancPluginHttpRequest* request);
+
+  void SetGlobalContext(OrthancPluginContext* context);
+
+  bool HasGlobalContext();
+
+  OrthancPluginContext* GetGlobalContext();
+
+  
+  class OrthancImage;
+
+
+  class MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Check(OrthancPluginErrorCode code);
+
+    bool CheckHttp(OrthancPluginErrorCode code);
+
+  public:
+    MemoryBuffer();
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    // This constructor makes a copy of the given buffer in the memory
+    // handled by the Orthanc core
+    MemoryBuffer(const void* buffer,
+                 size_t size);
+#endif
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    OrthancPluginMemoryBuffer* operator*()
+    {
+      return &buffer_;
+    }
+
+    // This transfers ownership from "other" to "this"
+    void Assign(OrthancPluginMemoryBuffer& other);
+
+    void Swap(MemoryBuffer& other);
+
+    OrthancPluginMemoryBuffer Release();
+
+    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;
+    }
+
+    bool IsEmpty() const
+    {
+      return GetSize() == 0 || GetData() == NULL;
+    }
+
+    void Clear();
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+
+    bool RestApiGet(const std::string& uri,
+                    bool applyPlugins);
+
+    bool RestApiGet(const std::string& uri,
+                    const std::map<std::string, std::string>& httpHeaders,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const void* body,
+                     size_t bodySize,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const void* body,
+                    size_t bodySize,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const Json::Value& body,
+                    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);
+    }
+
+    void CreateDicom(const Json::Value& tags,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void CreateDicom(const Json::Value& tags,
+                     const OrthancImage& pixelData,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void ReadFile(const std::string& path);
+
+    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
+
+    void DicomToJson(Json::Value& target,
+                     OrthancPluginDicomToJsonFormat format,
+                     OrthancPluginDicomToJsonFlags flags,
+                     uint32_t maxStringLength);
+
+    bool HttpGet(const std::string& url,
+                 const std::string& username,
+                 const std::string& password);
+
+    bool HttpPost(const std::string& url,
+                  const std::string& body,
+                  const std::string& username,
+                  const std::string& password);
+
+    bool HttpPut(const std::string& url,
+                 const std::string& body,
+                 const std::string& username,
+                 const std::string& password);
+
+    void GetDicomInstance(const std::string& instanceId);
+  };
+
+
+  class OrthancString : public boost::noncopyable
+  {
+  private:
+    char*   str_;
+
+    void Clear();
+
+  public:
+    OrthancString() :
+      str_(NULL)
+    {
+    }
+
+    ~OrthancString()
+    {
+      Clear();
+    }
+
+    // This transfers ownership, warning: The string must have been
+    // allocated by the Orthanc core
+    void Assign(char* str);
+
+    const char* GetContent() const
+    {
+      return str_;
+    }
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+  };
+
+
+  class OrthancConfiguration : public boost::noncopyable
+  {
+  private:
+    Json::Value  configuration_;  // Necessarily a Json::objectValue
+    std::string  path_;
+
+    std::string GetPath(const std::string& key) const;
+
+    void LoadConfiguration();
+    
+  public:
+    OrthancConfiguration();
+
+    explicit OrthancConfiguration(bool load);
+
+    const Json::Value& GetJson() const
+    {
+      return configuration_;
+    }
+
+    bool IsSection(const std::string& key) const;
+
+    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;
+
+    bool LookupListOfStrings(std::list<std::string>& target,
+                             const std::string& key,
+                             bool allowSingleString) const;
+
+    bool LookupSetOfStrings(std::set<std::string>& target,
+                            const std::string& key,
+                            bool allowSingleString) 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;
+
+    void GetDictionary(std::map<std::string, std::string>& target,
+                       const std::string& key) const;
+  };
+
+  class OrthancImage : public boost::noncopyable
+  {
+  private:
+    OrthancPluginImage*    image_;
+
+    void Clear();
+
+    void CheckImageAvailable() const;
+
+  public:
+    OrthancImage();
+
+    explicit OrthancImage(OrthancPluginImage* image);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height,
+                 uint32_t                  pitch,
+                 void*                     buffer);
+
+    ~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() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    unsigned int GetPitch() const;
+    
+    void* GetBuffer() const;
+
+    const OrthancPluginImage* GetObject() const
+    {
+      return image_;
+    }
+
+    void CompressPngImage(MemoryBuffer& target) const;
+
+    void CompressJpegImage(MemoryBuffer& target,
+                           uint8_t quality) const;
+
+    void AnswerPngImage(OrthancPluginRestOutput* output) const;
+
+    void AnswerJpegImage(OrthancPluginRestOutput* output,
+                         uint8_t quality) const;
+    
+    void* GetWriteableBuffer();
+
+    OrthancPluginImage* Release();
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  class FindMatcher : public boost::noncopyable
+  {
+  private:
+    OrthancPluginFindMatcher*          matcher_;
+    const OrthancPluginWorklistQuery*  worklist_;
+
+    void SetupDicom(const void*            query,
+                    uint32_t               size);
+
+  public:
+    explicit FindMatcher(const OrthancPluginWorklistQuery*  worklist);
+
+    FindMatcher(const void*  query,
+                uint32_t     size)
+    {
+      SetupDicom(query, size);
+    }
+
+    explicit FindMatcher(const MemoryBuffer&  dicom)
+    {
+      SetupDicom(dicom.GetData(), dicom.GetSize());
+    }
+
+    ~FindMatcher();
+
+    bool IsMatch(const void*  dicom,
+                 uint32_t     size) const;
+
+    bool IsMatch(const MemoryBuffer& dicom) const
+    {
+      return IsMatch(dicom.GetData(), dicom.GetSize());
+    }
+  };
+#endif
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins);
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins);
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const std::string& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
+                       body.size(), applyPlugins);
+  }
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const MemoryBuffer& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.GetData(),
+                       body.GetSize(), applyPlugins);
+  }
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins);
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins);
+
+  inline bool RestApiPut(Json::Value& result,
+                         const std::string& uri,
+                         const std::string& body,
+                         bool applyPlugins)
+  {
+    return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
+                      body.size(), applyPlugins);
+  }
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins);
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password);
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output);
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output);
+
+  void AnswerHttpError(uint16_t httpError,
+                       OrthancPluginRestOutput* output);
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path);
+#endif
+
+  void LogError(const std::string& message);
+
+  void LogWarning(const std::string& message);
+
+  void LogInfo(const std::string& message);
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision);
+  
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision);
+
+
+  namespace Internals
+  {
+    template <RestCallback Callback>
+    static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                          const char* url,
+                                          const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        Callback(output, url, request);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+  }
+
+  
+  template <RestCallback Callback>
+  void RegisterRestCallback(const std::string& uri,
+                            bool isThreadSafe)
+  {
+    if (isThreadSafe)
+    {
+      OrthancPluginRegisterRestCallbackNoLock
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+    else
+    {
+      OrthancPluginRegisterRestCallback
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  class OrthancPeers : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, uint32_t>   Index;
+
+    OrthancPluginPeers   *peers_;
+    Index                 index_;
+    uint32_t              timeout_;
+
+    size_t GetPeerIndex(const std::string& name) const;
+
+  public:
+    OrthancPeers();
+
+    ~OrthancPeers();
+
+    uint32_t GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void SetTimeout(uint32_t timeout)
+    {
+      timeout_ = timeout;
+    }
+
+    bool LookupName(size_t& target,
+                    const std::string& name) const;
+
+    std::string GetPeerName(size_t index) const;
+
+    std::string GetPeerUrl(size_t index) const;
+
+    std::string GetPeerUrl(const std::string& name) const;
+
+    size_t GetPeersCount() const
+    {
+      return index_.size();
+    }
+
+    bool LookupUserProperty(std::string& value,
+                            size_t index,
+                            const std::string& key) const;
+
+    bool LookupUserProperty(std::string& value,
+                            const std::string& peer,
+                            const std::string& key) const;
+
+    bool DoGet(MemoryBuffer& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(MemoryBuffer& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoPost(MemoryBuffer& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(MemoryBuffer& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPut(size_t index,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoPut(const std::string& name,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoDelete(size_t index,
+                  const std::string& uri) const;
+
+    bool DoDelete(const std::string& name,
+                  const std::string& uri) const;
+  };
+#endif
+
+
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  class OrthancJob : public boost::noncopyable
+  {
+  private:
+    std::string   jobType_;
+    std::string   content_;
+    bool          hasSerialized_;
+    std::string   serialized_;
+    float         progress_;
+
+    static void CallbackFinalize(void* job);
+
+    static float CallbackGetProgress(void* job);
+
+    static const char* CallbackGetContent(void* job);
+
+    static const char* CallbackGetSerialized(void* job);
+
+    static OrthancPluginJobStepStatus CallbackStep(void* job);
+
+    static OrthancPluginErrorCode CallbackStop(void* job,
+                                               OrthancPluginJobStopReason reason);
+
+    static OrthancPluginErrorCode CallbackReset(void* job);
+
+  protected:
+    void ClearContent();
+
+    void UpdateContent(const Json::Value& content);
+
+    void ClearSerialized();
+
+    void UpdateSerialized(const Json::Value& serialized);
+
+    void UpdateProgress(float progress);
+    
+  public:
+    OrthancJob(const std::string& jobType);
+    
+    virtual ~OrthancJob()
+    {
+    }
+
+    virtual OrthancPluginJobStepStatus Step() = 0;
+
+    virtual void Stop(OrthancPluginJobStopReason reason) = 0;
+    
+    virtual void Reset() = 0;
+
+    static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
+
+    static std::string Submit(OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    static void SubmitAndWait(Json::Value& result,
+                              OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    // Submit a job from a POST on the REST API with the same
+    // conventions as in the Orthanc core (according to the
+    // "Synchronous" and "Priority" options)
+    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                      const Json::Value& body,
+                                      OrthancJob* job);
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  inline void SetMetricsValue(char* name,
+                              float value)
+  {
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name,
+                                 value, OrthancPluginMetricsType_Default);
+  }
+
+  class MetricsTimer : public boost::noncopyable
+  {
+  private:
+    std::string               name_;
+    boost::posix_time::ptime  start_;
+
+  public:
+    explicit MetricsTimer(const char* name);
+
+    ~MetricsTimer();
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient : public boost::noncopyable
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class IRequestBody : public boost::noncopyable
+    {
+    public:
+      virtual ~IRequestBody()
+      {
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk) = 0;
+    };
+
+
+    class IAnswer : public boost::noncopyable
+    {
+    public:
+      virtual ~IAnswer()
+      {
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value) = 0;
+
+      virtual void AddChunk(const void* data,
+                            size_t size) = 0;
+    };
+
+
+  private:
+    class RequestBodyWrapper;
+
+    uint16_t                 httpStatus_;
+    OrthancPluginHttpMethod  method_;
+    std::string              url_;
+    HttpHeaders              headers_;
+    std::string              username_;
+    std::string              password_;
+    uint32_t                 timeout_;
+    std::string              certificateFile_;
+    std::string              certificateKeyFile_;
+    std::string              certificateKeyPassword_;
+    bool                     pkcs11_;
+    std::string              fullBody_;
+    IRequestBody*            chunkedBody_;
+    bool                     allowChunkedTransfers_;
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    void ExecuteWithStream(uint16_t& httpStatus,  // out
+                           IAnswer& answer,       // out
+                           IRequestBody& body) const;
+#endif
+
+    void ExecuteWithoutStream(uint16_t& httpStatus,        // out
+                              HttpHeaders& answerHeaders,  // out
+                              std::string& answerBody,     // out
+                              const std::string& body) const;
+    
+  public:
+    HttpClient();
+
+    uint16_t GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    void SetMethod(OrthancPluginHttpMethod method)
+    {
+      method_ = method;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    void SetHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    void AddHeaders(const HttpHeaders& headers);
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password);
+
+    void ClearCredentials();
+
+    void SetTimeout(unsigned int timeout)  // 0 for default timeout
+    {
+      timeout_ = timeout;
+    }
+
+    void SetCertificate(const std::string& certificateFile,
+                        const std::string& keyFile,
+                        const std::string& keyPassword);
+
+    void ClearCertificate();
+
+    void SetPkcs11(bool pkcs11)
+    {
+      pkcs11_ = pkcs11;
+    }
+
+    void ClearBody();
+
+    void SwapBody(std::string& body);
+
+    void SetBody(const std::string& body);
+
+    void SetBody(IRequestBody& body);
+
+    // This function can be used to disable chunked transfers if the
+    // remote server is Orthanc with a version <= 1.5.6.
+    void SetChunkedTransfersAllowed(bool allow)
+    {
+      allowChunkedTransfers_ = allow;
+    }
+
+    bool IsChunkedTransfersAllowed() const
+    {
+      return allowChunkedTransfers_;
+    }
+
+    void Execute(IAnswer& answer);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 std::string& answerBody /* out */);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 Json::Value& answerBody /* out */);
+
+    void Execute();
+  };
+#endif
+
+
+
+  class IChunkedRequestReader : public boost::noncopyable
+  {
+  public:
+    virtual ~IChunkedRequestReader()
+    {
+    }
+
+    virtual void AddChunk(const void* data,
+                          size_t size) = 0;
+
+    virtual void Execute(OrthancPluginRestOutput* output) = 0;
+  };
+
+
+  typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
+                                                         const OrthancPluginHttpRequest* request);
+
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request);
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request);
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+    template <ChunkedRestCallback Callback>
+    static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
+                                                const char* url,
+                                                const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+        else
+        {
+          *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
+          if (*reader == NULL)
+          {
+            return OrthancPluginErrorCode_Plugin;
+          }
+          else
+          {
+            return OrthancPluginErrorCode_Success;
+          }
+        }
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size);
+
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output);
+
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader);
+
+#else  
+
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback GetHandler,
+                                                    ChunkedRestCallback PostHandler,
+                                                    RestCallback DeleteHandler,
+                                                    ChunkedRestCallback PutHandler);
+
+    template<
+      RestCallback         GetHandler,
+      ChunkedRestCallback  PostHandler,
+      RestCallback         DeleteHandler,
+      ChunkedRestCallback  PutHandler
+      >
+    inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                           const char* url,
+                                                           const OrthancPluginHttpRequest* request)
+    {
+      return ChunkedRestCompatibility(output, url, request, GetHandler,
+                                      PostHandler, DeleteHandler, PutHandler);
+    }
+#endif
+  }
+
+
+
+  // NB: We use a templated class instead of a templated function, because
+  // default values are only available in functions since C++11
+  template<
+    RestCallback         GetHandler    = Internals::NullRestCallback,
+    ChunkedRestCallback  PostHandler   = Internals::NullChunkedRestCallback,
+    RestCallback         DeleteHandler = Internals::NullRestCallback,
+    ChunkedRestCallback  PutHandler    = Internals::NullChunkedRestCallback
+    >
+  class ChunkedRestRegistration : public boost::noncopyable
+  {
+  public:
+    static void Apply(const std::string& uri)
+    {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+      OrthancPluginRegisterChunkedRestCallback(
+        GetGlobalContext(), uri.c_str(),
+        GetHandler == Internals::NullRestCallback         ? NULL : Internals::Protect<GetHandler>,
+        PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
+        DeleteHandler == Internals::NullRestCallback      ? NULL : Internals::Protect<DeleteHandler>,
+        PutHandler == Internals::NullChunkedRestCallback  ? NULL : Internals::ChunkedProtect<PutHandler>,
+        Internals::ChunkedRequestReaderAddChunk,
+        Internals::ChunkedRequestReaderExecute,
+        Internals::ChunkedRequestReaderFinalize);
+#else
+      OrthancPluginRegisterRestCallbackNoLock(
+        GetGlobalContext(), uri.c_str(), 
+        Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
+#endif
+    }
+  };
+
+  
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  class IStorageCommitmentScpHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentScpHandler()
+    {
+    }
+    
+    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                               const std::string& sopInstanceUid) = 0;
+    
+    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
+                                         void* rawHandler,
+                                         const char* sopClassUid,
+                                         const char* sopInstanceUid);
+
+    static void Destructor(void* rawHandler);
+  };
+#endif
+
+
+  class DicomInstance : public boost::noncopyable
+  {
+  private:
+    bool toFree_;
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    const OrthancPluginDicomInstance*  instance_;
+#else
+    OrthancPluginDicomInstance*  instance_;
+#endif
+    
+  public:
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    explicit DicomInstance(const OrthancPluginDicomInstance* instance);
+#else
+    explicit DicomInstance(OrthancPluginDicomInstance* instance);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    DicomInstance(const void* buffer,
+                  size_t size);
+#endif
+
+    ~DicomInstance();
+
+    std::string GetRemoteAet() const;
+
+    const void* GetBuffer() const
+    {
+      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
+    }
+
+    size_t GetSize() const
+    {
+      return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
+    }
+
+    void GetJson(Json::Value& target) const;
+
+    void GetSimplifiedJson(Json::Value& target) const;
+
+    OrthancPluginInstanceOrigin GetOrigin() const
+    {
+      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
+    }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    std::string GetTransferSyntaxUid() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    bool HasPixelData() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    unsigned int GetFramesCount() const
+    {
+      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void GetRawFrame(std::string& target,
+                     unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void Serialize(std::string& target) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    static DicomInstance* Transcode(const void* buffer,
+                                    size_t size,
+                                    const std::string& transferSyntax);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-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.
+ * 
+ * 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(HAS_ORTHANC_EXCEPTION)
+#  error The macro HAS_ORTHANC_EXCEPTION must be defined
+#endif
+
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include <OrthancException.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
+#else
+#  include <orthanc/OrthancCPlugin.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
+#endif
+
+
+#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
+
+
+#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
+                                                  
+
+#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
+  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
+  {                                                                 \
+    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
+  }
+
+
+namespace OrthancPlugins
+{
+#if HAS_ORTHANC_EXCEPTION == 0
+  class PluginException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode GetErrorCode() const
+    {
+      return code_;
+    }
+
+    const char* What(OrthancPluginContext* context) const
+    {
+      const char* description = OrthancPluginGetErrorDescription(context, code_);
+      if (description)
+      {
+        return description;
+      }
+      else
+      {
+        return "No description available";
+      }
+    }
+  };
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,31 @@
+# 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.
+# 
+# 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/>.
+
+
+# In Orthanc <= 1.7.1, the instructions below were part of
+# "Compiler.cmake", and were protected by the (now unused) option
+# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map")
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list")
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/Plugins/VersionScriptPlugins.map	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,12 @@
+# This is a version-script for Orthanc plugins
+
+{
+global:
+  OrthancPluginInitialize;
+  OrthancPluginFinalize;
+  OrthancPluginGetName;
+  OrthancPluginGetVersion;
+
+local:
+  *;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Orthanc/README.txt	Tue Aug 11 13:24:38 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/Applications/Resources/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,4740 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup Worklists Worklists
+ * @brief Functions to register and manage worklists.
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
+#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 already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _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 worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       remoteAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, it is up to the plugin to
+   * implement the required locking mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * script.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), 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 char*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const char*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  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);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/SyncOrthancFolder.py	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,63 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import multiprocessing
+import os
+import stat
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+PLUGIN_SDK_VERSION = '1.0.0'
+REPOSITORY = 'https://hg.orthanc-server.com/orthanc/raw-file'
+
+FILES = [
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h',   'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginException.h',    'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/ExportedSymbolsPlugins.list', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake', 'Plugins'),
+    ('OrthancServer/Plugins/Samples/Common/VersionScriptPlugins.map',    'Plugins'),
+]
+
+SDK = [
+    'orthanc/OrthancCPlugin.h',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    with open(target, 'w') as f:
+        f.write(urllib2.urlopen(url).read())
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ 'default',
+                      f[0],
+                      os.path.join(f[1], os.path.basename(f[0])) ])
+
+for f in SDK:
+    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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Common/RtViewerApp.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,274 @@
+/**
+ * Stone of Orthanc
+ * 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/>.
+ **/
+
+// Sample app
+#include "RtViewerApp.h"
+#include "RtViewerView.h"
+#include "SampleHelpers.h"
+
+// Stone of Orthanc
+#include "../../Sources/StoneInitialization.h"
+#include "../../Sources/Scene2D/CairoCompositor.h"
+#include "../../Sources/Scene2D/ColorTextureSceneLayer.h"
+#include "../../Sources/Scene2D/OpenGLCompositor.h"
+#include "../../Sources/Scene2D/PanSceneTracker.h"
+#include "../../Sources/Scene2D/ZoomSceneTracker.h"
+#include "../../Sources/Scene2D/RotateSceneTracker.h"
+#include "../../Sources/Scene2DViewport/UndoStack.h"
+#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h"
+#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h"
+#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h"
+#include "../../Sources/Scene2DViewport/MeasureTool.h"
+#include "../../Sources/Scene2DViewport/PredeclaredTypes.h"
+#include "../../Sources/Volumes/VolumeSceneLayerSource.h"
+#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h"
+#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h"
+#include "../../Sources/StoneException.h"
+
+// Orthanc
+#include <Logging.h>
+#include <OrthancException.h>
+
+// System 
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/make_shared.hpp>
+
+#include <stdio.h>
+
+
+namespace OrthancStone
+{
+  void RtViewerApp::InvalidateAllViewports()
+  {
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->Invalidate();
+    }
+  }
+
+  const VolumeImageGeometry& RtViewerApp::GetMainGeometry()
+  {
+    ORTHANC_ASSERT(geometryProvider_.get() != NULL);
+    ORTHANC_ASSERT(geometryProvider_->HasGeometry());
+    const VolumeImageGeometry& geometry = geometryProvider_->GetImageGeometry();
+    return geometry;
+  }
+
+  RtViewerApp::RtViewerApp()
+    : undoStack_(new UndoStack)
+  {
+    // Create the volumes that will be filled later on
+    ctVolume_ = boost::make_shared<DicomVolumeImage>();
+    doseVolume_ = boost::make_shared<DicomVolumeImage>();
+  }
+
+  boost::shared_ptr<RtViewerApp> RtViewerApp::Create()
+  {
+    boost::shared_ptr<RtViewerApp> thisOne(new RtViewerApp());
+    return thisOne;
+  }
+
+  void RtViewerApp::DisableTracker()
+  {
+    if (activeTracker_)
+    {
+      activeTracker_->Cancel();
+      activeTracker_.reset();
+    }
+  }
+
+  void RtViewerApp::CreateView(const std::string& canvasId, VolumeProjection projection)
+  {
+    boost::shared_ptr<RtViewerView> 
+      view(new RtViewerView(shared_from_this(), canvasId, projection));
+
+    view->RegisterMessages();
+
+    view->CreateLayers(ctLoader_, doseLoader_, doseVolume_, rtstructLoader_);
+
+    views_.push_back(view);
+  }
+
+  void RtViewerApp::CreateLoaders()
+  {
+    // the viewport hosts the scene
+    {
+      // "true" means use progressive quality (jpeg 50 --> jpeg 90 --> 16-bit raw)
+      // "false" means only using hi quality
+      // TODO: add flag for quality
+      ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, true);
+      
+      // better priority for CT vs dose and struct
+      ctLoader_->SetSchedulingPriority(-100);
+
+
+      // we need to store the CT loader to ask from geometry details later on when geometry is loaded
+      geometryProvider_ = ctLoader_;
+
+      doseLoader_ = OrthancMultiframeVolumeLoader::Create(*loadersContext_, doseVolume_);
+      rtstructLoader_ = DicomStructureSetLoader::Create(*loadersContext_);
+    }
+
+    /**
+    Register for notifications issued by the loaders
+    */
+
+    Register<DicomVolumeImage::GeometryReadyMessage>
+      (*ctLoader_, &RtViewerApp::HandleGeometryReady);
+
+    Register<OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality>
+      (*ctLoader_, &RtViewerApp::HandleCTLoaded);
+
+    Register<DicomVolumeImage::ContentUpdatedMessage>
+      (*ctLoader_, &RtViewerApp::HandleCTContentUpdated);
+
+    Register<DicomVolumeImage::ContentUpdatedMessage>
+      (*doseLoader_, &RtViewerApp::HandleDoseLoaded);
+
+    Register<DicomStructureSetLoader::StructuresReady>
+      (*rtstructLoader_, &RtViewerApp::HandleStructuresReady);
+
+    Register<DicomStructureSetLoader::StructuresUpdated>
+      (*rtstructLoader_, &RtViewerApp::HandleStructuresUpdated);
+  }
+
+  void RtViewerApp::StartLoaders()
+  {
+    ORTHANC_ASSERT(HasArgument("ctseries") && HasArgument("rtdose") && HasArgument("rtstruct"));
+
+    LOG(INFO) << "About to load:";
+
+    if (GetArgument("ctseries") == "")
+    {
+      LOG(INFO) << "  CT       : <unspecified>";
+    }
+    else
+    {
+      LOG(INFO) << "  CT       : " << GetArgument("ctseries");
+      ctLoader_->LoadSeries(GetArgument("ctseries"));
+    }
+    
+    if (GetArgument("rtdose") == "")
+    {
+      LOG(INFO) << "  RTDOSE   : <unspecified>";
+    }
+    else
+    {
+      LOG(INFO) << "  RTDOSE   : " << GetArgument("rtdose");
+      doseLoader_->LoadInstance(GetArgument("rtdose"));
+    }
+
+    if (GetArgument("rtstruct") == "")
+    {
+      LOG(INFO) << "  RTSTRUCT : : <unspecified>";
+    }
+    else
+    {
+      LOG(INFO) << "  RTSTRUCT : : " << GetArgument("rtstruct");
+      rtstructLoader_->LoadInstanceFullVisibility(GetArgument("rtstruct"));
+    }
+  }
+
+  void RtViewerApp::HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message)
+  {
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->RetrieveGeometry();
+    }
+    FitContent();
+    UpdateLayersInAllViews();
+  }
+
+  void RtViewerApp::FitContent()
+  {
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->FitContent();
+    }
+  }
+
+  void RtViewerApp::UpdateLayersInAllViews()
+  {
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->UpdateLayers();
+    }
+  }
+
+  void RtViewerApp::HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message)
+  {
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->RetrieveGeometry();
+    }
+    UpdateLayersInAllViews();
+  }
+
+  void RtViewerApp::HandleCTContentUpdated(const DicomVolumeImage::ContentUpdatedMessage& message)
+  {
+    UpdateLayersInAllViews();
+  }
+
+  void RtViewerApp::HandleDoseLoaded(const DicomVolumeImage::ContentUpdatedMessage& message)
+  {
+    //TODO: compute dose extent, with outlier rejection
+    UpdateLayersInAllViews();
+  }
+
+  void RtViewerApp::HandleStructuresReady(const DicomStructureSetLoader::StructuresReady& message)
+  {
+    UpdateLayersInAllViews();
+  }
+
+  void RtViewerApp::HandleStructuresUpdated(const DicomStructureSetLoader::StructuresUpdated& message)
+  {
+    UpdateLayersInAllViews();
+  }
+
+  void RtViewerApp::SetArgument(const std::string& key, const std::string& value)
+  {
+    if (key == "loglevel")
+      OrthancStoneHelpers::SetLogLevel(value);
+    else
+      arguments_[key] = value;
+  }
+
+  std::string RtViewerApp::GetArgument(const std::string& key) const
+  {
+    std::map<std::string, std::string>::const_iterator found = arguments_.find(key);
+    if (found == arguments_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return found->second;
+    }
+  }
+
+  bool RtViewerApp::HasArgument(const std::string& key) const
+  {
+    return (arguments_.find(key) != arguments_.end());
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Common/RtViewerApp.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,169 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../Sources/Loaders/DicomStructureSetLoader.h"
+#include "../../Sources/Loaders/ILoadersContext.h"
+#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h"
+#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../Sources/Messages/IMessageEmitter.h"
+#include "../../Sources/Messages/IObserver.h"
+#include "../../Sources/Messages/ObserverBase.h"
+#include "../../Sources/Oracle/OracleCommandExceptionMessage.h"
+#include "../../Sources/Scene2DViewport/ViewportController.h"
+#include "../../Sources/Viewport/IViewport.h"
+#include "../../Sources/Volumes/DicomVolumeImage.h"
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/thread.hpp>
+#include <boost/noncopyable.hpp>
+
+#if ORTHANC_ENABLE_SDL
+#include <SDL.h>
+#endif
+
+namespace OrthancStone
+{
+  class OpenGLCompositor;
+  class IVolumeSlicer;
+  class ILayerStyleConfigurator;
+  class DicomStructureSetLoader;
+  class IOracle;
+  class ThreadedOracle;
+  class VolumeSceneLayerSource;
+  class SdlOpenGLViewport;
+  class RtViewerView;
+   
+  static const unsigned int FONT_SIZE_0 = 32;
+  static const unsigned int FONT_SIZE_1 = 24;
+
+  class Scene2D;
+  class UndoStack;
+
+  /**
+  This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that
+  can be sent from multiple threads)
+  */
+  class RtViewerApp : public ObserverBase<RtViewerApp>
+  {
+  public:
+
+    void PrepareScene();
+
+#if ORTHANC_ENABLE_SDL
+  public:
+    void RunSdl(int argc, char* argv[]);
+    void SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
+                    OrthancStone::DefaultViewportInteractor& interactor);
+  private:
+    void ProcessOptions(int argc, char* argv[]);
+    void HandleApplicationEvent(const SDL_Event& event);
+#elif ORTHANC_ENABLE_WASM
+  public:
+    void RunWasm();
+#else
+#  error Either ORTHANC_ENABLE_SDL or ORTHANC_ENABLE_WASM must be enabled
+#endif
+
+  public:
+    void DisableTracker();
+
+    /**
+    Called by command-line option processing or when parsing the URL 
+    parameters.
+    */
+    void SetArgument(const std::string& key, const std::string& value);
+
+    const VolumeImageGeometry& GetMainGeometry();
+
+    static boost::shared_ptr<RtViewerApp> Create();
+
+    void CreateView(const std::string& canvasId, VolumeProjection projection);
+
+  protected:
+    RtViewerApp();
+
+  private:
+    void CreateLoaders();
+    void StartLoaders();
+    void SelectNextTool();
+
+    // argument handling
+    // SetArgument is above (public section)
+    std::map<std::string, std::string> arguments_;
+
+    std::string GetArgument(const std::string& key) const;
+    bool HasArgument(const std::string& key) const;
+
+    /**
+      This adds the command at the top of the undo stack
+    */
+    //void Commit(boost::shared_ptr<TrackerCommand> cmd);
+    void Undo();
+    void Redo();
+
+    void HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message);
+    
+    // TODO: wire this
+    void HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message);
+    void HandleCTContentUpdated(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message);
+    void HandleDoseLoaded(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message);
+    void HandleStructuresReady(const OrthancStone::DicomStructureSetLoader::StructuresReady& message);
+    void HandleStructuresUpdated(const OrthancStone::DicomStructureSetLoader::StructuresUpdated& message);
+
+
+  private:
+    void RetrieveGeometry();
+    void FitContent();
+    void InvalidateAllViewports();
+    void UpdateLayersInAllViews();
+
+  private:
+    boost::shared_ptr<DicomVolumeImage>  ctVolume_;
+    boost::shared_ptr<DicomVolumeImage>  doseVolume_;
+
+    std::vector<boost::shared_ptr<RtViewerView> >  views_;
+
+    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader_;
+    boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader_;
+    boost::shared_ptr<DicomStructureSetLoader>  rtstructLoader_;
+
+    /** encapsulates resources shared by loaders */
+    boost::shared_ptr<ILoadersContext>                  loadersContext_;
+
+    /**
+    another interface to the ctLoader object (that also implements the IVolumeSlicer interface), that serves as the 
+    reference for the geometry (position and dimensions of the volume + size of each voxel). It could be changed to be 
+    the dose instead, but the CT is chosen because it usually has a better spatial resolution.
+    */
+    boost::shared_ptr<OrthancStone::IGeometryProvider>  geometryProvider_;
+
+
+    boost::shared_ptr<IFlexiblePointerTracker> activeTracker_;
+
+    boost::shared_ptr<UndoStack> undoStack_;
+  };
+
+}
+
+
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Common/RtViewerView.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,352 @@
+/**
+ * Stone of Orthanc
+ * 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/>.
+ **/
+
+
+// Sample app
+#include "RtViewerView.h"
+#include "RtViewerApp.h"
+#include "SampleHelpers.h"
+
+#include <EmbeddedResources.h>
+
+// Stone of Orthanc
+#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Sources/Scene2D/CairoCompositor.h"
+#include "../../Sources/Scene2D/ColorTextureSceneLayer.h"
+#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h"
+#include "../../Sources/Scene2D/OpenGLCompositor.h"
+#include "../../Sources/Scene2D/PanSceneTracker.h"
+#include "../../Sources/Scene2D/RotateSceneTracker.h"
+#include "../../Sources/Scene2D/ZoomSceneTracker.h"
+#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h"
+#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h"
+#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h"
+#include "../../Sources/Scene2DViewport/MeasureTool.h"
+#include "../../Sources/Scene2DViewport/PredeclaredTypes.h"
+#include "../../Sources/Scene2DViewport/UndoStack.h"
+#include "../../Sources/StoneException.h"
+#include "../../Sources/StoneInitialization.h"
+#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h"
+#include "../../Sources/Volumes/VolumeSceneLayerSource.h"
+
+// Orthanc
+#include <Compatibility.h>  // For std::unique_ptr<>
+#include <Logging.h>
+#include <OrthancException.h>
+
+// System 
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/make_shared.hpp>
+
+#include <stdio.h>
+
+
+namespace OrthancStone
+{
+  boost::shared_ptr<RtViewerApp> RtViewerView::GetApp()
+  {
+    return app_.lock();
+  }
+
+  void RtViewerView::DisplayInfoText()
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+
+    // do not try to use stuff too early!
+    OrthancStone::ICompositor& compositor = lock->GetCompositor();
+
+    std::stringstream msg;
+
+    for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin();
+         kv != infoTextMap_.end(); ++kv)
+    {
+      msg << kv->first << " : " << kv->second << std::endl;
+    }
+    std::string msgS = msg.str();
+
+    TextSceneLayer* layerP = NULL;
+    if (scene.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
+    {
+      TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>(
+        scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
+      layerP = &layer;
+    }
+    else
+    {
+      std::unique_ptr<TextSceneLayer> layer(new TextSceneLayer);
+      layerP = layer.get();
+      layer->SetColor(0, 255, 0);
+      layer->SetFontIndex(1);
+      layer->SetBorder(20);
+      layer->SetAnchor(BitmapAnchor_TopLeft);
+      //layer->SetPosition(0,0);
+      scene.SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
+    }
+    // position the fixed info text in the upper right corner
+    layerP->SetText(msgS.c_str());
+    double cX = compositor.GetCanvasWidth() * (-0.5);
+    double cY = compositor.GetCanvasHeight() * (-0.5);
+    scene.GetCanvasToSceneTransform().Apply(cX, cY);
+    layerP->SetPosition(cX, cY);
+    lock->Invalidate();
+  }
+
+  void RtViewerView::DisplayFloatingCtrlInfoText(const PointerEvent& e)
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+
+    ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
+
+    char buf[128];
+    sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)",
+            p.GetX(), p.GetY(),
+            e.GetMainPosition().GetX(), e.GetMainPosition().GetY());
+
+    if (scene.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
+    {
+      TextSceneLayer& layer =
+        dynamic_cast<TextSceneLayer&>(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
+      layer.SetText(buf);
+      layer.SetPosition(p.GetX(), p.GetY());
+    }
+    else
+    {
+      std::unique_ptr<TextSceneLayer> layer(new TextSceneLayer);
+      layer->SetColor(0, 255, 0);
+      layer->SetText(buf);
+      layer->SetBorder(20);
+      layer->SetAnchor(BitmapAnchor_BottomCenter);
+      layer->SetPosition(p.GetX(), p.GetY());
+      scene.SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
+    }
+  }
+
+  void RtViewerView::HideInfoText()
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+
+    scene.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
+  }
+
+  void RtViewerView::OnSceneTransformChanged(
+    const ViewportController::SceneTransformChanged& message)
+  {
+    DisplayInfoText();
+  }
+
+  void RtViewerView::Invalidate()
+  {
+    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+    lock->GetCompositor().FitContent(lock->GetController().GetScene());
+    lock->Invalidate();
+  }
+
+  void RtViewerView::FitContent()
+  {
+    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+    lock->GetCompositor().FitContent(lock->GetController().GetScene());
+    lock->Invalidate();
+  }
+
+  void RtViewerView::Scroll(int delta)
+  {
+    if (!planes_.empty())
+    {
+      int next = 0;
+      int temp = static_cast<int>(currentPlane_) + delta;
+
+      if (temp < 0)
+      {
+        next = 0;
+      }
+      else if (temp >= static_cast<int>(planes_.size()))
+      {
+        next = static_cast<unsigned int>(planes_.size()) - 1;
+      }
+      else
+      {
+        next = static_cast<size_t>(temp);
+      }
+      LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next;
+
+      if (next != static_cast<int>(currentPlane_))
+      {
+        currentPlane_ = next;
+        UpdateLayers();
+      }
+    }
+  }
+
+  void RtViewerView::RetrieveGeometry()
+  {
+    const VolumeImageGeometry& geometry = GetApp()->GetMainGeometry();
+
+    const unsigned int depth = geometry.GetProjectionDepth(projection_);
+    currentPlane_ = depth / 2;
+
+    planes_.resize(depth);
+
+    for (unsigned int z = 0; z < depth; z++)
+    {
+      planes_[z] = geometry.GetProjectionSlice(projection_, z);
+    }
+
+    UpdateLayers();
+  }
+
+  void RtViewerView::UpdateLayers()
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+
+    if (planes_.size() == 0)
+    {
+      RetrieveGeometry();
+    }
+
+    if (currentPlane_ < planes_.size())
+    {
+      if (ctVolumeLayerSource_.get() != NULL)
+      {
+        ctVolumeLayerSource_->Update(planes_[currentPlane_]);
+      }
+      if (doseVolumeLayerSource_.get() != NULL)
+      {
+        doseVolumeLayerSource_->Update(planes_[currentPlane_]);
+      }
+      if (structLayerSource_.get() != NULL)
+      {
+        structLayerSource_->Update(planes_[currentPlane_]);
+      }
+    }
+    lock->Invalidate();
+  }
+
+  void RtViewerView::PrepareViewport()
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    ICompositor& compositor = lock->GetCompositor();
+
+    // False means we do NOT let a hi-DPI aware desktop managedr treat this as a legacy application that requires
+    // scaling.
+    controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight());
+
+    std::string ttf;
+    Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT);
+    compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1);
+    compositor.SetFont(1, ttf, FONT_SIZE_1, Orthanc::Encoding_Latin1);
+  }
+
+  void RtViewerView::SetInfoDisplayMessage(
+    std::string key, std::string value)
+  {
+    if (value == "")
+      infoTextMap_.erase(key);
+    else
+      infoTextMap_[key] = value;
+    DisplayInfoText();
+  }
+
+  void RtViewerView::RegisterMessages()
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Register<ViewportController::SceneTransformChanged>(controller, &RtViewerView::OnSceneTransformChanged);
+  }
+
+  void RtViewerView::CreateLayers(boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader,
+                                  boost::shared_ptr<OrthancMultiframeVolumeLoader>        doseLoader,
+                                  boost::shared_ptr<DicomVolumeImage>                     doseVolume,
+                                  boost::shared_ptr<DicomStructureSetLoader>              rtstructLoader)
+  {
+    /**
+    Configure the CT
+    */
+    std::unique_ptr<GrayscaleStyleConfigurator> style(new GrayscaleStyleConfigurator);
+    style->SetLinearInterpolation(true);
+
+    this->SetCtVolumeSlicer(ctLoader, style.release());
+
+    {
+      std::string lut;
+      Orthanc::EmbeddedResources::GetFileResource(lut, Orthanc::EmbeddedResources::COLORMAP_HOT);
+
+      std::unique_ptr<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator);
+      config->SetLookupTable(lut);
+
+      boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(doseVolume));
+      this->SetDoseVolumeSlicer(tmp, config.release());
+    }
+
+    this->SetStructureSet(rtstructLoader);
+  }
+
+  void RtViewerView::SetCtVolumeSlicer(const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+                                       OrthancStone::ILayerStyleConfigurator* style)
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+    int depth = scene.GetMaxDepth() + 1;
+
+    ctVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume));
+
+    if (style != NULL)
+    {
+      ctVolumeLayerSource_->SetConfigurator(style);
+    }
+
+    ctLayer_ = depth;
+  }
+
+  void RtViewerView::SetDoseVolumeSlicer(const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+                                         OrthancStone::ILayerStyleConfigurator* style)
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+    int depth = scene.GetMaxDepth() + 1;
+
+    doseVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume));
+
+    if (style != NULL)
+    {
+      doseVolumeLayerSource_->SetConfigurator(style);
+    }
+  }
+
+  void RtViewerView::SetStructureSet(const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+    int depth = scene.GetMaxDepth() + 1;
+
+    structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Common/RtViewerView.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,144 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../Sources/Loaders/DicomStructureSetLoader.h"
+#include "../../Sources/Loaders/ILoadersContext.h"
+#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h"
+#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../Sources/Messages/IMessageEmitter.h"
+#include "../../Sources/Messages/IObserver.h"
+#include "../../Sources/Messages/ObserverBase.h"
+#include "../../Sources/Oracle/OracleCommandExceptionMessage.h"
+#include "../../Sources/Scene2DViewport/ViewportController.h"
+#include "../../Sources/Viewport/IViewport.h"
+#include "../../Sources/Volumes/DicomVolumeImage.h"
+#include "../../Sources/Volumes/VolumeSceneLayerSource.h"
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/thread.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class RtViewerApp;
+
+  class RtViewerView : public ObserverBase<RtViewerView>
+  {
+  public:
+    RtViewerView(boost::weak_ptr<RtViewerApp> app, 
+                 const std::string& canvasId, 
+                 VolumeProjection projection) 
+      : app_(app)
+      , currentPlane_(0)
+      , projection_(projection)
+      , ctLayer_(0)
+    {
+      viewport_ = CreateViewport(canvasId);
+      FLOATING_INFOTEXT_LAYER_ZINDEX = 6;
+      FIXED_INFOTEXT_LAYER_ZINDEX = 7;
+    }
+
+    /**
+    This method is called when the scene transform changes. It allows to
+    recompute the visual elements whose content depend upon the scene transform
+    */
+    void OnSceneTransformChanged(
+      const ViewportController::SceneTransformChanged& message);
+
+    /**
+    This method will ask the VolumeSceneLayerSource, that are responsible to
+    generated 2D content based on a volume and a cutting plane, to regenerate
+    it. This is required if the volume itself changes (during loading) or if
+    the cutting plane is changed
+    */
+    void UpdateLayers();
+
+    void Refresh();
+
+    void TakeScreenshot(
+      const std::string& target,
+      unsigned int canvasWidth,
+      unsigned int canvasHeight);
+
+    void Scroll(int delta);
+
+    void Invalidate();
+    void FitContent();
+    void RetrieveGeometry();
+    void PrepareViewport();
+    void RegisterMessages();
+
+#if ORTHANC_ENABLE_SDL == 1
+    void EnableGLDebugOutput();
+#endif
+
+    void CreateLayers(boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader,
+                      boost::shared_ptr<OrthancMultiframeVolumeLoader>        doseLoader,
+                      boost::shared_ptr<DicomVolumeImage>                     doseVolume,
+                      boost::shared_ptr<DicomStructureSetLoader>              rtstructLoader);
+
+    boost::shared_ptr<IViewport> GetViewport()
+    {
+      return viewport_;
+    }
+
+    int GetCtLayerIndex() const
+    {
+      return ctLayer_;
+    }
+
+  private:
+    void SetInfoDisplayMessage(std::string key, std::string value);
+    boost::shared_ptr<RtViewerApp> GetApp();
+    boost::shared_ptr<IViewport> CreateViewport(const std::string& canvasId);
+    void DisplayInfoText();
+    void HideInfoText();
+    void DisplayFloatingCtrlInfoText(const PointerEvent& e);
+
+    void SetCtVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume,
+                           ILayerStyleConfigurator* style);
+
+    void SetDoseVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume,
+                             ILayerStyleConfigurator* style);
+
+    void SetStructureSet(const boost::shared_ptr<DicomStructureSetLoader>& volume);
+
+  private:
+    boost::weak_ptr<RtViewerApp> app_;
+    boost::shared_ptr<VolumeSceneLayerSource>  ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_;
+
+  // collection of cutting planes for this particular view
+    std::vector<OrthancStone::CoordinateSystem3D>       planes_;
+    size_t                                              currentPlane_;
+
+    VolumeProjection                                    projection_;
+
+    std::map<std::string, std::string> infoTextMap_;
+
+    int FLOATING_INFOTEXT_LAYER_ZINDEX;
+    int FIXED_INFOTEXT_LAYER_ZINDEX;
+    boost::shared_ptr<IViewport> viewport_;
+
+    int ctLayer_;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Common/SampleHelpers.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,58 @@
+/**
+ * Stone of Orthanc
+ * 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 <Logging.h>
+
+#include <boost/algorithm/string.hpp>
+
+#include <string>
+#include <iostream>
+
+namespace OrthancStoneHelpers
+{
+  inline void SetLogLevel(std::string logLevel)
+  {
+    boost::to_lower(logLevel);
+    if (logLevel == "warning")
+    {
+      Orthanc::Logging::EnableInfoLevel(false);
+      Orthanc::Logging::EnableTraceLevel(false);
+    }
+    else if (logLevel == "info")
+    {
+      Orthanc::Logging::EnableInfoLevel(true);
+      Orthanc::Logging::EnableTraceLevel(false);
+    }
+    else if (logLevel == "trace")
+    {
+      Orthanc::Logging::EnableInfoLevel(true);
+      Orthanc::Logging::EnableTraceLevel(true);
+    }
+    else
+    {
+      std::cerr << "Unknown log level \"" << logLevel << "\". Will use TRACE as default!";
+      Orthanc::Logging::EnableInfoLevel(true);
+      Orthanc::Logging::EnableTraceLevel(true);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/README.md	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,267 @@
+General
+=======
+These samples assume that a recent version of Orthanc is checked out in an
+`orthanc` folder next to the `orthanc-stone` folder. Let's call the top folder
+the `devroot` folder. This name does not matter and is not used anywhere.
+
+Here's the directory layout that we suggest:
+
+```
+devroot/
+ |
+ +- orthanc/
+ |
+ +- orthanc-stone/
+ |
+ ...
+```
+
+ Orthanc can be retrieved with:
+ ```
+ hg clone https://hg.orthanc-server.com/orthanc
+ ```
+
+Furthermore, the samples usually assume that an Orthanc is running locally,
+without authentication, on port 8042. The samples can easily be tweaked if 
+your setup is different.
+
+When Dicom resources are to be displayed, their IDs can be supplied in the 
+various ways suitable for the platform (command-line arguments, URL parameters
+or through the GUI)
+
+
+This repo contains two sample projects:
+
+SingleFrameViewer
+-----------------
+
+This sample application displays a single frame of a Dicom instance that can
+be loaded from Orthanc, either by using the Orthanc REST API or through the 
+Dicomweb server functionality of Orthanc.
+
+RtViewer
+--------
+
+This sample application displays set of Radiotherapy data:
+- a CT scan
+- an RT-Dose
+- an RT-Struct
+
+The WebAssembly version displays 3 viewports with MPR data
+while the SDL sample displays a single viewport.
+
+ 
+WebAssembly samples
+===================
+
+Building the WebAssembly samples require the Emscripten SDK 
+(https://emscripten.org/). This SDK goes far beyond the simple compilation to
+the wasm (Web Assembly) bytecode and provides a comprehensive library that 
+eases porting native C and C++ programs and libraries. The Emscripten SDK also
+makes it easy to generate the companion Javascript files requires to use a 
+wasm module in a web application.
+
+Although Emscripten runs on all major platforms, Stone of Orthanc is developed
+and tested with the Linux version of Emscripten.
+
+Emscripten runs perfectly fine under the Windows Subsystem for Linux (that is
+the environment used quite often by the Stone of Orthanc team)
+
+**Important note:** The following examples **and the build scripts** will 
+assume that you have installed the Emscripten SDK in `~/apps/emsdk`.
+
+The following packages should get you going (a Debian-like distribution such 
+as Debian or Ubuntu is assumed)
+
+```
+sudo apt-get update 
+sudo apt-get install -y build-essential curl wget git python cmake pkg-config
+sudo apt-get install -y mercurial unzip npm ninja-build p7zip-full gettext-base 
+```
+
+To build the Wasm samples, just launch the `build-wasm-samples.sh` script from
+this folder.  Optionaly, you can pass the build type as an argument.
+We suggest that you do *not* use the `Debug` configuration unless you really 
+need it, for the additional checks that are made will lead to a very long 
+build time and much slower execution (more severe than with a native non-wasm
+target)
+
+In order to run the sample, you may serve it with the ServeFolders plugin.
+You can i.e: add such a section in your orthanc configuration file:
+
+```
+{
+  "Plugins" : ["LibServeFolders.so],
+  "ServeFolders" : {
+    "/single-frame-viewer" : "..../out/install-stone-wasm-samples-RelWithDebInfo/SingleFrameViewer",
+    "/rt-viewer": "..../out/install-stone-wasm-samples-RelWithDebInfo/RtViewer"
+  }
+}
+```
+
+You'll then be able to open the single-frame-viewer demo at `http://localhost:8042/single-frame-viewer/index.html` 
+
+The rt-viewer demo at
+`http://localhost:8044/rt-viewer/index.html?ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9`.  Note that you must provide 3 ids in the url:
+
+- the CT series Orthanc ID
+- the RT-Dose instance Orthanc ID
+- the RT-Struct instance Orthanc ID
+
+
+RtViewer
+-----------------
+
+This sample application displays three MPR views of a CT+RTDOSE+RTSTRUCT dataset, loaded from Orthanc. The Orthanc IDs of the dataset must be supplied as URL parameters like:
+
+```
+http://localhost:9979/stone-rtviewer/index.html?loglevel=info&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+```
+
+(notice the loglevel parameter that can be `warning`, `info` or `trace`, in increasing verbosity order)
+
+This sample uses plain Javascript and requires the 
+Emscripten toolchain and cmake, in addition to a few standard packages.
+
+To build it, just launch the `build-wasm-RtViewer.sh` script from
+this folder.  Optionaly, you can pass the build type as an argument.
+We suggest that you do *not* use the `Debug` configuration unless you really 
+need it, for the additional checks that are made will lead to a very long 
+build time and much slower execution (more severe than with a native non-wasm
+target)
+
+In order to run the sample, you may serve it with the ServeFolders plugin.
+You can i.e: add such a section in your orthanc configuration file:
+
+```
+{
+  "Plugins" : ["LibServeFolders.so],
+  "ServeFolders" : {
+    "/rt-viewer" : "..../out/install-stone-wasm-RtViewer-RelWithDebInfo"
+  }
+}
+```
+
+You'll then be able to open the demo at `http://localhost:8042/rt-viewer/index.html`
+
+
+RtViewerPlugin
+---------------
+This C++ plugin allows to extend the Orthanc Explorer to add a button labeled "Stone RT Viewer" 
+in the series page. 
+
+It also embeds and serves the RT Viewer files and is thus a standalone way of using this viewer.
+
+Please note that building this plugin requires that the RtViewer be built inside the wasm-binaries 
+folder of the repo.
+
+This will automatically be the case if you use the `<REPO-ROOT>/OrthancStone/Samples/WebAssembly/docker-build.sh` script.
+
+If you use the `build-wasm-samples.sh` script above, you will have the copy `RtViewer` **folder**
+from `<REPO-ROOT>/out/install-stone-wasm-RtViewer-RelWithDebInfo` to `<REPO-ROOT>/wasm-binaries/`.
+
+TL;DR: Build like this (assuming `~/orthanc-stone` is the repo ):
+
+```
+~/orthanc-stone/OrthancStone/Samples/WebAssembly/docker-build.sh
+~/orthanc-stone/OrthancStone/Samples/RtViewerPlugin/docker-build.sh
+```
+
+Once this is done, the plugin can be found in:
+
+```
+~/orthanc-stone/wasm-binaries/share/orthanc/plugins/libRtViewerPlugin.so
+```
+
+Add this path to the `"Plugins"` section of your Orthanc configuration, start Orthanc, and you 
+should now see a "Stone MPR RT Viewer" button in the Orthanc Explorer, at the *series* level.
+
+Open it on a CT series, and the RTSTRUCT and RTDOSE series of the same study will be loaded in
+the viewer.
+
+Native samples
+=================
+
+### Windows build 
+
+Here's how to build the SdlSimpleViewer example using Visual Studio 2019
+(the shell is Powershell, but the legacy shell can also be used with some 
+tweaks). This example is meant to be launched from the folder above 
+orthanc-stone.
+
+```
+  # create the build folder and navigate to it
+  $buildDir = "build-stone-sdlviewer-msvc16-x64"
+
+  if (-not (Test-Path $buildDir)) {
+    mkdir -p $buildDir | Out-Null
+  }
+  
+  cd $buildDir
+  
+  # perform the configuration
+  cmake -G "Visual Studio 16 2019" -A x64 `
+    -DMSVC_MULTIPLE_PROCESSES=ON `
+    -DALLOW_DOWNLOADS=ON `
+    -DSTATIC_BUILD=ON `
+    -DOPENSSL_NO_CAPIENG=ON `
+    ../orthanc-stone/OrthancStone/Samples/Sdl
+  
+  $solutionPath = ls -filter *.sln
+  Write-Host "Solution file(s) available at: $solutionPath"
+```
+
+The initial configuration step will be quite lengthy, for CMake needs to 
+setup its internal cache based on your environment and build tools.
+
+Subsequent runs will be several orders of magnitude faster!
+
+One the solution (.sln) file is ready, you can open it using the Visual Studio
+IDE and choose Build --> Build solution.
+
+An alternative is to execute `cmake --build .` in the build folder created by
+the script.
+
+In order to run the sample, make sure you've an Orthanc server running i.e. on 
+port 8042 and launch:
+
+```
+./SdlSimpleViewer --orthanc http://localhost:8042 --instance 7fc84013-abef174e-3354ca83-b9cdb2a4-f1a74368
+
+./RtViewerSdl --orthanc http://localhost:8042 --ctseries a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --rtdose 830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --rtstruct 54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+```
+
+RtViewer
+---------------
+
+### Windows build 
+
+Here's how to build the SdlSimpleViewer example using Visual Studio 2019
+(the shell is Powershell, but the legacy shell can also be used with some 
+tweaks). This example is meant to be launched from the folder above 
+orthanc-stone.
+
+```
+  $buildRootDir = "out"
+  $buildDirName = "build-stone-sdl-RtViewer-msvc16-x64"
+  $buildDir = Join-Path -Path $buildRootDir -ChildPath $buildDirName
+
+  if (-not (Test-Path $buildDir)) {
+    mkdir -p $buildDir | Out-Null
+  }
+  
+  cd $buildDir
+  
+  cmake -G "Visual Studio 16 2019" -A x64 `
+    -DMSVC_MULTIPLE_PROCESSES=ON `
+    -DALLOW_DOWNLOADS=ON `
+    -DSTATIC_BUILD=ON `
+    -DOPENSSL_NO_CAPIENG=ON `
+    ../../orthanc-stone/OrthancStone/Samples/Sdl/RtViewer
+```
+
+Executing `cmake --build .` in the build folder will launch the Microsoft 
+builder on the solution.
+
+Alternatively, you can open and build the solution using Visual Studio 2019.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,98 @@
+cmake_minimum_required(VERSION 2.8.3)
+
+project(StoneWebViewerPlugin)
+
+set(ORTHANC_PLUGIN_VERSION "mainline")
+
+if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/RtViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder")
+
+# 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 \"system\", \"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_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked")
+mark_as_advanced(ORTHANC_FRAMEWORK_STATIC)
+
+
+# Download and setup the Orthanc framework
+include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
+
+include_directories(${ORTHANC_FRAMEWORK_ROOT})
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES})
+
+else()
+  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+  set(ENABLE_MODULE_IMAGES OFF)
+  set(ENABLE_MODULE_JOBS OFF)
+  set(ENABLE_MODULE_DICOM OFF)
+  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+endif()
+
+include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake)
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  include_directories(${CMAKE_SOURCE_DIR}/Resources/OrthancSdk-1.0.0)
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
+  -DPLUGIN_NAME="stone-rtviewer"
+  )
+
+
+EmbedResources(
+  # Web Viewer Folders
+  # IMAGES                 ${STONE_BINARIES_WEB_VIEWER}/img/
+  # WEB_APPLICATION        ${CMAKE_SOURCE_DIR}/../WebApplication
+
+  # Explorer extension code
+  ORTHANC_EXPLORER       ${CMAKE_SOURCE_DIR}/OrthancExplorer.js
+
+  # RtViewer individual files
+  RT_VIEWER_WASM_JS      ${STONE_BINARIES}/RtViewerWasm.js
+  RT_VIEWER_WASM         ${STONE_BINARIES}/RtViewerWasm.wasm
+  RT_VIEWER_WASM_APP_JS  ${STONE_BINARIES}/RtViewerWasmApp.js
+  RT_VIEWER_INDEX_HTML   ${STONE_BINARIES}/index.html
+  )
+
+add_library(RtViewerPlugin SHARED
+  Plugin.cpp
+  ${AUTOGENERATED_SOURCES}
+  ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
+  ${ORTHANC_CORE_SOURCES}
+  )
+
+set_target_properties(RtViewerPlugin PROPERTIES 
+  VERSION ${ORTHANC_PLUGIN_VERSION} 
+  SOVERSION ${ORTHANC_PLUGIN_VERSION})
+
+install(
+  TARGETS RtViewerPlugin
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/OrthancExplorer.js	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,87 @@
+$('#series').live('pagebeforecreate', function() {
+  var b = $('<a>')
+      .attr('data-role', 'button')
+      .attr('href', '#')
+      .attr('data-icon', 'search')
+      .attr('data-theme', 'e')
+      .text('Stone MPR RT Sample Viewer');``
+
+  b.insertBefore($('#series-delete').parent().parent());
+  b.click(function() {
+    if ($.mobile.pageData) {
+      $.ajax({
+        url: '../series/' + $.mobile.pageData.uuid,
+        dataType: 'json',
+        cache: false,
+        success: function(series) {
+
+          // we consider that the imaging series to display is the 
+          // current one.
+          // we will look for RTDOSE and RTSTRUCT instances in the 
+          // sibling series from the same study. The first one of 
+          // each modality will be grabbed.
+          let ctSeries = $.mobile.pageData.uuid;
+
+          $.ajax({
+            url: '../studies/' + series.ParentStudy,
+            dataType: 'json',
+            cache: false,
+            success: function(study) {
+              // Loop on the study series and find the first RTSTRUCT and RTDOSE instances,
+              // if any.
+              let rtStructInstance = null;
+              let rtDoseInstance = null;
+              let rtPetInstance = null;
+              let seriesRequests = []
+
+              study.Series.forEach( function(studySeriesUuid) {
+                let request = $.ajax({
+                  url: '../series/' + studySeriesUuid,
+                  dataType: 'json',
+                  cache: false,
+                });
+                seriesRequests.push(request);
+              });
+
+              $.when.apply($,seriesRequests).then(function() {
+                [].forEach.call(arguments, function(response) {
+                  siblingSeries = response[0]
+                  if (siblingSeries.MainDicomTags.Modality == "RTDOSE") {
+                    // we have found an RTDOSE series. Let's grab the first instance
+                    if (siblingSeries.Instances.length > 0) {
+                      if(rtDoseInstance == null) {
+                        rtDoseInstance = siblingSeries.Instances[0];
+                      }
+                    }
+                  }
+                  if (siblingSeries.MainDicomTags.Modality == "PT") {
+                    // we have found an RTDOSE series. Let's grab the first instance
+                    if (siblingSeries.Instances.length > 0) {
+                      if(rtPetInstance == null) {
+                        rtPetInstance = siblingSeries.Instances[0];
+                      }
+                    }
+                  }
+                  if (siblingSeries.MainDicomTags.Modality == "RTSTRUCT") {
+                    // we have found an RTDOSE series. Let's grab the first instance
+                    if (siblingSeries.Instances.length > 0) {
+                      if(rtStructInstance == null) {
+                        rtStructInstance = siblingSeries.Instances[0];
+                      }
+                    }
+                  }
+                });
+                let mprViewerUrl = '../stone-rtviewer/index.html?ctseries=' + ctSeries + 
+                '&rtdose=' + rtDoseInstance + 
+                '&rtstruct=' + rtStructInstance;
+                //console.log("About to open: " + mprViewerUrl);
+                window.open(mprViewerUrl);
+              });
+            }
+          });      
+        }
+      });      
+    }
+  });
+});
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Plugin.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,185 @@
+/**
+ * Stone of Orthanc
+ * 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 "Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+
+#include <EmbeddedResources.h>
+
+#include <SystemToolbox.h>
+#include <Toolbox.h>
+
+OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
+                                        OrthancPluginResourceType resourceType,
+                                        const char* resourceId)
+{
+  try
+  {
+    if (changeType == OrthancPluginChangeType_OrthancStarted)
+    {
+      Json::Value info;
+      if (!OrthancPlugins::RestApiGet(info, "/plugins/web-viewer", false))
+      {
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_InternalError,
+          "The Stone MPR RT viewer requires the Web Viewer plugin to be installed");
+      }
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception: " << e.What();
+    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+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);
+  }
+}
+
+
+template <enum Orthanc::EmbeddedResources::FileResourceId file>
+void ServeEmbeddedFile(OrthancPluginRestOutput* output,
+                       const char* url,
+                       const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url));
+
+    std::string s;
+    Orthanc::EmbeddedResources::GetFileResource(s, file);
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime);
+  }
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    OrthancPlugins::SetGlobalContext(context);
+
+#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2)
+    Orthanc::Logging::InitializePluginContext(context);
+#else
+    Orthanc::Logging::Initialize(context);
+#endif
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context, info);
+      return -1;
+    }
+
+    try
+    {
+      std::string explorer;
+      Orthanc::EmbeddedResources::GetFileResource(
+        explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
+      OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str());
+      
+      // RtViewer files below.
+      // ---------------------
+      // we do not serve the whole directory at once (with ServeEmbeddedFolder)
+      // because it contains uppercase characters that are forbidden by the
+      // resource embedding system
+
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_JS> >
+        ("/stone-rtviewer/RtViewerWasm.js", true);
+
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM> >
+        ("/stone-rtviewer/RtViewerWasm.wasm", true);
+
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_APP_JS> >
+        ("/stone-rtviewer/RtViewerWasmApp.js", true);
+
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_INDEX_HTML> >
+        ("/stone-rtviewer/index.html", true);
+
+      OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
+    }
+    catch (...)
+    {
+      OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin");
+      return -1;
+    }
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return PLUGIN_NAME;
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,3383 @@
+/**
+ * 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.
+ * 
+ * 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 <boost/algorithm/string/predicate.hpp>
+#include <boost/move/unique_ptr.hpp>
+#include <boost/thread.hpp>
+#include <json/reader.h>
+#include <json/writer.h>
+
+
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
+#endif
+
+
+namespace OrthancPlugins
+{
+  static OrthancPluginContext* globalContext_ = NULL;
+
+
+  void SetGlobalContext(OrthancPluginContext* context)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+    else if (globalContext_ == NULL)
+    {
+      globalContext_ = context;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+  }
+
+
+  bool HasGlobalContext()
+  {
+    return globalContext_ != NULL;
+  }
+
+
+  OrthancPluginContext* GetGlobalContext()
+  {
+    if (globalContext_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      return globalContext_;
+    }
+  }
+
+
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (code == OrthancPluginErrorCode_UnknownResource ||
+             code == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  MemoryBuffer::MemoryBuffer()
+  {
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  MemoryBuffer::MemoryBuffer(const void* buffer,
+                             size_t size)
+  {
+    uint32_t s = static_cast<uint32_t>(size);
+    if (static_cast<size_t>(s) != size)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
+             OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else
+    {
+      memcpy(buffer_.data, buffer, size);
+    }
+  }
+#endif
+
+
+  void MemoryBuffer::Clear()
+  {
+    if (buffer_.data != NULL)
+    {
+      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &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::Swap(MemoryBuffer& other)
+  {
+    std::swap(buffer_.data, other.buffer_.data);
+    std::swap(buffer_.size, other.buffer_.size);
+  }
+
+
+  OrthancPluginMemoryBuffer MemoryBuffer::Release()
+  {
+    OrthancPluginMemoryBuffer result = buffer_;
+
+    buffer_.data = NULL;
+    buffer_.size = 0;
+
+    return result;
+  }
+
+
+  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)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
+
+    Json::Reader reader;
+    if (!reader.parse(tmp, tmp + buffer_.size, target))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+  }
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                const std::map<std::string, std::string>& httpHeaders,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    std::vector<const char*> headersKeys;
+    std::vector<const char*> headersValues;
+    
+    for (std::map<std::string, std::string>::const_iterator
+           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
+    {
+      headersKeys.push_back(it->first.c_str());
+      headersValues.push_back(it->second.c_str());
+    }
+
+    return CheckHttp(OrthancPluginRestApiGet2(
+                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
+                       (headersKeys.empty() ? NULL : &headersKeys[0]),
+                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
+  }
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const void* body,
+                                 size_t bodySize,
+                                 bool applyPlugins)
+  {
+    Clear();
+    
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const void* body,
+                                size_t bodySize,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const Json::Value& body,
+                                bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(uri, writer.write(body), applyPlugins);
+  }
+
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
+  }
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 const OrthancImage& pixelData,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
+  }
+
+
+  void MemoryBuffer::ReadFile(const std::string& path)
+  {
+    Clear();
+    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+  {
+    Clear();
+    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
+  }
+
+
+  void OrthancString::Assign(char* str)
+  {
+    Clear();
+
+    if (str != NULL)
+    {
+      str_ = str;
+    }
+  }
+
+
+  void OrthancString::Clear()
+  {
+    if (str_ != NULL)
+    {
+      OrthancPluginFreeString(GetGlobalContext(), 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)
+    {
+      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Reader reader;
+    if (!reader.parse(str_, target))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void MemoryBuffer::DicomToJson(Json::Value& target,
+                                 OrthancPluginDicomToJsonFormat format,
+                                 OrthancPluginDicomToJsonFlags flags,
+                                 uint32_t maxStringLength)
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginDicomBufferToJson
+               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
+    str.ToJson(target);
+  }
+
+
+  bool MemoryBuffer::HttpGet(const std::string& url,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPost(const std::string& url,
+                              const std::string& body,
+                              const std::string& username,
+                              const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
+                                           body.c_str(), body.size(),
+                                           username.empty() ? NULL : username.c_str(),
+                                           password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPut(const std::string& url,
+                             const std::string& body,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
+                                          body.empty() ? NULL : body.c_str(),
+                                          body.size(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
+  {
+    Clear();
+    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
+  }
+
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password)
+  {
+    OrthancPluginErrorCode error = OrthancPluginHttpDelete
+      (GetGlobalContext(), url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void LogError(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogError(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogWarning(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogInfo(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void OrthancConfiguration::LoadConfiguration()
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
+
+    if (str.GetContent() == NULL)
+    {
+      LogError("Cannot access the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    str.ToJson(configuration_);
+
+    if (configuration_.type() != Json::objectValue)
+    {
+      LogError("Unable to read the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+    
+
+  OrthancConfiguration::OrthancConfiguration()
+  {
+    LoadConfiguration();
+  }
+
+
+  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
+  {
+    if (loadConfiguration)
+    {
+      LoadConfiguration();
+    }
+    else
+    {
+      configuration_ = Json::objectValue;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetPath(const std::string& key) const
+  {
+    if (path_.empty())
+    {
+      return key;
+    }
+    else
+    {
+      return path_ + "." + key;
+    }
+  }
+
+
+  bool OrthancConfiguration::IsSection(const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    return (configuration_.isMember(key) &&
+            configuration_[key].type() == Json::objectValue);
+  }
+
+
+  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
+                                        const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.path_ = GetPath(key);
+
+    if (!configuration_.isMember(key))
+    {
+      target.configuration_ = Json::objectValue;
+    }
+    else
+    {
+      if (configuration_[key].type() != Json::objectValue)
+      {
+        LogError("The configuration section \"" + target.path_ +
+                 "\" is not an associative array as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(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)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                        const std::string& key) const
+  {
+    int tmp;
+    if (!LookupIntegerValue(tmp, key))
+    {
+      return false;
+    }
+
+    if (tmp < 0)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a positive integer as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a Boolean as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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 = static_cast<float>(configuration_[key].asInt());
+        return true;
+
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
+
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
+                                                 const std::string& key,
+                                                 bool allowSingleString) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::arrayValue:
+      {
+        bool ok = true;
+
+        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+        {
+          if (configuration_[key][i].type() == Json::stringValue)
+          {
+            target.push_back(configuration_[key][i].asString());
+          }
+          else
+          {
+            ok = false;
+          }
+        }
+
+        if (ok)
+        {
+          return true;
+        }
+
+        break;
+      }
+
+      case Json::stringValue:
+        if (allowSingleString)
+        {
+          target.push_back(configuration_[key].asString());
+          return true;
+        }
+
+        break;
+
+      default:
+        break;
+    }
+
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a list of strings as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+
+  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
+                                                const std::string& key,
+                                                bool allowSingleString) const
+  {
+    std::list<std::string> lst;
+
+    if (LookupListOfStrings(lst, key, allowSingleString))
+    {
+      target.clear();
+
+      for (std::list<std::string>::const_iterator
+             it = lst.begin(); it != lst.end(); ++it)
+      {
+        target.insert(*it);
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  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 OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
+                                           const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return;
+    }
+
+    if (configuration_[key].type() != Json::objectValue)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    Json::Value::Members members = configuration_[key].getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& value = configuration_[key][members[i]];
+
+      if (value.type() == Json::stringValue)
+      {
+        target[members[i]] = value.asString();
+      }
+      else
+      {
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not a dictionary mapping strings to strings");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+      }
+    }
+  }
+
+
+  void OrthancImage::Clear()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(GetGlobalContext(), image_);
+      image_ = NULL;
+    }
+  }
+
+
+  void OrthancImage::CheckImageAvailable() const
+  {
+    if (image_ == NULL)
+    {
+      LogError("Trying to access a NULL image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage() :
+    image_(NULL)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
+    image_(image)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height)
+  {
+    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height,
+                             uint32_t                  pitch,
+                             void*                     buffer)
+  {
+    image_ = OrthancPluginCreateImageAccessor
+      (GetGlobalContext(), format, width, height, pitch, buffer);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image accessor");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+  void OrthancImage::UncompressPngImage(const void* data,
+                                        size_t size)
+  {
+    Clear();
+
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a PNG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::UncompressJpegImage(const void* data,
+                                         size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a JPEG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::DecodeDicomImage(const void* data,
+                                      size_t size,
+                                      unsigned int frame)
+  {
+    Clear();
+    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a DICOM image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetWidth() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetHeight() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetPitch() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
+  }
+
+
+  void* OrthancImage::GetBuffer() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
+  }
+
+
+  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                       uint8_t quality) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
+                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+  }
+
+
+  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                     uint8_t quality) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
+                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+  }
+
+
+  OrthancPluginImage* OrthancImage::Release()
+  {
+    CheckImageAvailable();
+    OrthancPluginImage* tmp = image_;
+    image_ = NULL;
+    return tmp;
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
+    matcher_(NULL),
+    worklist_(worklist)
+  {
+    if (worklist_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void FindMatcher::SetupDicom(const void*  query,
+                               uint32_t     size)
+  {
+    worklist_ = NULL;
+
+    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
+    if (matcher_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  FindMatcher::~FindMatcher()
+  {
+    // The "worklist_" field
+
+    if (matcher_ != NULL)
+    {
+      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
+    }
+  }
+
+
+
+  bool FindMatcher::IsMatch(const void*  dicom,
+                            uint32_t     size) const
+  {
+    int32_t result;
+
+    if (matcher_ != NULL)
+    {
+      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
+    }
+    else if (worklist_ != NULL)
+    {
+      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (result == 0)
+    {
+      return false;
+    }
+    else if (result == 1)
+    {
+      return true;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output
+    )
+  {
+    Json::StyledWriter writer;
+    std::string bodyString = writer.write(value);
+
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
+  }
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output
+    )
+  {
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
+  }
+
+  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
+  {
+    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
+  }
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
+  {
+    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        result.assign(answer.GetData(), answer.GetSize());
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(result, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(result, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins)
+  {
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision)
+  {
+    LogError("Your version of the Orthanc core (" +
+             std::string(GetGlobalContext()->orthancVersion) +
+             ") is too old to run this plugin (version " +
+             boost::lexical_cast<std::string>(major) + "." +
+             boost::lexical_cast<std::string>(minor) + "." +
+             boost::lexical_cast<std::string>(revision) +
+             " is required)");
+  }
+
+
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (!HasGlobalContext())
+    {
+      LogError("Bad Orthanc context in the plugin");
+      return false;
+    }
+
+    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
+    {
+      // Assume compatibility with the mainline
+      return true;
+    }
+
+    // Parse the version of the Orthanc core
+    int aa, bb, cc;
+    if (
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
+    {
+      return false;
+    }
+
+    unsigned int a = static_cast<unsigned int>(aa);
+    unsigned int b = static_cast<unsigned int>(bb);
+    unsigned int c = static_cast<unsigned int>(cc);
+
+    // Check the major version number
+
+    if (a > major)
+    {
+      return true;
+    }
+
+    if (a < major)
+    {
+      return false;
+    }
+
+
+    // Check the minor version number
+    assert(a == major);
+
+    if (b > minor)
+    {
+      return true;
+    }
+
+    if (b < minor)
+    {
+      return false;
+    }
+
+    // Check the patch level version number
+    assert(a == major && b == minor);
+
+    if (c >= revision)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path)
+  {
+    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
+
+    if (mime == NULL)
+    {
+      // Should never happen, just for safety
+      return "application/octet-stream";
+    }
+    else
+    {
+      return mime;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
+  {
+    size_t index;
+    if (LookupName(index, name))
+    {
+      return index;
+    }
+    else
+    {
+      LogError("Inexistent peer: " + name);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
+    }
+  }
+
+
+  OrthancPeers::OrthancPeers() :
+    peers_(NULL),
+    timeout_(0)
+  {
+    peers_ = OrthancPluginGetPeers(GetGlobalContext());
+
+    if (peers_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+
+    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
+
+    for (uint32_t i = 0; i < count; i++)
+    {
+      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
+      if (name == NULL)
+      {
+        OrthancPluginFreePeers(GetGlobalContext(), peers_);
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+
+      index_[name] = i;
+    }
+  }
+
+
+  OrthancPeers::~OrthancPeers()
+  {
+    if (peers_ != NULL)
+    {
+      OrthancPluginFreePeers(GetGlobalContext(), peers_);
+    }
+  }
+
+
+  bool OrthancPeers::LookupName(size_t& target,
+                                const std::string& name) const
+  {
+    Index::const_iterator found = index_.find(name);
+
+    if (found == index_.end())
+    {
+      return false;
+    }
+    else
+    {
+      target = found->second;
+      return true;
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerName(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
+  {
+    return GetPeerUrl(GetPeerIndex(name));
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        size_t index,
+                                        const std::string& key) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
+      if (s == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        value.assign(s);
+        return true;
+      }
+    }
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        const std::string& peer,
+                                        const std::string& key) const
+  {
+    return LookupUserProperty(value, GetPeerIndex(peer), key);
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoGet(target, index, uri));
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, index, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, name, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPost(target, index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, index, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, name, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(size_t index,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(const std::string& name,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPut(index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoDelete(size_t index,
+                              const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoDelete(const std::string& name,
+                              const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoDelete(index, uri));
+  }
+#endif
+
+
+
+
+
+  /******************************************************************
+   ** JOBS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  void OrthancJob::CallbackFinalize(void* job)
+  {
+    if (job != NULL)
+    {
+      delete reinterpret_cast<OrthancJob*>(job);
+    }
+  }
+
+
+  float OrthancJob::CallbackGetProgress(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->progress_;
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetContent(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetSerialized(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
+
+      if (tmp.hasSerialized_)
+      {
+        return tmp.serialized_.c_str();
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->Step();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+    catch (...)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
+                                                  OrthancPluginJobStopReason reason)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Reset();
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  void OrthancJob::ClearContent()
+  {
+    Json::Value empty = Json::objectValue;
+    UpdateContent(empty);
+  }
+
+
+  void OrthancJob::UpdateContent(const Json::Value& content)
+  {
+    if (content.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      Json::FastWriter writer;
+      content_ = writer.write(content);
+    }
+  }
+
+
+  void OrthancJob::ClearSerialized()
+  {
+    hasSerialized_ = false;
+    serialized_.clear();
+  }
+
+
+  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
+  {
+    if (serialized.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      Json::FastWriter writer;
+      serialized_ = writer.write(serialized);
+      hasSerialized_ = true;
+    }
+  }
+
+
+  void OrthancJob::UpdateProgress(float progress)
+  {
+    if (progress < 0 ||
+        progress > 1)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    progress_ = progress;
+  }
+
+
+  OrthancJob::OrthancJob(const std::string& jobType) :
+    jobType_(jobType),
+    progress_(0)
+  {
+    ClearContent();
+    ClearSerialized();
+  }
+
+
+  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
+      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+      CallbackStep, CallbackStop, CallbackReset);
+
+    if (orthanc == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      return orthanc;
+    }
+  }
+
+
+  std::string OrthancJob::Submit(OrthancJob* job,
+                                 int priority)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = Create(job);
+
+    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
+
+    if (id == NULL)
+    {
+      LogError("Plugin cannot submit job");
+      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      std::string tmp(id);
+      tmp.assign(id);
+      OrthancPluginFreeString(GetGlobalContext(), id);
+
+      return tmp;
+    }
+  }
+
+
+  void OrthancJob::SubmitAndWait(Json::Value& result,
+                                 OrthancJob* job /* takes ownership */,
+                                 int priority)
+  {
+    std::string id = Submit(job, priority);
+
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+      Json::Value status;
+      if (!RestApiGet(status, "/jobs/" + id, false) ||
+          !status.isMember("State") ||
+          status["State"].type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
+      }
+
+      const std::string state = status["State"].asString();
+      if (state == "Success")
+      {
+        if (status.isMember("Content"))
+        {
+          result = status["Content"];
+        }
+        else
+        {
+          result = Json::objectValue;
+        }
+
+        return;
+      }
+      else if (state == "Running")
+      {
+        continue;
+      }
+      else if (!status.isMember("ErrorCode") ||
+               status["ErrorCode"].type() != Json::intValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
+      }
+      else
+      {
+        if (!status.isMember("ErrorDescription") ||
+            status["ErrorDescription"].type() != Json::stringValue)
+        {
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
+        }
+        else
+        {
+#if HAS_ORTHANC_EXCEPTION == 1
+          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
+                                          status["ErrorDescription"].asString());
+#else
+          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
+#endif
+        }
+      }
+    }
+  }
+
+
+  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                         const Json::Value& body,
+                                         OrthancJob* job)
+  {
+    static const char* KEY_SYNCHRONOUS = "Synchronous";
+    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
+    static const char* KEY_PRIORITY = "Priority";
+
+    boost::movelib::unique_ptr<OrthancJob> protection(job);
+  
+    if (body.type() != Json::objectValue)
+    {
+#if HAS_ORTHANC_EXCEPTION == 1
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Expected a JSON object in the body");
+#else
+      LogError("Expected a JSON object in the body");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+    }
+
+    bool synchronous = true;
+  
+    if (body.isMember(KEY_SYNCHRONOUS))
+    {
+      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = body[KEY_SYNCHRONOUS].asBool();
+      }
+    }
+
+    if (body.isMember(KEY_ASYNCHRONOUS))
+    {
+      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
+      }
+    }
+
+    int priority = 0;
+
+    if (body.isMember(KEY_PRIORITY))
+    {
+      if (body[KEY_PRIORITY].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_PRIORITY) +
+                                        "\" must be an integer");
+#else
+        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        priority = !body[KEY_PRIORITY].asInt();
+      }
+    }
+  
+    Json::Value result;
+
+    if (synchronous)
+    {
+      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
+    }
+    else
+    {
+      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
+
+      result = Json::objectValue;
+      result["ID"] = id;
+      result["Path"] = "/jobs/" + id;
+    }
+
+    std::string s = result.toStyledString();
+    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
+                              s.size(), "application/json");
+  }
+
+#endif
+
+
+
+
+  /******************************************************************
+   ** METRICS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  MetricsTimer::MetricsTimer(const char* name) :
+    name_(name)
+  {
+    start_ = boost::posix_time::microsec_clock::universal_time();
+  }
+  
+  MetricsTimer::~MetricsTimer()
+  {
+    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
+    const boost::posix_time::time_duration diff = stop - start_;
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
+                                 OrthancPluginMetricsType_Timer);
+  }
+#endif
+
+
+
+
+  /******************************************************************
+   ** HTTP CLIENT
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient::RequestBodyWrapper : public boost::noncopyable
+  {
+  private:
+    static RequestBodyWrapper& GetObject(void* body)
+    {
+      assert(body != NULL);
+      return *reinterpret_cast<RequestBodyWrapper*>(body);
+    }
+
+    IRequestBody&  body_;
+    bool           done_;
+    std::string    chunk_;
+
+  public:
+    RequestBodyWrapper(IRequestBody& body) :
+      body_(body),
+      done_(false)
+    {
+    }      
+
+    static uint8_t IsDone(void* body)
+    {
+      return GetObject(body).done_;
+    }
+    
+    static const void* GetChunkData(void* body)
+    {
+      return GetObject(body).chunk_.c_str();
+    }
+    
+    static uint32_t GetChunkSize(void* body)
+    {
+      return static_cast<uint32_t>(GetObject(body).chunk_.size());
+    }
+
+    static OrthancPluginErrorCode Next(void* body)
+    {
+      RequestBodyWrapper& that = GetObject(body);
+        
+      if (that.done_)
+      {
+        return OrthancPluginErrorCode_BadSequenceOfCalls;
+      }
+      else
+      {
+        try
+        {
+          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
+          return OrthancPluginErrorCode_Success;
+        }
+        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+        {
+          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+        }
+        catch (...)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+      }
+    }    
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
+                                                        const char* key,
+                                                        const char* value)
+  {
+    assert(answer != NULL && key != NULL && value != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
+                                                       const void* data,
+                                                       uint32_t size)
+  {
+    assert(answer != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+  HttpClient::HttpClient() :
+    httpStatus_(0),
+    method_(OrthancPluginHttpMethod_Get),
+    timeout_(0),
+    pkcs11_(false),
+    chunkedBody_(NULL),
+    allowChunkedTransfers_(true)
+  {
+  }
+
+
+  void HttpClient::AddHeaders(const HttpHeaders& headers)
+  {
+    for (HttpHeaders::const_iterator it = headers.begin();
+         it != headers.end(); ++it)
+    {
+      headers_[it->first] = it->second;
+    }
+  }
+
+  
+  void HttpClient::SetCredentials(const std::string& username,
+                                  const std::string& password)
+  {
+    username_ = username;
+    password_ = password;
+  }
+
+  
+  void HttpClient::ClearCredentials()
+  {
+    username_.empty();
+    password_.empty();
+  }
+
+
+  void HttpClient::SetCertificate(const std::string& certificateFile,
+                                  const std::string& keyFile,
+                                  const std::string& keyPassword)
+  {
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = keyFile;
+    certificateKeyPassword_ = keyPassword;
+  }
+
+  
+  void HttpClient::ClearCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+  void HttpClient::ClearBody()
+  {
+    fullBody_.clear();
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SwapBody(std::string& body)
+  {
+    fullBody_.swap(body);
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(const std::string& body)
+  {
+    fullBody_ = body;
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(IRequestBody& body)
+  {
+    fullBody_.clear();
+    chunkedBody_ = &body;
+  }
+
+
+  namespace
+  {
+    class HeadersWrapper : public boost::noncopyable
+    {
+    private:
+      std::vector<const char*>  headersKeys_;
+      std::vector<const char*>  headersValues_;
+
+    public:
+      HeadersWrapper(const HttpClient::HttpHeaders& headers)
+      {
+        headersKeys_.reserve(headers.size());
+        headersValues_.reserve(headers.size());
+
+        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+        {
+          headersKeys_.push_back(it->first.c_str());
+          headersValues_.push_back(it->second.c_str());
+        }
+      }
+
+      void AddStaticString(const char* key,
+                           const char* value)
+      {
+        headersKeys_.push_back(key);
+        headersValues_.push_back(value);
+      }
+
+      uint32_t GetCount() const
+      {
+        return headersKeys_.size();
+      }
+
+      const char* const* GetKeys() const
+      {
+        return headersKeys_.empty() ? NULL : &headersKeys_[0];
+      }
+
+      const char* const* GetValues() const
+      {
+        return headersValues_.empty() ? NULL : &headersValues_[0];
+      }
+    };
+
+
+    class MemoryRequestBody : public HttpClient::IRequestBody
+    {
+    private:
+      std::string  body_;
+      bool         done_;
+
+    public:
+      MemoryRequestBody(const std::string& body) :
+        body_(body),
+        done_(false)
+      {
+        if (body_.empty())
+        {
+          done_ = true;
+        }
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk)
+      {
+        if (done_)
+        {
+          return false;
+        }
+        else
+        {
+          chunk.swap(body_);
+          done_ = true;
+          return true;
+        }
+      }
+    };
+
+
+    // This class mimics Orthanc::ChunkedBuffer
+    class ChunkedBuffer : public boost::noncopyable
+    {
+    private:
+      typedef std::list<std::string*>  Content;
+
+      Content  content_;
+      size_t   size_;
+
+    public:
+      ChunkedBuffer() :
+        size_(0)
+      {
+      }
+
+      ~ChunkedBuffer()
+      {
+        Clear();
+      }
+
+      void Clear()
+      {
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          delete *it;
+        }
+
+        content_.clear();
+      }
+
+      void Flatten(std::string& target) const
+      {
+        target.resize(size_);
+
+        size_t pos = 0;
+
+        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          size_t s = (*it)->size();
+
+          if (s != 0)
+          {
+            memcpy(&target[pos], (*it)->c_str(), s);
+            pos += s;
+          }
+        }
+
+        assert(size_ == 0 ||
+               pos == target.size());
+      }
+
+      void AddChunk(const void* data,
+                    size_t size)
+      {
+        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
+        size_ += size;
+      }
+
+      void AddChunk(const std::string& chunk)
+      {
+        content_.push_back(new std::string(chunk));
+        size_ += chunk.size();
+      }
+    };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    class MemoryAnswer : public HttpClient::IAnswer
+    {
+    private:
+      HttpClient::HttpHeaders  headers_;
+      ChunkedBuffer            body_;
+
+    public:
+      const HttpClient::HttpHeaders& GetHeaders() const
+      {
+        return headers_;
+      }
+
+      const ChunkedBuffer& GetBody() const
+      {
+        return body_;
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value)
+      {
+        headers_[key] = value;
+      }
+
+      virtual void AddChunk(const void* data,
+                            size_t size)
+      {
+        body_.AddChunk(data, size);
+      }
+    };
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
+                                     IAnswer& answer,
+                                     IRequestBody& body) const
+  {
+    HeadersWrapper h(headers_);
+
+    if (method_ == OrthancPluginHttpMethod_Post ||
+        method_ == OrthancPluginHttpMethod_Put)
+    {
+      // Automatically set the "Transfer-Encoding" header if absent
+      bool found = false;
+
+      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        if (boost::iequals(it->first, "Transfer-Encoding"))
+        {
+          found = true;
+          break;
+        }
+      }
+
+      if (!found)
+      {
+        h.AddStaticString("Transfer-Encoding", "chunked");
+      }
+    }
+
+    RequestBodyWrapper request(body);
+        
+    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
+      GetGlobalContext(),
+      &answer,
+      AnswerAddChunkCallback,
+      AnswerAddHeaderCallback,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      h.GetCount(),
+      h.GetKeys(),
+      h.GetValues(),
+      &request,
+      RequestBodyWrapper::IsDone,
+      RequestBodyWrapper::GetChunkData,
+      RequestBodyWrapper::GetChunkSize,
+      RequestBodyWrapper::Next,
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+#endif    
+
+
+  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
+                                        HttpHeaders& answerHeaders,
+                                        std::string& answerBody,
+                                        const std::string& body) const
+  {
+    HeadersWrapper headers(headers_);
+
+    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
+
+    OrthancPluginErrorCode error = OrthancPluginHttpClient(
+      GetGlobalContext(),
+      *answerBodyBuffer,
+      *answerHeadersBuffer,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      headers.GetCount(),
+      headers.GetKeys(),
+      headers.GetValues(),
+      body.empty() ? NULL : body.c_str(),
+      body.size(),
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+
+    Json::Value v;
+    answerHeadersBuffer.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    answerHeaders.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        answerHeaders[members[i]] = h.asString();
+      }
+    }
+
+    answerBodyBuffer.ToString(answerBody);
+  }
+
+
+  void HttpClient::Execute(IAnswer& answer)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      if (chunkedBody_ != NULL)
+      {
+        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
+      }
+      else
+      {
+        MemoryRequestBody wrapper(fullBody_);
+        ExecuteWithStream(httpStatus_, answer, wrapper);
+      }
+
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the answer body are sent at once)
+
+    HttpHeaders answerHeaders;
+    std::string answerBody;
+    Execute(answerHeaders, answerBody);
+
+    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+         it != answerHeaders.end(); ++it)
+    {
+      answer.AddHeader(it->first, it->second);      
+    }
+
+    if (!answerBody.empty())
+    {
+      answer.AddChunk(answerBody.c_str(), answerBody.size());
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           std::string& answerBody /* out */)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      MemoryAnswer answer;
+      Execute(answer);
+      answerHeaders = answer.GetHeaders();
+      answer.GetBody().Flatten(answerBody);
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the request body are sent at once)
+
+    if (chunkedBody_ != NULL)
+    {
+      ChunkedBuffer buffer;
+      
+      std::string chunk;
+      while (chunkedBody_->ReadNextChunk(chunk))
+      {
+        buffer.AddChunk(chunk);
+      }
+
+      std::string body;
+      buffer.Flatten(body);
+
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
+    }
+    else
+    {
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           Json::Value& answerBody /* out */)
+  {
+    std::string body;
+    Execute(answerHeaders, body);
+    
+    Json::Reader reader;
+    if (!reader.parse(body, answerBody))
+    {
+      LogError("Cannot convert HTTP answer body to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void HttpClient::Execute()
+  {
+    HttpHeaders answerHeaders;
+    std::string body;
+    Execute(answerHeaders, body);
+  }
+
+#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
+
+
+
+
+
+  /******************************************************************
+   ** CHUNKED HTTP SERVER
+   ******************************************************************/
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request)
+    {
+    }
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request)
+    {
+      return NULL;
+    }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader)
+    {
+      if (reader != NULL)
+      {
+        delete reinterpret_cast<IChunkedRequestReader*>(reader);
+      }
+    }
+
+#else
+    
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback         GetHandler,
+                                                    ChunkedRestCallback  PostHandler,
+                                                    RestCallback         DeleteHandler,
+                                                    ChunkedRestCallback  PutHandler)
+    {
+      try
+      {
+        std::string allowed;
+
+        if (GetHandler != Internals::NullRestCallback)
+        {
+          allowed += "GET";
+        }
+
+        if (PostHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "POST";
+        }
+
+        if (DeleteHandler != Internals::NullRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "DELETE";
+        }
+
+        if (PutHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "PUT";
+        }
+      
+        switch (request->method)
+        {
+          case OrthancPluginHttpMethod_Get:
+            if (GetHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              GetHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Post:
+            if (PostHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Delete:
+            if (DeleteHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              DeleteHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Put:
+            if (PutHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          default:
+            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* rawHandler,
+    const char* sopClassUid,
+    const char* sopInstanceUid)
+  {
+    assert(target != NULL &&
+           rawHandler != NULL);
+      
+    try
+    {
+      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+      *target = handler.Lookup(sopClassUid, sopInstanceUid);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
+  {
+    assert(rawHandler != NULL);
+    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#else
+  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance::DicomInstance(const void* buffer,
+                               size_t size) :
+    toFree_(true),
+    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
+  {
+    if (instance_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+  }
+#endif
+
+
+  DicomInstance::~DicomInstance()
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    if (toFree_ &&
+        instance_ != NULL)
+    {
+      OrthancPluginFreeDicomInstance(
+        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
+    }
+#endif
+  }
+
+  
+  std::string DicomInstance::GetRemoteAet() const
+  {
+    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return std::string(s);
+    }
+  }
+
+
+  void DicomInstance::GetJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+  
+
+  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  std::string DicomInstance::GetTransferSyntaxUid() const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
+
+    std::string result;
+    s.ToString(result);
+    return result;
+  }
+#endif
+
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  bool DicomInstance::HasPixelData() const
+  {
+    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
+    if (result < 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  void DicomInstance::GetRawFrame(std::string& target,
+                                  unsigned int frameIndex) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
+      GetGlobalContext(), *buffer, instance_, frameIndex);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
+  {
+    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
+      GetGlobalContext(), instance_, frameIndex);
+
+    if (image == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return new OrthancImage(image);
+    }
+  }
+#endif  
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void DicomInstance::Serialize(std::string& target) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
+      GetGlobalContext(), *buffer, instance_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+  
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance* DicomInstance::Transcode(const void* buffer,
+                                          size_t size,
+                                          const std::string& transferSyntax)
+  {
+    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
+      GetGlobalContext(), buffer, size, transferSyntax.c_str());
+
+    if (instance == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
+      result->toFree_ = true;
+      return result.release();
+    }
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,1228 @@
+/**
+ * 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.
+ * 
+ * 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 "OrthancPluginException.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <boost/noncopyable.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <json/value.h>
+#include <vector>
+#include <list>
+#include <set>
+#include <map>
+
+
+
+/**
+ * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
+ * backward compatibility with Orthanc SDK <= 1.3.0.
+ * 
+ *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
+ *
+ **/
+#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
+
+
+#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
+#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
+  (ORTHANC_VERSION_MAJOR > major ||                                     \
+   (ORTHANC_VERSION_MAJOR == major &&                                   \
+    (ORTHANC_VERSION_MINOR > minor ||                                   \
+     (ORTHANC_VERSION_MINOR == minor &&                                 \
+      ORTHANC_VERSION_REVISION >= revision))))
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
+#  define HAS_ORTHANC_PLUGIN_PEERS  1
+#  define HAS_ORTHANC_PLUGIN_JOB    1
+#else
+#  define HAS_ORTHANC_PLUGIN_PEERS  0
+#  define HAS_ORTHANC_PLUGIN_JOB    0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
+#  define HAS_ORTHANC_PLUGIN_METRICS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_METRICS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
+#else
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
+#endif
+
+
+
+namespace OrthancPlugins
+{
+  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
+                                const char* url,
+                                const OrthancPluginHttpRequest* request);
+
+  void SetGlobalContext(OrthancPluginContext* context);
+
+  bool HasGlobalContext();
+
+  OrthancPluginContext* GetGlobalContext();
+
+  
+  class OrthancImage;
+
+
+  class MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Check(OrthancPluginErrorCode code);
+
+    bool CheckHttp(OrthancPluginErrorCode code);
+
+  public:
+    MemoryBuffer();
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    // This constructor makes a copy of the given buffer in the memory
+    // handled by the Orthanc core
+    MemoryBuffer(const void* buffer,
+                 size_t size);
+#endif
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    OrthancPluginMemoryBuffer* operator*()
+    {
+      return &buffer_;
+    }
+
+    // This transfers ownership from "other" to "this"
+    void Assign(OrthancPluginMemoryBuffer& other);
+
+    void Swap(MemoryBuffer& other);
+
+    OrthancPluginMemoryBuffer Release();
+
+    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;
+    }
+
+    bool IsEmpty() const
+    {
+      return GetSize() == 0 || GetData() == NULL;
+    }
+
+    void Clear();
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+
+    bool RestApiGet(const std::string& uri,
+                    bool applyPlugins);
+
+    bool RestApiGet(const std::string& uri,
+                    const std::map<std::string, std::string>& httpHeaders,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const void* body,
+                     size_t bodySize,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const void* body,
+                    size_t bodySize,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const Json::Value& body,
+                    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);
+    }
+
+    void CreateDicom(const Json::Value& tags,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void CreateDicom(const Json::Value& tags,
+                     const OrthancImage& pixelData,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void ReadFile(const std::string& path);
+
+    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
+
+    void DicomToJson(Json::Value& target,
+                     OrthancPluginDicomToJsonFormat format,
+                     OrthancPluginDicomToJsonFlags flags,
+                     uint32_t maxStringLength);
+
+    bool HttpGet(const std::string& url,
+                 const std::string& username,
+                 const std::string& password);
+
+    bool HttpPost(const std::string& url,
+                  const std::string& body,
+                  const std::string& username,
+                  const std::string& password);
+
+    bool HttpPut(const std::string& url,
+                 const std::string& body,
+                 const std::string& username,
+                 const std::string& password);
+
+    void GetDicomInstance(const std::string& instanceId);
+  };
+
+
+  class OrthancString : public boost::noncopyable
+  {
+  private:
+    char*   str_;
+
+    void Clear();
+
+  public:
+    OrthancString() :
+      str_(NULL)
+    {
+    }
+
+    ~OrthancString()
+    {
+      Clear();
+    }
+
+    // This transfers ownership, warning: The string must have been
+    // allocated by the Orthanc core
+    void Assign(char* str);
+
+    const char* GetContent() const
+    {
+      return str_;
+    }
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+  };
+
+
+  class OrthancConfiguration : public boost::noncopyable
+  {
+  private:
+    Json::Value  configuration_;  // Necessarily a Json::objectValue
+    std::string  path_;
+
+    std::string GetPath(const std::string& key) const;
+
+    void LoadConfiguration();
+    
+  public:
+    OrthancConfiguration();
+
+    explicit OrthancConfiguration(bool load);
+
+    const Json::Value& GetJson() const
+    {
+      return configuration_;
+    }
+
+    bool IsSection(const std::string& key) const;
+
+    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;
+
+    bool LookupListOfStrings(std::list<std::string>& target,
+                             const std::string& key,
+                             bool allowSingleString) const;
+
+    bool LookupSetOfStrings(std::set<std::string>& target,
+                            const std::string& key,
+                            bool allowSingleString) 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;
+
+    void GetDictionary(std::map<std::string, std::string>& target,
+                       const std::string& key) const;
+  };
+
+  class OrthancImage : public boost::noncopyable
+  {
+  private:
+    OrthancPluginImage*    image_;
+
+    void Clear();
+
+    void CheckImageAvailable() const;
+
+  public:
+    OrthancImage();
+
+    explicit OrthancImage(OrthancPluginImage* image);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height,
+                 uint32_t                  pitch,
+                 void*                     buffer);
+
+    ~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() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    unsigned int GetPitch() const;
+    
+    void* GetBuffer() const;
+
+    const OrthancPluginImage* GetObject() const
+    {
+      return image_;
+    }
+
+    void CompressPngImage(MemoryBuffer& target) const;
+
+    void CompressJpegImage(MemoryBuffer& target,
+                           uint8_t quality) const;
+
+    void AnswerPngImage(OrthancPluginRestOutput* output) const;
+
+    void AnswerJpegImage(OrthancPluginRestOutput* output,
+                         uint8_t quality) const;
+    
+    void* GetWriteableBuffer();
+
+    OrthancPluginImage* Release();
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  class FindMatcher : public boost::noncopyable
+  {
+  private:
+    OrthancPluginFindMatcher*          matcher_;
+    const OrthancPluginWorklistQuery*  worklist_;
+
+    void SetupDicom(const void*            query,
+                    uint32_t               size);
+
+  public:
+    explicit FindMatcher(const OrthancPluginWorklistQuery*  worklist);
+
+    FindMatcher(const void*  query,
+                uint32_t     size)
+    {
+      SetupDicom(query, size);
+    }
+
+    explicit FindMatcher(const MemoryBuffer&  dicom)
+    {
+      SetupDicom(dicom.GetData(), dicom.GetSize());
+    }
+
+    ~FindMatcher();
+
+    bool IsMatch(const void*  dicom,
+                 uint32_t     size) const;
+
+    bool IsMatch(const MemoryBuffer& dicom) const
+    {
+      return IsMatch(dicom.GetData(), dicom.GetSize());
+    }
+  };
+#endif
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins);
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins);
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const std::string& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
+                       body.size(), applyPlugins);
+  }
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const MemoryBuffer& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.GetData(),
+                       body.GetSize(), applyPlugins);
+  }
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins);
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins);
+
+  inline bool RestApiPut(Json::Value& result,
+                         const std::string& uri,
+                         const std::string& body,
+                         bool applyPlugins)
+  {
+    return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
+                      body.size(), applyPlugins);
+  }
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins);
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password);
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output);
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output);
+
+  void AnswerHttpError(uint16_t httpError,
+                       OrthancPluginRestOutput* output);
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path);
+#endif
+
+  void LogError(const std::string& message);
+
+  void LogWarning(const std::string& message);
+
+  void LogInfo(const std::string& message);
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision);
+  
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision);
+
+
+  namespace Internals
+  {
+    template <RestCallback Callback>
+    static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                          const char* url,
+                                          const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        Callback(output, url, request);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+  }
+
+  
+  template <RestCallback Callback>
+  void RegisterRestCallback(const std::string& uri,
+                            bool isThreadSafe)
+  {
+    if (isThreadSafe)
+    {
+      OrthancPluginRegisterRestCallbackNoLock
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+    else
+    {
+      OrthancPluginRegisterRestCallback
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  class OrthancPeers : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, uint32_t>   Index;
+
+    OrthancPluginPeers   *peers_;
+    Index                 index_;
+    uint32_t              timeout_;
+
+    size_t GetPeerIndex(const std::string& name) const;
+
+  public:
+    OrthancPeers();
+
+    ~OrthancPeers();
+
+    uint32_t GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void SetTimeout(uint32_t timeout)
+    {
+      timeout_ = timeout;
+    }
+
+    bool LookupName(size_t& target,
+                    const std::string& name) const;
+
+    std::string GetPeerName(size_t index) const;
+
+    std::string GetPeerUrl(size_t index) const;
+
+    std::string GetPeerUrl(const std::string& name) const;
+
+    size_t GetPeersCount() const
+    {
+      return index_.size();
+    }
+
+    bool LookupUserProperty(std::string& value,
+                            size_t index,
+                            const std::string& key) const;
+
+    bool LookupUserProperty(std::string& value,
+                            const std::string& peer,
+                            const std::string& key) const;
+
+    bool DoGet(MemoryBuffer& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(MemoryBuffer& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoPost(MemoryBuffer& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(MemoryBuffer& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPut(size_t index,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoPut(const std::string& name,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoDelete(size_t index,
+                  const std::string& uri) const;
+
+    bool DoDelete(const std::string& name,
+                  const std::string& uri) const;
+  };
+#endif
+
+
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  class OrthancJob : public boost::noncopyable
+  {
+  private:
+    std::string   jobType_;
+    std::string   content_;
+    bool          hasSerialized_;
+    std::string   serialized_;
+    float         progress_;
+
+    static void CallbackFinalize(void* job);
+
+    static float CallbackGetProgress(void* job);
+
+    static const char* CallbackGetContent(void* job);
+
+    static const char* CallbackGetSerialized(void* job);
+
+    static OrthancPluginJobStepStatus CallbackStep(void* job);
+
+    static OrthancPluginErrorCode CallbackStop(void* job,
+                                               OrthancPluginJobStopReason reason);
+
+    static OrthancPluginErrorCode CallbackReset(void* job);
+
+  protected:
+    void ClearContent();
+
+    void UpdateContent(const Json::Value& content);
+
+    void ClearSerialized();
+
+    void UpdateSerialized(const Json::Value& serialized);
+
+    void UpdateProgress(float progress);
+    
+  public:
+    OrthancJob(const std::string& jobType);
+    
+    virtual ~OrthancJob()
+    {
+    }
+
+    virtual OrthancPluginJobStepStatus Step() = 0;
+
+    virtual void Stop(OrthancPluginJobStopReason reason) = 0;
+    
+    virtual void Reset() = 0;
+
+    static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
+
+    static std::string Submit(OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    static void SubmitAndWait(Json::Value& result,
+                              OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    // Submit a job from a POST on the REST API with the same
+    // conventions as in the Orthanc core (according to the
+    // "Synchronous" and "Priority" options)
+    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                      const Json::Value& body,
+                                      OrthancJob* job);
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  inline void SetMetricsValue(char* name,
+                              float value)
+  {
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name,
+                                 value, OrthancPluginMetricsType_Default);
+  }
+
+  class MetricsTimer : public boost::noncopyable
+  {
+  private:
+    std::string               name_;
+    boost::posix_time::ptime  start_;
+
+  public:
+    explicit MetricsTimer(const char* name);
+
+    ~MetricsTimer();
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient : public boost::noncopyable
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class IRequestBody : public boost::noncopyable
+    {
+    public:
+      virtual ~IRequestBody()
+      {
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk) = 0;
+    };
+
+
+    class IAnswer : public boost::noncopyable
+    {
+    public:
+      virtual ~IAnswer()
+      {
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value) = 0;
+
+      virtual void AddChunk(const void* data,
+                            size_t size) = 0;
+    };
+
+
+  private:
+    class RequestBodyWrapper;
+
+    uint16_t                 httpStatus_;
+    OrthancPluginHttpMethod  method_;
+    std::string              url_;
+    HttpHeaders              headers_;
+    std::string              username_;
+    std::string              password_;
+    uint32_t                 timeout_;
+    std::string              certificateFile_;
+    std::string              certificateKeyFile_;
+    std::string              certificateKeyPassword_;
+    bool                     pkcs11_;
+    std::string              fullBody_;
+    IRequestBody*            chunkedBody_;
+    bool                     allowChunkedTransfers_;
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    void ExecuteWithStream(uint16_t& httpStatus,  // out
+                           IAnswer& answer,       // out
+                           IRequestBody& body) const;
+#endif
+
+    void ExecuteWithoutStream(uint16_t& httpStatus,        // out
+                              HttpHeaders& answerHeaders,  // out
+                              std::string& answerBody,     // out
+                              const std::string& body) const;
+    
+  public:
+    HttpClient();
+
+    uint16_t GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    void SetMethod(OrthancPluginHttpMethod method)
+    {
+      method_ = method;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    void SetHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    void AddHeaders(const HttpHeaders& headers);
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password);
+
+    void ClearCredentials();
+
+    void SetTimeout(unsigned int timeout)  // 0 for default timeout
+    {
+      timeout_ = timeout;
+    }
+
+    void SetCertificate(const std::string& certificateFile,
+                        const std::string& keyFile,
+                        const std::string& keyPassword);
+
+    void ClearCertificate();
+
+    void SetPkcs11(bool pkcs11)
+    {
+      pkcs11_ = pkcs11;
+    }
+
+    void ClearBody();
+
+    void SwapBody(std::string& body);
+
+    void SetBody(const std::string& body);
+
+    void SetBody(IRequestBody& body);
+
+    // This function can be used to disable chunked transfers if the
+    // remote server is Orthanc with a version <= 1.5.6.
+    void SetChunkedTransfersAllowed(bool allow)
+    {
+      allowChunkedTransfers_ = allow;
+    }
+
+    bool IsChunkedTransfersAllowed() const
+    {
+      return allowChunkedTransfers_;
+    }
+
+    void Execute(IAnswer& answer);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 std::string& answerBody /* out */);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 Json::Value& answerBody /* out */);
+
+    void Execute();
+  };
+#endif
+
+
+
+  class IChunkedRequestReader : public boost::noncopyable
+  {
+  public:
+    virtual ~IChunkedRequestReader()
+    {
+    }
+
+    virtual void AddChunk(const void* data,
+                          size_t size) = 0;
+
+    virtual void Execute(OrthancPluginRestOutput* output) = 0;
+  };
+
+
+  typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
+                                                         const OrthancPluginHttpRequest* request);
+
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request);
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request);
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+    template <ChunkedRestCallback Callback>
+    static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
+                                                const char* url,
+                                                const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+        else
+        {
+          *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
+          if (*reader == NULL)
+          {
+            return OrthancPluginErrorCode_Plugin;
+          }
+          else
+          {
+            return OrthancPluginErrorCode_Success;
+          }
+        }
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size);
+
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output);
+
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader);
+
+#else  
+
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback GetHandler,
+                                                    ChunkedRestCallback PostHandler,
+                                                    RestCallback DeleteHandler,
+                                                    ChunkedRestCallback PutHandler);
+
+    template<
+      RestCallback         GetHandler,
+      ChunkedRestCallback  PostHandler,
+      RestCallback         DeleteHandler,
+      ChunkedRestCallback  PutHandler
+      >
+    inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                           const char* url,
+                                                           const OrthancPluginHttpRequest* request)
+    {
+      return ChunkedRestCompatibility(output, url, request, GetHandler,
+                                      PostHandler, DeleteHandler, PutHandler);
+    }
+#endif
+  }
+
+
+
+  // NB: We use a templated class instead of a templated function, because
+  // default values are only available in functions since C++11
+  template<
+    RestCallback         GetHandler    = Internals::NullRestCallback,
+    ChunkedRestCallback  PostHandler   = Internals::NullChunkedRestCallback,
+    RestCallback         DeleteHandler = Internals::NullRestCallback,
+    ChunkedRestCallback  PutHandler    = Internals::NullChunkedRestCallback
+    >
+  class ChunkedRestRegistration : public boost::noncopyable
+  {
+  public:
+    static void Apply(const std::string& uri)
+    {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+      OrthancPluginRegisterChunkedRestCallback(
+        GetGlobalContext(), uri.c_str(),
+        GetHandler == Internals::NullRestCallback         ? NULL : Internals::Protect<GetHandler>,
+        PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
+        DeleteHandler == Internals::NullRestCallback      ? NULL : Internals::Protect<DeleteHandler>,
+        PutHandler == Internals::NullChunkedRestCallback  ? NULL : Internals::ChunkedProtect<PutHandler>,
+        Internals::ChunkedRequestReaderAddChunk,
+        Internals::ChunkedRequestReaderExecute,
+        Internals::ChunkedRequestReaderFinalize);
+#else
+      OrthancPluginRegisterRestCallbackNoLock(
+        GetGlobalContext(), uri.c_str(), 
+        Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
+#endif
+    }
+  };
+
+  
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  class IStorageCommitmentScpHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentScpHandler()
+    {
+    }
+    
+    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                               const std::string& sopInstanceUid) = 0;
+    
+    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
+                                         void* rawHandler,
+                                         const char* sopClassUid,
+                                         const char* sopInstanceUid);
+
+    static void Destructor(void* rawHandler);
+  };
+#endif
+
+
+  class DicomInstance : public boost::noncopyable
+  {
+  private:
+    bool toFree_;
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    const OrthancPluginDicomInstance*  instance_;
+#else
+    OrthancPluginDicomInstance*  instance_;
+#endif
+    
+  public:
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    DicomInstance(const OrthancPluginDicomInstance* instance);
+#else
+    DicomInstance(OrthancPluginDicomInstance* instance);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    DicomInstance(const void* buffer,
+                  size_t size);
+#endif
+
+    ~DicomInstance();
+
+    std::string GetRemoteAet() const;
+
+    const void* GetBuffer() const
+    {
+      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
+    }
+
+    size_t GetSize() const
+    {
+      return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
+    }
+
+    void GetJson(Json::Value& target) const;
+
+    void GetSimplifiedJson(Json::Value& target) const;
+
+    OrthancPluginInstanceOrigin GetOrigin() const
+    {
+      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
+    }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    std::string GetTransferSyntaxUid() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    bool HasPixelData() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    unsigned int GetFramesCount() const
+    {
+      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void GetRawFrame(std::string& target,
+                     unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void Serialize(std::string& target) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    static DicomInstance* Transcode(const void* buffer,
+                                    size_t size,
+                                    const std::string& transferSyntax);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-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.
+ * 
+ * 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(HAS_ORTHANC_EXCEPTION)
+#  error The macro HAS_ORTHANC_EXCEPTION must be defined
+#endif
+
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include <OrthancException.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
+#else
+#  include <orthanc/OrthancCPlugin.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
+#endif
+
+
+#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
+
+
+#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
+                                                  
+
+#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
+  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
+  {                                                                 \
+    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
+  }
+
+
+namespace OrthancPlugins
+{
+#if HAS_ORTHANC_EXCEPTION == 0
+  class PluginException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode GetErrorCode() const
+    {
+      return code_;
+    }
+
+    const char* What(OrthancPluginContext* context) const
+    {
+      const char* description = OrthancPluginGetErrorDescription(context, code_);
+      if (description)
+      {
+        return description;
+      }
+      else
+      {
+        return "No description available";
+      }
+    }
+  };
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,31 @@
+# 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.
+# 
+# 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/>.
+
+
+# In Orthanc <= 1.7.1, the instructions below were part of
+# "Compiler.cmake", and were protected by the (now unused) option
+# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map")
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list")
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,12 @@
+# This is a version-script for Orthanc plugins
+
+{
+global:
+  OrthancPluginInitialize;
+  OrthancPluginFinalize;
+  OrthancPluginGetName;
+  OrthancPluginGetVersion;
+
+local:
+  *;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,4740 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup Worklists Worklists
+ * @brief Functions to register and manage worklists.
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
+#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 already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _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 worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       remoteAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, it is up to the plugin to
+   * implement the required locking mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * script.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), 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 char*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const char*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  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);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/BoostExtendedConfiguration.cmake	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,40 @@
+# Stone of Orthanc
+# 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/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
+  set(BOOST_EXTENDED_SOURCES
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp
+    ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp
+    ${BOOST_SOURCES_DIR}/libs/chrono/src/thread_clock.cpp
+    ${BOOST_SOURCES_DIR}/libs/chrono/src/chrono.cpp
+    ${BOOST_SOURCES_DIR}/libs/chrono/src/process_cpu_clocks.cpp
+    #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp
+    )
+  add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB)
+else()
+  link_libraries(boost_program_options)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,111 @@
+cmake_minimum_required(VERSION 2.8.10)
+
+project(OrthancStone)
+
+include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake)
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  set(ORTHANC_BOOST_COMPONENTS program_options)
+
+  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
+  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake)
+  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
+
+else()
+  set(ENABLE_GOOGLE_TEST ON)
+  set(ENABLE_LOCALE ON)  # Necessary for text rendering
+  set(ENABLE_OPENGL ON)  #  <==
+  set(ENABLE_WEB_CLIENT ON)
+endif()
+
+set(ENABLE_DCMTK ON)  # <==
+set(ENABLE_SDL ON)
+
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Utilities.cmake)
+
+if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  # This include must be after "OrthancStoneConfiguration.cmake" to
+  # have "BOOST_SOURCES_DIR" defined
+  include(${CMAKE_SOURCE_DIR}/BoostExtendedConfiguration.cmake)
+endif()
+
+
+DownloadPackage(
+  "a24b8136b8f3bb93f166baf97d9328de"
+  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
+  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
+
+EmbedResources(
+  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut
+  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
+  )
+
+SortFilesInSourceGroups()
+
+add_library(OrthancStone STATIC
+  ${ORTHANC_STONE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  ${BOOST_EXTENDED_SOURCES}
+  )
+
+message(${AUTOGENERATED_SOURCES})
+
+
+
+#############################
+project(RtViewerSdl)
+
+add_executable(RtViewerSdl
+  RtViewer/RtViewerSdl.cpp
+  SdlHelpers.h
+  ../Common/RtViewerApp.cpp
+  ../Common/RtViewerApp.h
+  ../Common/RtViewerView.cpp
+  ../Common/RtViewerView.h
+  ../Common/SampleHelpers.h
+  )
+
+target_link_libraries(RtViewerSdl OrthancStone ${DCMTK_LIBRARIES})
+
+#############################
+project(SdlSimpleViewer)
+
+add_executable(SdlSimpleViewer
+  SdlHelpers.h
+  ../Common/SampleHelpers.h
+  SingleFrameViewer/SdlSimpleViewerApplication.h
+  SingleFrameViewer/SdlSimpleViewer.cpp
+  )
+
+target_link_libraries(SdlSimpleViewer OrthancStone ${DCMTK_LIBRARIES})
+
+#############################
+project(UnitTests)
+
+add_executable(UnitTests
+  ${GOOGLE_TEST_SOURCES}
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/SortedFramesTests.cpp
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
+  )
+
+target_link_libraries(UnitTests OrthancStone)
+
+add_custom_command(
+  TARGET UnitTests
+  POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E copy
+    "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" 
+    "$<TARGET_FILE_DIR:UnitTests>/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json"
+)
+
+target_link_libraries(UnitTests OrthancStone ${DCMTK_LIBRARIES})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/RtViewer/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,60 @@
+cmake_minimum_required(VERSION 2.8.10)
+
+project(RtViewerSdl)
+
+include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  set(ORTHANC_BOOST_COMPONENTS program_options)
+
+  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
+  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake)
+  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
+
+else()
+  set(ENABLE_GOOGLE_TEST ON)
+  set(ENABLE_LOCALE ON)  # Necessary for text rendering
+  set(ENABLE_OPENGL ON)  #  <==
+  set(ENABLE_WEB_CLIENT ON)
+endif()
+
+set(ENABLE_DCMTK ON)  # <==
+set(ENABLE_SDL ON)
+
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../Utilities.cmake)
+
+if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  # This include must be after "OrthancStoneConfiguration.cmake" to
+  # have "BOOST_SOURCES_DIR" defined
+  include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake)
+endif()
+
+DownloadPackage(
+  "a24b8136b8f3bb93f166baf97d9328de"
+  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
+  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
+
+EmbedResources(
+  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut
+  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
+  )
+
+SortFilesInSourceGroups()
+
+add_executable(RtViewerSdl
+  RtViewerSdl.cpp
+  ../SdlHelpers.h
+  ../../Common/RtViewerApp.cpp
+  ../../Common/RtViewerApp.h
+  ../../Common/RtViewerView.cpp
+  ../../Common/RtViewerView.h
+  ../../Common/SampleHelpers.h
+  ${ORTHANC_STONE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  ${BOOST_EXTENDED_SOURCES}
+  )
+
+target_link_libraries(RtViewerSdl ${DCMTK_LIBRARIES})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/RtViewer/CMakeSettings.json	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,34 @@
+{
+  // this file is meant to be used with Visual Studio CMake support
+  // tested with VS 16.5.4+ 
+  "configurations": [
+    {
+      "name": "x64-Debug",
+      "generator": "Ninja",
+      "configurationType": "Debug",
+      "inheritEnvironments": [ "msvc_x64_x64" ],
+      "buildRoot": "${projectDir}/../../../../out/build-stone-sdl-RtViewer-ninja-msvc16-x64-Debug",
+      "installRoot": "${projectDir}/../../../../out/install-stone-sdl-RtViewer-ninja-msvc16-x64-Debug",
+      "cmakeCommandArgs": "",
+      "buildCommandArgs": "-v",
+      "ctestCommandArgs": "",
+      "variables": [
+        {
+          "name": "ALLOW_DOWNLOADS",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "MSVC_MULTIPLE_PROCESSES",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "STATIC_BUILD",
+          "value": "True",
+          "type": "BOOL"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,461 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../Common/RtViewerApp.h"
+#include "../../Common/RtViewerView.h"
+#include "../SdlHelpers.h"
+
+#include <EmbeddedResources.h>
+
+// Stone of Orthanc includes
+#include "../../../Sources/Loaders/GenericLoadersContext.h"
+#include "../../../Sources/OpenGL/OpenGLIncludes.h"
+#include "../../../Sources/OpenGL/SdlOpenGLContext.h"
+#include "../../../Sources/StoneException.h"
+#include "../../../Sources/StoneInitialization.h"
+
+// Orthanc (a.o. for screenshot capture)
+#include <Compatibility.h>  // For std::unique_ptr<>
+#include <Images/Image.h>
+#include <Images/ImageProcessing.h>
+#include <Images/PngWriter.h>
+
+
+#include <boost/program_options.hpp>
+#include <boost/shared_ptr.hpp>
+
+// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions
+
+#include <SDL.h>
+
+#include <string>
+
+
+#if !defined(__APPLE__)
+/**
+ * OpenGL: "OS X does not seem to support debug output functionality
+ * (as gathered online)."
+ * https://learnopengl.com/In-Practice/Debugging
+ **/
+static void GLAPIENTRY
+OpenGLMessageCallback(GLenum source,
+                      GLenum type,
+                      GLuint id,
+                      GLenum severity,
+                      GLsizei length,
+                      const GLchar* message,
+                      const void* userParam)
+{
+  if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
+  {
+    fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+            (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
+            type, severity, message);
+  }
+}
+#endif
+
+namespace OrthancStone
+{
+  void RtViewerView::EnableGLDebugOutput()
+  {
+#if !defined(__APPLE__)
+    glEnable(GL_DEBUG_OUTPUT);
+    glDebugMessageCallback(OpenGLMessageCallback, 0);
+#endif
+  }
+
+  boost::shared_ptr<IViewport> RtViewerView::CreateViewport(const std::string& canvasId)
+  {
+    // False means we do NOT let Windows treat this as a legacy application that needs to be scaled
+    return SdlOpenGLViewport::Create(canvasId, 1024, 1024, false);
+  }
+
+  void RtViewerApp::ProcessOptions(int argc, char* argv[])
+  {
+    namespace po = boost::program_options;
+    po::options_description desc("Usage");
+
+    desc.add_options()
+      ("loglevel", po::value<std::string>()->default_value("WARNING"),
+       "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)")
+
+      ("orthanc", po::value<std::string>()->default_value("http://localhost:8042"),
+       "Base URL of the Orthanc instance")
+
+      ("ctseries", po::value<std::string>()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"),
+       "Orthanc ID of the CT series to load. This must be supplied.")
+
+      ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"),
+       "Orthanc ID of the RTDOSE instance to load. This may be an empty string.")
+
+      ("rtstruct", po::value<std::string>()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"),
+       "Orthanc ID of the RTSTRUCT instance to load. This may be an empty string.")
+      ;
+
+    std::cout << desc << std::endl;
+
+    po::variables_map vm;
+    try
+    {
+      po::store(po::parse_command_line(argc, argv, desc), vm);
+      po::notify(vm);
+    }
+    catch (std::exception& e)
+    {
+      std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl;
+    }
+
+    for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it)
+    {
+      std::string key = it->first;
+      const po::variable_value& value = it->second;
+      const std::string& strValue = value.as<std::string>();
+      SetArgument(key, strValue);
+    }
+  }
+
+  void RtViewerApp::RunSdl(int argc, char* argv[])
+  {
+    ProcessOptions(argc, argv);
+
+    /**
+    Create the shared loaders context
+    */
+    loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
+
+    // we are in SDL --> downcast to concrete type
+    boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_);
+
+    /**
+      Url of the Orthanc instance
+      Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In 
+      wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative
+      URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders
+      plugin that serves the main web application from an URL like "http://localhost:8042/stone-rtviewer" (with ".." 
+      leading to the main Orthanc root URL)
+    */
+    std::string orthancUrl = arguments_["orthanc"];
+
+    {
+      Orthanc::WebServiceParameters p;
+      if (HasArgument("orthanc"))
+      {
+        p.SetUrl(orthancUrl);
+      }
+      if (HasArgument("user"))
+      {
+        ORTHANC_ASSERT(HasArgument("password"));
+        p.SetCredentials(GetArgument("user"), GetArgument("password"));
+      } 
+      else
+      {
+        ORTHANC_ASSERT(!HasArgument("password"));
+      }
+      loadersContext->SetOrthancParameters(p);
+    }
+
+    loadersContext->StartOracle();
+
+    CreateLoaders();
+
+    /**
+    Create viewports
+    */
+    CreateView("RtViewer Axial", VolumeProjection_Axial);
+    CreateView("RtViewer Coronal", VolumeProjection_Coronal);
+    CreateView("RtViewer Sagittal", VolumeProjection_Sagittal);
+
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->PrepareViewport();
+      views_[i]->EnableGLDebugOutput();
+    }
+
+    DefaultViewportInteractor interactor;
+
+    /**
+    It is very important that the Oracle (responsible for network I/O) be started before creating and firing the 
+    loaders, for any command scheduled by the loader before the oracle is started will be lost.
+    */
+    StartLoaders();
+
+
+    SdlRunLoop(views_, interactor);
+    loadersContext->StopOracle();
+  }
+
+  void RtViewerView::TakeScreenshot(const std::string& target,
+                                   unsigned int canvasWidth,
+                                   unsigned int canvasHeight)
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+
+    std::string ttf;
+    Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT);
+    
+    CairoCompositor compositor(canvasWidth, canvasHeight);
+    compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1);
+    compositor.Refresh(scene);
+
+    Orthanc::ImageAccessor canvas;
+    compositor.GetCanvas().GetReadOnlyAccessor(canvas);
+
+    Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
+    Orthanc::ImageProcessing::Convert(png, canvas);
+
+    Orthanc::PngWriter writer;
+    writer.WriteToFile(target, png);
+  }
+
+  static boost::shared_ptr<OrthancStone::RtViewerView> GetViewFromWindowId(
+    const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
+    Uint32 windowID)
+  {
+    using namespace OrthancStone;
+    for (size_t i = 0; i < views.size(); ++i)
+    {
+      boost::shared_ptr<OrthancStone::RtViewerView> view = views[i];
+      boost::shared_ptr<IViewport> viewport = view->GetViewport();
+      boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport);
+      Uint32 curWindowID = sdlViewport->GetSdlWindowId();
+      if (windowID == curWindowID)
+        return view;
+    }
+    return boost::shared_ptr<OrthancStone::RtViewerView>();
+  }
+
+  void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
+                               OrthancStone::DefaultViewportInteractor& interactor)
+  {
+    using namespace OrthancStone;
+
+    // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views
+    std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports;
+    for (size_t i = 0; i < views.size(); ++i)
+    {
+      boost::shared_ptr<RtViewerView> view = views[i];
+      boost::shared_ptr<IViewport> viewport = view->GetViewport();
+      boost::shared_ptr<SdlViewport> sdlViewport =
+        boost::dynamic_pointer_cast<SdlViewport>(viewport);
+      viewports.push_back(sdlViewport);
+    }
+
+    {
+      int scancodeCount = 0;
+      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+      bool stop = false;
+      while (!stop)
+      {
+        std::vector<SDL_Event> sdlEvents;
+        std::map<Uint32,SDL_Event> userEventsMap;
+        SDL_Event sdlEvent;
+
+        // FIRST: collect all pending events
+        while (SDL_PollEvent(&sdlEvent) != 0)
+        {
+          if ( (sdlEvent.type >= SDL_USEREVENT) && 
+               (sdlEvent.type < SDL_LASTEVENT) )
+          {
+            // we don't want to have multiple refresh events ,
+            // and since every refresh event is a user event with a special type,
+            // we use a map
+            userEventsMap[sdlEvent.type] = sdlEvent;
+          }
+          else
+          {
+            sdlEvents.push_back(sdlEvent);
+          }
+        }
+
+        // SECOND: add all user events to sdlEvents
+        for (std::map<Uint32,SDL_Event>::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it)
+          sdlEvents.push_back(it->second);
+
+        // now process the events
+        for (std::vector<SDL_Event>::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it)
+        {
+          const SDL_Event& sdlEvent = *it;
+
+          if (sdlEvent.type == SDL_QUIT)
+          {
+            stop = true;
+            break;
+          }
+          else if (sdlEvent.type == SDL_WINDOWEVENT &&
+                   (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED ||
+                    sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, sdlEvent.window.windowID);
+
+            boost::shared_ptr<SdlViewport> sdlViewport =
+              boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
+
+            sdlViewport->UpdateSize(sdlEvent.window.data1, sdlEvent.window.data2);
+          }
+          else if (sdlEvent.type == SDL_WINDOWEVENT &&
+                   (sdlEvent.window.event == SDL_WINDOWEVENT_SHOWN ||
+                    sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED))
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, sdlEvent.window.windowID);
+            boost::shared_ptr<SdlViewport> sdlViewport =
+              boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
+            sdlViewport->Paint();
+          }
+          else if (sdlEvent.type == SDL_KEYDOWN &&
+                   sdlEvent.key.repeat == 0 /* Ignore key bounce */)
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, sdlEvent.window.windowID);
+
+            switch (sdlEvent.key.keysym.sym)
+            {
+            case SDLK_f:
+            {
+              boost::shared_ptr<SdlViewport> sdlViewport =
+                boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
+              sdlViewport->ToggleMaximize();
+            }
+            break;
+
+            case SDLK_s:
+            {
+              std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
+              lock->GetCompositor().FitContent(lock->GetController().GetScene());
+              lock->Invalidate();
+            }
+            break;
+
+            case SDLK_q:
+              stop = true;
+              break;
+
+            default:
+              break;
+            }
+          }
+          else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
+                   sdlEvent.type == SDL_MOUSEMOTION ||
+                   sdlEvent.type == SDL_MOUSEBUTTONUP)
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, sdlEvent.window.windowID);
+
+            std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
+            if (lock->HasCompositor())
+            {
+              OrthancStone::PointerEvent p;
+              OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
+                                                   sdlEvent, keyboardState, scancodeCount);
+
+              switch (sdlEvent.type)
+              {
+              case SDL_MOUSEBUTTONDOWN:
+                interactor.SetWindowingLayer(view->GetCtLayerIndex());
+                lock->GetController().HandleMousePress(interactor, p,
+                                                       lock->GetCompositor().GetCanvasWidth(),
+                                                       lock->GetCompositor().GetCanvasHeight());
+                lock->Invalidate();
+                break;
+
+              case SDL_MOUSEMOTION:
+                if (lock->GetController().HandleMouseMove(p))
+                {
+                  lock->Invalidate();
+                }
+                break;
+
+              case SDL_MOUSEBUTTONUP:
+                lock->GetController().HandleMouseRelease(p);
+                lock->Invalidate();
+                break;
+
+              default:
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+            }
+          }
+          else if (sdlEvent.type == SDL_MOUSEWHEEL)
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, sdlEvent.window.windowID);
+
+            int delta = 0;
+            if (sdlEvent.wheel.y < 0)
+              delta = -1;
+            if (sdlEvent.wheel.y > 0)
+              delta = 1;
+
+            view->Scroll(delta);
+          }
+          else
+          {
+            for (size_t i = 0; i < views.size(); ++i)
+            {
+              boost::shared_ptr<SdlViewport> sdlViewport =
+                boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport());
+              if (sdlViewport->IsRefreshEvent(sdlEvent))
+              {
+                sdlViewport->Paint();
+              }
+            }
+          }
+        }
+        // Small delay to avoid using 100% of CPU
+        SDL_Delay(1);
+      }
+    }
+  }
+}
+
+boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
+
+/**
+ * IMPORTANT: The full arguments to "main()" are needed for SDL on
+ * Windows. Otherwise, one gets the linking error "undefined reference
+ * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
+ **/
+int main(int argc, char* argv[])
+{
+  using namespace OrthancStone;
+
+  StoneInitialize();
+
+  try
+  {
+    boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
+    g_app = app;
+    app->RunSdl(argc,argv);
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "EXCEPTION: " << e.What();
+  }
+
+  StoneFinalize();
+
+  return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/SdlHelpers.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,142 @@
+/**
+ * Stone of Orthanc
+ * 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
+
+#if ORTHANC_ENABLE_SDL != 1
+# error This file cannot be used if ORTHANC_ENABLE_SDL != 1
+#endif
+
+#include "../../Sources/Viewport/SdlViewport.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <SDL.h>
+
+#include <map>
+#include <string>
+
+namespace OrthancStoneHelpers
+{
+
+  inline OrthancStone::KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState,
+                                                              const int scancodeCount)
+  {
+    using namespace OrthancStone;
+    int result = KeyboardModifiers_None;
+
+    if (keyboardState != NULL)
+    {
+      if (SDL_SCANCODE_LSHIFT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LSHIFT])
+      {
+        result |= KeyboardModifiers_Shift;
+      }
+
+      if (SDL_SCANCODE_RSHIFT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RSHIFT])
+      {
+        result |= KeyboardModifiers_Shift;
+      }
+
+      if (SDL_SCANCODE_LCTRL < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LCTRL])
+      {
+        result |= KeyboardModifiers_Control;
+      }
+
+      if (SDL_SCANCODE_RCTRL < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RCTRL])
+      {
+        result |= KeyboardModifiers_Control;
+      }
+
+      if (SDL_SCANCODE_LALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LALT])
+      {
+        result |= KeyboardModifiers_Alt;
+      }
+
+      if (SDL_SCANCODE_RALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RALT])
+      {
+        result |= KeyboardModifiers_Alt;
+      }
+    }
+
+    return static_cast<KeyboardModifiers>(result);
+  }
+
+
+  inline void GetPointerEvent(OrthancStone::PointerEvent& p,
+                              const OrthancStone::ICompositor& compositor,
+                              SDL_Event event,
+                              const uint8_t* keyboardState,
+                              const int scancodeCount)
+  {
+    using namespace OrthancStone;
+    KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
+
+    switch (event.button.button)
+    {
+    case SDL_BUTTON_LEFT:
+      p.SetMouseButton(OrthancStone::MouseButton_Left);
+      break;
+
+    case SDL_BUTTON_RIGHT:
+      p.SetMouseButton(OrthancStone::MouseButton_Right);
+      break;
+
+    case SDL_BUTTON_MIDDLE:
+      p.SetMouseButton(OrthancStone::MouseButton_Middle);
+      break;
+
+    default:
+      p.SetMouseButton(OrthancStone::MouseButton_None);
+      break;
+    }
+
+    p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+    p.SetAltModifier( (modifiers & KeyboardModifiers_Alt) != 0);
+    p.SetControlModifier( (modifiers & KeyboardModifiers_Control) != 0);
+    p.SetShiftModifier( (modifiers & KeyboardModifiers_Shift) != 0);
+  }
+
+  
+  inline boost::shared_ptr<OrthancStone::SdlViewport> GetSdlViewportFromWindowId(
+    const std::vector<boost::shared_ptr<OrthancStone::SdlViewport> >& viewports,
+    Uint32 windowID)
+  {
+    using namespace OrthancStone;
+    for (size_t i = 0; i < viewports.size(); ++i)
+    {
+      boost::shared_ptr<IViewport> viewport = viewports[i];
+      boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport);
+      Uint32 curWindowID = sdlViewport->GetSdlWindowId();
+      if (windowID == curWindowID)
+        return sdlViewport;
+    }
+    
+    return boost::shared_ptr<OrthancStone::SdlViewport>();
+  }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/SingleFrameViewer/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,46 @@
+cmake_minimum_required(VERSION 2.8.10)
+
+project(SdlSimpleViewer)
+
+include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  set(ORTHANC_BOOST_COMPONENTS program_options)
+
+  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
+  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake)
+  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
+
+else()
+  set(ENABLE_GOOGLE_TEST ON)
+  set(ENABLE_LOCALE ON)  # Necessary for text rendering
+  set(ENABLE_OPENGL ON)  #  <==
+  set(ENABLE_WEB_CLIENT ON)
+endif()
+
+set(ENABLE_DCMTK ON)  # <==
+set(ENABLE_SDL ON)
+
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../Utilities.cmake)
+
+if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  # This include must be after "OrthancStoneConfiguration.cmake" to
+  # have "BOOST_SOURCES_DIR" defined
+  include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake)
+endif()
+
+SortFilesInSourceGroups()
+
+add_executable(SdlSimpleViewer
+  ../SdlHelpers.h
+  ../../Common/SampleHelpers.h
+  SdlSimpleViewerApplication.h
+  SdlSimpleViewer.cpp
+  ${ORTHANC_STONE_SOURCES}
+  )
+
+target_link_libraries(SdlSimpleViewer ${DCMTK_LIBRARIES})
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/SingleFrameViewer/CMakeSettings.json	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,37 @@
+{
+  "configurations": [
+    {
+      "name": "x64-Debug",
+      "generator": "Ninja",
+      "configurationType": "Debug",
+      "inheritEnvironments": [ "msvc_x64_x64" ],
+      "buildRoot": "${projectDir}\\out\\build\\${name}",
+      "installRoot": "${projectDir}\\out\\install\\${name}",
+      "cmakeCommandArgs": "",
+      "buildCommandArgs": "-v",
+      "ctestCommandArgs": "",
+      "variables": [
+        {
+          "name": "MSVC_MULTIPLE_PROCESSES",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "ALLOW_DOWNLOADS",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "STATIC_BUILD",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "OPENSSL_NO_CAPIENG",
+          "value": "True",
+          "type": "BOOL"
+        },
+      ]
+    }
+  ]
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,276 @@
+/**
+ * Stone of Orthanc
+ * 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 "SdlSimpleViewerApplication.h"
+#include "../SdlHelpers.h"
+#include "../../Common/SampleHelpers.h"
+
+#include "../../../Sources/Loaders/GenericLoadersContext.h"
+#include "../../../Sources/StoneException.h"
+#include "../../../Sources/StoneEnumerations.h"
+#include "../../../Sources/StoneInitialization.h"
+#include "../../../Sources/Viewport/SdlViewport.h"
+
+#include <Compatibility.h>  // For std::unique_ptr<>
+#include <OrthancException.h>
+
+#include <boost/program_options.hpp>
+#include <SDL.h>
+
+#include <string>
+
+
+std::string orthancUrl;
+std::string instanceId;
+int frameIndex = 0;
+
+
+static void ProcessOptions(int argc, char* argv[])
+{
+  namespace po = boost::program_options;
+  po::options_description desc("Usage");
+
+  desc.add_options()
+    ("loglevel", po::value<std::string>()->default_value("WARNING"),
+     "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)")
+
+    ("orthanc", po::value<std::string>()->default_value("http://localhost:8042"),
+     "Base URL of the Orthanc instance")
+
+    ("instance", po::value<std::string>()->default_value("285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"),
+     "Orthanc ID of the instance to display")
+
+    ("frame_index", po::value<int>()->default_value(0),
+     "The zero-based index of the frame (for multi-frame instances)")
+    ;
+
+  std::cout << desc << std::endl;
+
+  po::variables_map vm;
+  try
+  {
+    po::store(po::parse_command_line(argc, argv, desc), vm);
+    po::notify(vm);
+  }
+  catch (std::exception& e)
+  {
+    std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl;
+  }
+
+  if (vm.count("loglevel") > 0)
+  {
+    std::string logLevel = vm["loglevel"].as<std::string>();
+    OrthancStoneHelpers::SetLogLevel(logLevel);
+  }
+
+  if (vm.count("orthanc") > 0)
+  {
+    // maybe check URL validity here
+    orthancUrl = vm["orthanc"].as<std::string>();
+  }
+
+  if (vm.count("instance") > 0)
+  {
+    instanceId = vm["instance"].as<std::string>();
+  }
+
+  if (vm.count("frame_index") > 0)
+  {
+    frameIndex = vm["frame_index"].as<int>();
+  }
+
+}
+
+/**
+ * IMPORTANT: The full arguments to "main()" are needed for SDL on
+ * Windows. Otherwise, one gets the linking error "undefined reference
+ * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
+ **/
+int main(int argc, char* argv[])
+{
+  try
+  {
+    OrthancStone::StoneInitialize();
+
+    ProcessOptions(argc, argv);
+
+    //Orthanc::Logging::EnableInfoLevel(true);
+    //Orthanc::Logging::EnableTraceLevel(true);
+
+    {
+
+#if 1
+      boost::shared_ptr<OrthancStone::SdlViewport> viewport =
+        OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600);
+#else
+      boost::shared_ptr<OrthancStone::SdlViewport> viewport =
+        OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600);
+#endif
+
+      OrthancStone::GenericLoadersContext context(1, 4, 1);
+
+      Orthanc::WebServiceParameters orthancWebService;
+      orthancWebService.SetUrl(orthancUrl);
+      context.SetOrthancParameters(orthancWebService);
+
+      context.StartOracle();
+
+      {
+
+        boost::shared_ptr<SdlSimpleViewerApplication> application(
+          SdlSimpleViewerApplication::Create(context, viewport));
+
+        OrthancStone::DicomSource source;
+
+        application->LoadOrthancFrame(source, instanceId, frameIndex);
+
+        OrthancStone::DefaultViewportInteractor interactor;
+        interactor.SetWindowingLayer(0);
+
+        {
+          int scancodeCount = 0;
+          const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+          bool stop = false;
+          while (!stop)
+          {
+            bool paint = false;
+            SDL_Event event;
+            while (SDL_PollEvent(&event))
+            {
+              if (event.type == SDL_QUIT)
+              {
+                stop = true;
+                break;
+              }
+              else if (viewport->IsRefreshEvent(event))
+              {
+                paint = true;
+              }
+              else if (event.type == SDL_WINDOWEVENT &&
+                       (event.window.event == SDL_WINDOWEVENT_RESIZED ||
+                        event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
+              {
+                viewport->UpdateSize(event.window.data1, event.window.data2);
+              }
+              else if (event.type == SDL_WINDOWEVENT &&
+                       (event.window.event == SDL_WINDOWEVENT_SHOWN ||
+                        event.window.event == SDL_WINDOWEVENT_EXPOSED))
+              {
+                paint = true;
+              }
+              else if (event.type == SDL_KEYDOWN &&
+                       event.key.repeat == 0 /* Ignore key bounce */)
+              {
+                switch (event.key.keysym.sym)
+                {
+                case SDLK_f:
+                  viewport->ToggleMaximize();
+                  break;
+
+                case SDLK_s:
+                  application->FitContent();
+                  break;
+
+                case SDLK_q:
+                  stop = true;
+                  break;
+
+                default:
+                  break;
+                }
+              }
+              else if (event.type == SDL_MOUSEBUTTONDOWN ||
+                       event.type == SDL_MOUSEMOTION ||
+                       event.type == SDL_MOUSEBUTTONUP)
+              {
+                std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport->Lock());
+                if (lock->HasCompositor())
+                {
+                  OrthancStone::PointerEvent p;
+                  OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
+                                                       event, keyboardState, scancodeCount);
+
+                  switch (event.type)
+                  {
+                  case SDL_MOUSEBUTTONDOWN:
+                    lock->GetController().HandleMousePress(interactor, p,
+                                                           lock->GetCompositor().GetCanvasWidth(),
+                                                           lock->GetCompositor().GetCanvasHeight());
+                    lock->Invalidate();
+                    break;
+
+                  case SDL_MOUSEMOTION:
+                    if (lock->GetController().HandleMouseMove(p))
+                    {
+                      lock->Invalidate();
+                    }
+                    break;
+
+                  case SDL_MOUSEBUTTONUP:
+                    lock->GetController().HandleMouseRelease(p);
+                    lock->Invalidate();
+                    break;
+
+                  default:
+                    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+                  }
+                }
+              }
+            }
+
+            if (paint)
+            {
+              viewport->Paint();
+            }
+
+            // Small delay to avoid using 100% of CPU
+            SDL_Delay(1);
+          }
+        }
+        context.StopOracle();
+      }
+    }
+
+    OrthancStone::StoneFinalize();
+    return 0;
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "OrthancException: " << e.What();
+    return -1;
+  }
+  catch (OrthancStone::StoneException& e)
+  {
+    LOG(ERROR) << "StoneException: " << e.What();
+    return -1;
+  }
+  catch (std::runtime_error& e)
+  {
+    LOG(ERROR) << "Runtime error: " << e.what();
+    return -1;
+  }
+  catch (...)
+  {
+    LOG(ERROR) << "Native exception";
+    return -1;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,168 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../../Sources/Viewport/IViewport.h"
+#include "../../../Sources/Loaders/DicomResourcesLoader.h"
+#include "../../../Sources/Loaders/ILoadersContext.h"
+#include "../../../Sources/Loaders/SeriesFramesLoader.h"
+#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h"
+
+#include <Compatibility.h>  // For std::unique_ptr<>
+
+#include <boost/make_shared.hpp>
+
+
+using OrthancStone::ILoadersContext;
+using OrthancStone::ObserverBase;
+using OrthancStone::IViewport;
+using OrthancStone::DicomResourcesLoader;
+using OrthancStone::SeriesFramesLoader;
+using OrthancStone::TextureBaseSceneLayer;
+using OrthancStone::DicomSource;
+using OrthancStone::SeriesThumbnailsLoader;
+using OrthancStone::LoadedDicomResources;
+using OrthancStone::SeriesThumbnailType;
+using OrthancStone::OracleScheduler;
+using OrthancStone::OrthancRestApiCommand;
+using OrthancStone::OracleScheduler;
+using OrthancStone::OracleScheduler;
+using OrthancStone::OracleScheduler;
+
+
+class SdlSimpleViewerApplication : public ObserverBase<SdlSimpleViewerApplication>
+{
+
+public:
+  static boost::shared_ptr<SdlSimpleViewerApplication> Create(ILoadersContext& context, boost::shared_ptr<IViewport> viewport)
+  {
+    boost::shared_ptr<SdlSimpleViewerApplication> application(new SdlSimpleViewerApplication(context, viewport));
+
+    {
+      std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
+      application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
+    }
+
+    application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle);
+
+    return application;
+  }
+
+  void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame)
+  {
+    std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+
+    dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
+                                              0, source, Orthanc::ResourceType_Instance, instanceId,
+                                              new Orthanc::SingleValueObject<unsigned int>(frame));
+  }
+
+#if 0
+  void LoadDicomWebFrame(const DicomSource& source,
+                         const std::string& studyInstanceUid,
+                         const std::string& seriesInstanceUid,
+                         const std::string& sopInstanceUid,
+                         unsigned int frame)
+  {
+    std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+
+    // We first must load the "/metadata" to know the number of frames
+    dicomLoader_->ScheduleGetDicomWeb(
+      boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source,
+      "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata",
+      new Orthanc::SingleValueObject<unsigned int>(frame));
+  }
+#endif 
+
+  void FitContent()
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    lock->GetCompositor().FitContent(lock->GetController().GetScene());
+    lock->Invalidate();
+  }
+
+private:
+  ILoadersContext& context_;
+  boost::shared_ptr<IViewport>             viewport_;
+  boost::shared_ptr<DicomResourcesLoader>  dicomLoader_;
+  boost::shared_ptr<SeriesFramesLoader>    framesLoader_;
+
+  SdlSimpleViewerApplication(ILoadersContext& context,
+                             boost::shared_ptr<IViewport> viewport) :
+    context_(context),
+    viewport_(viewport)
+  {
+  }
+
+  void Handle(const SeriesFramesLoader::FrameLoadedMessage& message)
+  {
+    LOG(INFO) << "Frame decoded! "
+      << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight()
+      << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat());
+
+    std::unique_ptr<TextureBaseSceneLayer> layer(
+      message.GetInstanceParameters().CreateTexture(message.GetImage()));
+    layer->SetLinearInterpolation(true);
+
+    {
+      std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().SetLayer(0, layer.release());
+      lock->GetCompositor().FitContent(lock->GetController().GetScene());
+      lock->Invalidate();
+    }
+  }
+
+  void Handle(const DicomResourcesLoader::SuccessMessage& message)
+  {
+    if (message.GetResources()->GetSize() != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    //message.GetResources()->GetResource(0).Print(stdout);
+
+    {
+      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+      SeriesFramesLoader::Factory f(*message.GetResources());
+
+      framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(
+        f.Create(*lock));
+      
+      Register<SeriesFramesLoader::FrameLoadedMessage>(
+        *framesLoader_, &SdlSimpleViewerApplication::Handle);
+
+      assert(message.HasUserPayload());
+
+      const Orthanc::SingleValueObject<unsigned int>& payload =
+        dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(
+          message.GetUserPayload());
+
+      LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue();
+      framesLoader_->ScheduleLoadFrame(
+        0, message.GetDicomSource(), payload.GetValue(),
+        message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */,
+        NULL);
+    }
+  }
+
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Sdl/Utilities.cmake	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,84 @@
+
+
+macro(GetFilenameFromPath TargetVariable Path)
+#message(STATUS "GetFilenameFromPath (1): Path = ${Path} TargetVariable = ${${TargetVariable}}")
+string(REPLACE "\\" "/" PathWithFwdSlashes "${Path}")
+string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${PathWithFwdSlashes}")
+#message(STATUS "GetFilenameFromPath (2): Path = ${Path} Path = ${PathWithFwdSlashes} TargetVariable = ${TargetVariable}")
+endmacro()
+
+macro(GetFilePathWithoutLastExtension TargetVariable FilePath)
+string(REGEX REPLACE "(^.*)\\.([^\\.]+)" "\\1" ${TargetVariable} "${FilePath}")
+#message(STATUS "GetFileNameWithoutLastExtension: FilePath = ${FilePath} TargetVariable = ${${TargetVariable}}")
+endmacro()
+
+macro(Test_GetFilePathWithoutLastExtension)
+set(tmp "/prout/zi/goui.goui.cpp")
+GetFilePathWithoutLastExtension(TargetVariable "${tmp}")
+if(NOT ("${TargetVariable}" STREQUAL "/prout/zi/goui.goui"))
+  message(FATAL_ERROR "Test_GetFilePathWithoutLastExtension failed (1)")
+else()
+  #message(STATUS "Test_GetFilePathWithoutLastExtension: <<OK>>")
+endif()
+endmacro()
+
+Test_GetFilePathWithoutLastExtension()
+
+macro(Test_GetFilenameFromPath)
+set(tmp "/prout/../../dada/zi/goui.goui.cpp")
+GetFilenameFromPath(TargetVariable "${tmp}")
+if(NOT ("${TargetVariable}" STREQUAL "goui.goui.cpp"))
+  message(FATAL_ERROR "Test_GetFilenameFromPath failed")
+else()
+  #message(STATUS "Test_GetFilenameFromPath: <<OK>>")
+endif()
+endmacro()
+
+Test_GetFilenameFromPath()
+
+macro(SortFilesInSourceGroups)
+  if(FALSE)
+    foreach(source IN LISTS ORTHANC_STONE_SOURCES)
+        # if("${source}" MATCHES ".*/pixman.*\\.c")
+        #   message("pixman source: ${source}")
+        # elseif("${source}" MATCHES ".*/pixman.*\\.c")
+        #   message("pixman header: ${source}")
+        # endif()
+        
+        if("${source}" MATCHES ".*\\.\\./.*")
+          message("source raw: ${source}")
+          #file(TO_CMAKE_PATH ${source} sourceCMakePath)
+          get_filename_component(sourceCMakePath ${source} ABSOLUTE)
+          message("source CMake: ${sourceCMakePath}")
+        endif()
+
+        # returns the containing directory with forward slashes
+        # get_filename_component(source_path "${source}" PATH) 
+
+        # converts / to \
+        # string(REPLACE "/" "\\" source_path_msvc "${source_path}")
+        #source_group("Stone ${source_path_msvc}" FILES "${source}")
+    endforeach()
+  endif()
+
+  source_group("Orthanc Framework\\Sources" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*cpp")
+  source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*hpp")
+  source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*h")
+
+  source_group("Stone Library\\Sources" REGULAR_EXPRESSION ".*/orthanc-stone/.*cpp")
+  source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*hpp")
+  source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*h")
+
+  source_group("Stone Samples\\Source" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.cpp")
+  source_group("Stone Samples\\Headers" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.h")
+
+  source_group("ThirdParty\\cairo" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/cairo[^/]*/.*")
+  source_group("ThirdParty\\pixman" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/pixman[^/]*/.*")
+  source_group("ThirdParty\\base64" REGULAR_EXPRESSION ".*ThirdParty/base64.*")
+  source_group("ThirdParty\\SDL2" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/SDL2.*")
+  source_group("ThirdParty\\glew" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/glew.*")
+  source_group("AUTOGENERATED" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/AUTOGENERATED/.*")
+  source_group("ThirdParty\\minizip" REGULAR_EXPRESSION ".*ThirdParty/minizip/.*")
+  source_group("ThirdParty\\md5" REGULAR_EXPRESSION ".*ThirdParty/md5/.*")
+endmacro()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,133 @@
+cmake_minimum_required(VERSION 2.8.3)
+
+project(OrthancStone)
+
+# Configuration of the Emscripten compiler for WebAssembly target
+# ---------------------------------------------------------------
+set(USE_WASM ON CACHE BOOL "")
+
+set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
+
+set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
+endif()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
+add_definitions(
+  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
+)
+
+# Stone of Orthanc configuration
+# ---------------------------------------------------------------
+set(ALLOW_DOWNLOADS ON)
+
+include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake)
+
+SET(ENABLE_DCMTK OFF)  # Not necessary
+SET(ENABLE_GOOGLE_TEST OFF)
+SET(ENABLE_LOCALE ON)  # Necessary for text rendering
+SET(ENABLE_WASM ON)
+SET(ORTHANC_SANDBOXED ON)
+
+# this will set up the build system for Stone of Orthanc and will
+# populate the ORTHANC_STONE_SOURCES CMake variable
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+
+# We embed a font to be used for on-screen overlays
+# ---------------------------------------------------------------
+
+DownloadPackage(
+  "a24b8136b8f3bb93f166baf97d9328de"
+  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
+  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
+
+EmbedResources(
+  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut  
+  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
+  )
+
+add_library(OrthancStone STATIC
+  ${ORTHANC_STONE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+################################################################################
+
+# Define the WASM module
+# ---------------------------------------------------------------
+
+project(RtViewerWasm)
+
+add_executable(RtViewerWasm
+  RtViewer/RtViewerWasm.cpp
+  ../Common/RtViewerApp.cpp
+  ../Common/RtViewerApp.h
+  ../Common/RtViewerView.cpp
+  ../Common/RtViewerView.h
+  )
+
+target_link_libraries(RtViewerWasm OrthancStone)
+
+# Declare installation files for the module
+# ---------------------------------------------------------------
+install(
+  TARGETS RtViewerWasm
+  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/
+  )
+
+# Declare installation files for the companion files (web scaffolding)
+# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
+# (the generated JS loader for the WASM module) is handled by the `install1`
+# section above: it is considered to be the binary output of 
+# the linker.
+# ---------------------------------------------------------------
+install(
+  FILES
+  ${CMAKE_SOURCE_DIR}/RtViewer/RtViewerWasmApp.js
+  ${CMAKE_SOURCE_DIR}/RtViewer/index.html
+  ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm
+  DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/
+  )
+
+################################################################################
+
+# Define the WASM module
+# ---------------------------------------------------------------
+
+project(SingleFrameViewerWasm)
+
+add_executable(SingleFrameViewerWasm
+  SingleFrameViewer/SingleFrameViewer.cpp
+  )
+
+target_link_libraries(SingleFrameViewerWasm OrthancStone)
+
+# Declare installation files for the module
+# ---------------------------------------------------------------
+install(
+  TARGETS SingleFrameViewerWasm
+  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/
+  )
+
+# Declare installation files for the companion files (web scaffolding)
+# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
+# (the generated JS loader for the WASM module) is handled by the `install1`
+# section above: it is considered to be the binary output of 
+# the linker.
+# ---------------------------------------------------------------
+install(
+  FILES
+  ${CMAKE_SOURCE_DIR}/SingleFrameViewer/SingleFrameViewerApp.js
+  ${CMAKE_SOURCE_DIR}/SingleFrameViewer/index.html
+  ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm
+  DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/NOTES.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,22 @@
+
+Building WebAssembly samples using Docker
+=========================================
+
+The script "./docker-build.sh" can be used to quickly build the
+WebAssembly samples on any GNU/Linux distribution equipped with
+Docker. This avoids newcomers to install Emscripten and learn the
+CMake options. Just type:
+
+$ ./docker-build.sh Release
+
+After successful build, the binaries will be installed in the
+following folder (i.e. in the folder "wasm-binaries" at the root of
+the source distribution):
+
+$ ls -l ../../wasm-binaries
+
+
+NB: The source code of the Docker build environment can be found at
+the following location:
+https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/RtViewer/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,89 @@
+cmake_minimum_required(VERSION 2.8.3)
+
+project(RtViewerWasm)
+
+# Configuration of the Emscripten compiler for WebAssembly target
+# ---------------------------------------------------------------
+set(USE_WASM ON CACHE BOOL "")
+
+set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
+
+set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
+endif()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
+add_definitions(
+  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
+)
+
+# Stone of Orthanc configuration
+# ---------------------------------------------------------------
+set(ALLOW_DOWNLOADS ON)
+
+include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
+
+SET(ENABLE_DCMTK OFF)  # Not necessary
+SET(ENABLE_GOOGLE_TEST OFF)
+SET(ENABLE_LOCALE ON)  # Necessary for text rendering
+SET(ENABLE_WASM ON)
+SET(ORTHANC_SANDBOXED ON)
+
+# this will set up the build system for Stone of Orthanc and will
+# populate the ORTHANC_STONE_SOURCES CMake variable
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+
+# We embed a font to be used for on-screen overlays
+# ---------------------------------------------------------------
+
+DownloadPackage(
+  "a24b8136b8f3bb93f166baf97d9328de"
+  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
+  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
+
+EmbedResources(
+  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut  
+  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
+  )
+
+# Define the WASM module
+# ---------------------------------------------------------------
+add_executable(RtViewerWasm
+  RtViewerWasm.cpp
+  ../../Common/RtViewerApp.cpp
+  ../../Common/RtViewerApp.h
+  ../../Common/RtViewerView.cpp
+  ../../Common/RtViewerView.h
+  ${ORTHANC_STONE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+# Declare installation files for the module
+# ---------------------------------------------------------------
+install(
+  TARGETS RtViewerWasm
+  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
+  )
+
+# Declare installation files for the companion files (web scaffolding)
+# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
+# (the generated JS loader for the WASM module) is handled by the `install1`
+# section above: it is considered to be the binary output of 
+# the linker.
+# ---------------------------------------------------------------
+install(
+  FILES
+  ${CMAKE_SOURCE_DIR}/RtViewerWasmApp.js
+  ${CMAKE_SOURCE_DIR}/index.html
+  ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm
+  DESTINATION ${CMAKE_INSTALL_PREFIX}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/RtViewer/OBSOLETE.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,559 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../../Framework/Oracle/SleepOracleCommand.h"
+#include "../../../Framework/Oracle/WebAssemblyOracle.h"
+#include "../../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../../Framework/Scene2D/PanSceneTracker.h"
+#include "../../../Framework/Scene2D/RotateSceneTracker.h"
+#include "../../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../../Framework/Scene2DViewport/UndoStack.h"
+#include "../../../Framework/Scene2DViewport/ViewportController.h"
+#include "../../../Framework/StoneInitialization.h"
+#include "../../../Framework/Viewport/WebAssemblyViewport.h"
+#include "../../../Framework/Volumes/VolumeSceneLayerSource.h"
+
+#include <OrthancException.h>
+
+#include <emscripten/html5.h>
+#include <emscripten.h>
+
+#include <boost/make_shared.hpp>
+
+
+class ViewportManager;
+
+static const unsigned int FONT_SIZE = 32;
+
+boost::shared_ptr<OrthancStone::DicomVolumeImage>                     ct_(new OrthancStone::DicomVolumeImage);
+boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_;
+std::unique_ptr<ViewportManager>                        widget1_;
+std::unique_ptr<ViewportManager>                        widget2_;
+std::unique_ptr<ViewportManager>                        widget3_;
+//OrthancStone::MessageBroker                                         broker_;
+//OrthancStone::WebAssemblyOracle                                     oracle_(broker_);
+std::unique_ptr<OrthancStone::IFlexiblePointerTracker>                tracker_;
+static std::map<std::string, std::string>                             arguments_;
+static bool                                                           ctrlDown_ = false;
+
+
+#if 0
+
+// use the one from WebAssemblyViewport
+static OrthancStone::PointerEvent* ConvertMouseEvent(
+  const EmscriptenMouseEvent&        source,
+  OrthancStone::IViewport& viewport)
+{
+
+  std::unique_ptr<OrthancStone::PointerEvent> target(
+    new OrthancStone::PointerEvent);
+
+  target->AddPosition(viewport.GetPixelCenterCoordinates(
+                        source.targetX, source.targetY));
+  target->SetAltModifier(source.altKey);
+  target->SetControlModifier(source.ctrlKey);
+  target->SetShiftModifier(source.shiftKey);
+
+  return target.release();
+}
+#endif
+
+
+EM_BOOL OnMouseEvent(int eventType, 
+                     const EmscriptenMouseEvent *mouseEvent, 
+                     void *userData)
+{
+  if (mouseEvent != NULL &&
+      userData != NULL)
+  {
+    boost::shared_ptr<OrthancStone::WebGLViewport>& viewport = 
+      *reinterpret_cast<boost::shared_ptr<OrthancStone::WebGLViewport>*>(userData);
+
+    std::unique_ptr<OrthancStone::IViewport::ILock> lock = (*viewport)->Lock();
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+    
+    switch (eventType)
+    {
+      case EMSCRIPTEN_EVENT_CLICK:
+      {
+        static unsigned int count = 0;
+        char buf[64];
+        sprintf(buf, "click %d", count++);
+
+        std::unique_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
+        layer->SetText(buf);
+        scene.SetLayer(100, layer.release());
+        lock->Invalidate();
+        break;
+      }
+
+      case EMSCRIPTEN_EVENT_MOUSEDOWN:
+      {
+        boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t;
+
+        {
+          std::unique_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
+
+          switch (mouseEvent->button)
+          {
+            case 0:  // Left button
+              emscripten_console_log("Creating RotateSceneTracker");
+              t.reset(new OrthancStone::RotateSceneTracker(
+                        viewport, *event));
+              break;
+
+            case 1:  // Middle button
+              emscripten_console_log("Creating PanSceneTracker");
+              LOG(INFO) << "Creating PanSceneTracker" ;
+              t.reset(new OrthancStone::PanSceneTracker(
+                        viewport, *event));
+              break;
+
+            case 2:  // Right button
+              emscripten_console_log("Creating ZoomSceneTracker");
+              t.reset(new OrthancStone::ZoomSceneTracker(
+                        viewport, *event, controller->GetViewport().GetCanvasWidth()));
+              break;
+
+            default:
+              break;
+          }
+        }
+
+        if (t.get() != NULL)
+        {
+          tracker_.reset(
+            new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier()));
+          controller->GetViewport().Refresh();
+        }
+
+        break;
+      }
+
+      case EMSCRIPTEN_EVENT_MOUSEMOVE:
+        if (tracker_.get() != NULL)
+        {
+          std::unique_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
+          tracker_->PointerMove(*event);
+          controller->GetViewport().Refresh();
+        }
+        break;
+
+      case EMSCRIPTEN_EVENT_MOUSEUP:
+        if (tracker_.get() != NULL)
+        {
+          std::unique_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
+          tracker_->PointerUp(*event);
+          controller->GetViewport().Refresh();
+          if (!tracker_->IsAlive())
+            tracker_.reset();
+        }
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  return true;
+}
+
+
+void SetupEvents(const std::string& canvas,
+                 boost::shared_ptr<OrthancStone::WebGLViewport>& viewport)
+{
+  emscripten_set_mousedown_callback(canvas.c_str(), &viewport, false, OnMouseEvent);
+  emscripten_set_mousemove_callback(canvas.c_str(), &viewport, false, OnMouseEvent);
+  emscripten_set_mouseup_callback(canvas.c_str(), &viewport, false, OnMouseEvent);
+}
+
+  class ViewportManager : public OrthanStone::ObserverBase<ViewportManager>
+  {
+  private:
+    OrthancStone::WebAssemblyViewport         viewport_;
+    std::unique_ptr<VolumeSceneLayerSource>   source_;
+    VolumeProjection                          projection_;
+    std::vector<CoordinateSystem3D>           planes_;
+    size_t                                    currentPlane_;
+
+    void Handle(const DicomVolumeImage::GeometryReadyMessage& message)
+    {
+      LOG(INFO) << "Geometry is available";
+
+      const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry();
+
+      const unsigned int depth = geometry.GetProjectionDepth(projection_);
+
+      // select an initial cutting plane halfway through the volume
+      currentPlane_ = depth / 2;
+
+      planes_.resize(depth);
+
+      for (unsigned int z = 0; z < depth; z++)
+      {
+        planes_[z] = geometry.GetProjectionSlice(projection_, z);
+      }
+
+      Refresh();
+
+      viewport_.FitContent();
+    }
+    
+  public:
+    ViewportManager(const std::string& canvas,
+                    VolumeProjection projection) :
+      projection_(projection),
+      currentPlane_(0)
+    {
+      viewport_ = OrthancStone::WebGLViewport::Create(canvas);
+    }
+
+    void UpdateSize()
+    {
+      viewport_.UpdateSize();
+    }
+
+    void SetSlicer(int layerDepth,
+                   const boost::shared_ptr<IVolumeSlicer>& slicer,
+                   IObservable& loader,
+                   ILayerStyleConfigurator* configurator)
+    {
+      if (source_.get() != NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "Only one slicer can be registered");
+      }
+      
+      loader.RegisterObserverCallback(
+        new Callable<ViewportManager, DicomVolumeImage::GeometryReadyMessage>
+        (*this, &ViewportManager::Handle));
+
+      source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer));
+
+      if (configurator != NULL)
+      {
+        source_->SetConfigurator(configurator);
+      }
+    }    
+
+    void Refresh()
+    {
+      if (source_.get() != NULL &&
+          currentPlane_ < planes_.size())
+      {
+        source_->Update(planes_[currentPlane_]);
+        viewport_.Refresh();
+      }
+    }
+
+    size_t GetSlicesCount() const
+    {
+      return planes_.size();
+    }
+
+    void Scroll(int delta)
+    {
+      if (!planes_.empty())
+      {
+        int tmp = static_cast<int>(currentPlane_) + delta;
+        unsigned int next;
+
+        if (tmp < 0)
+        {
+          next = 0;
+        }
+        else if (tmp >= static_cast<int>(planes_.size()))
+        {
+          next = planes_.size() - 1;
+        }
+        else
+        {
+          next = static_cast<size_t>(tmp);
+        }
+
+        if (next != currentPlane_)
+        {
+          currentPlane_ = next;
+          Refresh();
+        }
+      }
+    }
+  };
+}
+
+
+EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
+{
+  try
+  {
+    if (widget1_.get() != NULL)
+    {
+      widget1_->UpdateSize();
+    }
+  
+    if (widget2_.get() != NULL)
+    {
+      widget2_->UpdateSize();
+    }
+  
+    if (widget3_.get() != NULL)
+    {
+      widget3_->UpdateSize();
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception while updating canvas size: " << e.What();
+  }
+  
+  return true;
+}
+
+EM_BOOL OnAnimationFrame(double time, void *userData)
+{
+  try
+  {
+    if (widget1_.get() != NULL)
+    {
+      widget1_->Refresh();
+    }
+  
+    if (widget2_.get() != NULL)
+    {
+      widget2_->Refresh();
+    }
+  
+    if (widget3_.get() != NULL)
+    {
+      widget3_->Refresh();
+    }
+
+    return true;
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What();
+    return false;
+  }  
+}
+
+EM_BOOL OnMouseWheel(int eventType,
+                     const EmscriptenWheelEvent *wheelEvent,
+                     void *userData)
+{
+  try
+  {
+    if (userData != NULL)
+    {
+      int delta = 0;
+
+      if (wheelEvent->deltaY < 0)
+      {
+        delta = -1;
+      }
+           
+      if (wheelEvent->deltaY > 0)
+      {
+        delta = 1;
+      }
+
+      OrthancStone::ViewportManager& widget =
+        *reinterpret_cast<OrthancStone::ViewportManager*>(userData);
+      
+      if (ctrlDown_)
+      {
+        delta *= static_cast<int>(widget.GetSlicesCount() / 10);
+      }
+
+      widget.Scroll(delta);
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception in the wheel event: " << e.What();
+  }
+  
+  return true;
+}
+
+
+EM_BOOL OnKeyDown(int eventType,
+                  const EmscriptenKeyboardEvent *keyEvent,
+                  void *userData)
+{
+  ctrlDown_ = keyEvent->ctrlKey;
+  return false;
+}
+
+
+EM_BOOL OnKeyUp(int eventType,
+                const EmscriptenKeyboardEvent *keyEvent,
+                void *userData)
+{
+  ctrlDown_ = false;
+  return false;
+}
+
+
+
+#if 0
+namespace OrthancStone
+{
+  class TestSleep : public IObserver
+  {
+  private:
+    WebAssemblyOracle&  oracle_;
+
+    void Schedule()
+    {
+      oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000));
+    }
+    
+    void Handle(const SleepOracleCommand::TimeoutMessage& message)
+    {
+      LOG(INFO) << "TIMEOUT";
+      Schedule();
+    }
+    
+  public:
+    TestSleep(MessageBroker& broker,
+              WebAssemblyOracle& oracle) :
+      IObserver(broker),
+      oracle_(oracle)
+    {
+      oracle.RegisterObserverCallback(
+        new Callable<TestSleep, SleepOracleCommand::TimeoutMessage>
+        (*this, &TestSleep::Handle));
+
+      LOG(INFO) << "STARTING";
+      Schedule();
+    }
+  };
+
+  //static TestSleep testSleep(broker_, oracle_);
+}
+#endif
+
+static bool GetArgument(std::string& value,
+                        const std::string& key)
+{
+  std::map<std::string, std::string>::const_iterator found = arguments_.find(key);
+
+  if (found == arguments_.end())
+  {
+    return false;
+  }
+  else
+  {
+    value = found->second;
+    return true;
+  }
+}
+
+
+extern "C"
+{
+  int main(int argc, char const *argv[]) 
+  {
+    OrthancStone::StoneInitialize();
+    Orthanc::Logging::EnableInfoLevel(true);
+    // Orthanc::Logging::EnableTraceLevel(true);
+    EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded")););
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetArgument(const char* key, const char* value)
+  {
+    // This is called for each GET argument (cf. "app.js")
+    LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]";
+    arguments_[key] = value;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void Initialize()
+  {
+    try
+    {
+      oracle_.SetOrthancRoot("..");
+      
+      loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_));
+    
+      widget1_.reset(new OrthancStone::ViewportManager("mycanvas1", OrthancStone::VolumeProjection_Axial));
+      {
+        std::unique_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
+        style->SetLinearInterpolation(true);
+        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
+        widget1_->SetSlicer(0, loader_, *loader_, style.release());
+      }
+      widget1_->UpdateSize();
+
+      widget2_.reset(new OrthancStone::ViewportManager("mycanvas2", OrthancStone::VolumeProjection_Coronal));
+      {
+        std::unique_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
+        style->SetLinearInterpolation(true);
+        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
+        widget2_->SetSlicer(0, loader_, *loader_, style.release());
+      }
+      widget2_->UpdateSize();
+
+      widget3_.reset(new OrthancStone::ViewportManager("mycanvas3", OrthancStone::VolumeProjection_Sagittal));
+      {
+        std::unique_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
+        style->SetLinearInterpolation(true);
+        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
+        widget3_->SetSlicer(0, loader_, *loader_, style.release());
+      }
+      widget3_->UpdateSize();
+
+      emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnWindowResize); // DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 !!
+
+      emscripten_set_wheel_callback("#mycanvas1", widget1_.get(), false, OnMouseWheel);
+      emscripten_set_wheel_callback("#mycanvas2", widget2_.get(), false, OnMouseWheel);
+      emscripten_set_wheel_callback("#mycanvas3", widget3_.get(), false, OnMouseWheel);
+
+      emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyDown);
+      emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyUp);
+    
+      //emscripten_request_animation_frame_loop(OnAnimationFrame, NULL);
+
+      std::string ct;
+      if (GetArgument(ct, "ct"))
+      {
+        //loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");
+        loader_->LoadSeries(ct);
+      }
+      else
+      {
+        LOG(ERROR) << "No Orthanc identifier for the CT series was provided";
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception during Initialize(): " << e.What();
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,196 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../Common/RtViewerApp.h"
+#include "../../Common/RtViewerView.h"
+#include "../../Common/SampleHelpers.h"
+
+// Stone of Orthanc includes
+#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h"
+#include "../../../Sources/StoneException.h"
+#include "../../../Sources/StoneInitialization.h"
+#include "../../../Sources/Viewport/WebGLViewport.h"
+//#include "../../../Sources/OpenGL/WebAssemblyOpenGLContext.h"
+
+#include <Toolbox.h>
+
+#include <boost/program_options.hpp>
+#include <boost/shared_ptr.hpp>
+// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+
+
+#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
+  EM_ASM(                                                       \
+    const customEvent = document.createEvent("CustomEvent");    \
+    customEvent.initCustomEvent(name, false, false, undefined); \
+    window.dispatchEvent(customEvent);                          \
+    );
+
+#define EXTERN_CATCH_EXCEPTIONS                         \
+  catch (Orthanc::OrthancException& e)                  \
+  {                                                     \
+    LOG(ERROR) << "OrthancException: " << e.What();     \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (OrthancStone::StoneException& e)               \
+  {                                                     \
+    LOG(ERROR) << "StoneException: " << e.What();       \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (std::exception& e)                             \
+  {                                                     \
+    LOG(ERROR) << "Runtime error: " << e.what();        \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (...)                                           \
+  {                                                     \
+    LOG(ERROR) << "Native exception";                   \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }
+
+namespace OrthancStone
+{
+  //   typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData);
+
+  EM_BOOL RtViewerView_Scroll(int eventType, 
+                              const EmscriptenWheelEvent* wheelEvent, 
+                              void* userData)
+  {
+    RtViewerView* that = reinterpret_cast<RtViewerView*>(userData);
+
+    int delta = 0;
+    if (wheelEvent->deltaY < 0)
+      delta = -1;
+    if (wheelEvent->deltaY > 0)
+      delta = 1;
+
+    that->Scroll(delta);
+
+    return 1;
+  }
+  
+  boost::shared_ptr<IViewport> RtViewerView::CreateViewport(
+    const std::string& canvasId)
+  {
+    boost::shared_ptr<IViewport> viewport = WebGLViewport::Create(canvasId);
+
+    void* userData = reinterpret_cast<void*>(this);
+
+    // manually add the mouse wheel handler
+
+    std::string selector = "#" + canvasId;
+
+    emscripten_set_wheel_callback_on_thread(
+      selector.c_str(),
+      userData,
+      false,
+      &RtViewerView_Scroll,
+      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
+
+    return viewport;
+  }
+
+  void RtViewerView::TakeScreenshot(const std::string& target,
+                                   unsigned int canvasWidth,
+                                   unsigned int canvasHeight)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+
+  void RtViewerApp::RunWasm()
+  {
+    loadersContext_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
+
+    // we are in WASM --> downcast to concrete type
+    boost::shared_ptr<WebAssemblyLoadersContext> loadersContext = 
+      boost::dynamic_pointer_cast<WebAssemblyLoadersContext>(loadersContext_);
+
+    if (HasArgument("orthanc"))
+      loadersContext->SetLocalOrthanc(GetArgument("orthanc"));
+    else 
+      loadersContext->SetLocalOrthanc("..");
+
+    loadersContext->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
+
+    CreateLoaders();
+    
+    CreateView("RtViewer_Axial", VolumeProjection_Axial);
+    CreateView("RtViewer_Coronal", VolumeProjection_Coronal);
+    CreateView("RtViewer_Sagittal", VolumeProjection_Sagittal);
+
+    for (size_t i = 0; i < views_.size(); ++i)
+    {
+      views_[i]->PrepareViewport();
+    }
+
+    StartLoaders();
+  }
+}
+
+extern "C"
+{
+  boost::shared_ptr<OrthancStone::RtViewerApp> g_app;
+
+  int main(int argc, char const *argv[]) 
+  {
+    try
+    {
+      OrthancStone::StoneInitialize();
+      Orthanc::Logging::Initialize();
+      Orthanc::Logging::EnableTraceLevel(true);
+
+      LOG(WARNING) << "Initializing native Stone";
+
+      LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__
+                   << "." << __EMSCRIPTEN_minor__
+                   << "." << __EMSCRIPTEN_tiny__;
+
+      LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness());
+
+      g_app = OrthancStone::RtViewerApp::Create();
+  
+      DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized");
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void Initialize(const char* canvasId)
+  {
+    try
+    {
+      g_app->RunWasm();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetArgument(const char* key, const char* value)
+  {
+    // This is called for each GET argument (cf. "app.js")
+    LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]";
+    g_app->SetArgument(key, value);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,85 @@
+
+// This object wraps the functions exposed by the wasm module
+
+const WasmModuleWrapper = function() {
+  this._InitializeViewport = undefined;
+};
+
+WasmModuleWrapper.prototype.Setup = function(Module) {
+  this._SetArgument = Module.cwrap('SetArgument', null, [ 'string', 'string' ]);
+  this._Initialize = Module.cwrap('Initialize', null, [ 'string' ]);
+};
+
+WasmModuleWrapper.prototype.SetArgument = function(key, value) {
+  this._SetArgument(key, value);
+};
+
+WasmModuleWrapper.prototype.Initialize = function(canvasId) {
+  this._Initialize(canvasId);
+};
+
+var wasmModuleWrapper = new WasmModuleWrapper();
+
+$(document).ready(function() {
+
+  window.addEventListener('WasmModuleInitialized', function() {
+
+    // bind the C++ global functions
+    wasmModuleWrapper.Setup(Module);
+
+    console.warn('Native C++ module initialized');
+
+    // Loop over the GET arguments
+    var parameters = window.location.search.substr(1);
+    if (parameters != null && parameters != '') {
+      var tokens = parameters.split('&');
+      for (var i = 0; i < tokens.length; i++) {
+        var arg = tokens[i].split('=');
+        if (arg.length == 2) {
+          // Send each GET argument to WebAssembly
+          wasmModuleWrapper.SetArgument(arg[0], decodeURIComponent(arg[1]));
+        }
+      }
+    }
+    wasmModuleWrapper.Initialize();
+  });
+
+  window.addEventListener('StoneException', function() {
+    alert('Exception caught in C++ code');
+  });    
+
+  var scriptSource;
+
+  if ('WebAssembly' in window) {
+    console.warn('Loading WebAssembly');
+    scriptSource = 'RtViewerWasm.js';
+  } else {
+    console.error('Your browser does not support WebAssembly!');
+  }
+
+  // Option 1: Loading script using plain HTML
+  
+  /*
+    var script = document.createElement('script');
+    script.src = scriptSource;
+    script.type = 'text/javascript';
+    document.body.appendChild(script);
+  */
+
+  // Option 2: Loading script using AJAX (gives the opportunity to
+  // report explicit errors)
+  
+  axios.get(scriptSource)
+    .then(function (response) {
+      var script = document.createElement('script');
+      script.innerHTML = response.data;
+      script.type = 'text/javascript';
+      document.body.appendChild(script);
+    })
+    .catch(function (error) {
+      alert('Cannot load the WebAssembly framework');
+    });
+});
+
+// http://localhost:9979/stone-rtviewer/index.html?loglevel=trace&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,27 @@
+
+const Stone = function() {
+  this._InitializeViewport = undefined;
+  this._LoadOrthanc = undefined;
+  this._LoadDicomWeb = undefined;
+};
+
+Stone.prototype.Setup = function(Module) {
+  this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]);
+  this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]);
+  this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]);
+};
+
+Stone.prototype.InitializeViewport = function(canvasId) {
+  this._InitializeViewport(canvasId);
+};
+
+Stone.prototype.LoadOrthanc = function(instance, frame) {
+  this._LoadOrthanc(instance, frame);
+};
+
+Stone.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) {
+  this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame);
+};
+
+var stone = new Stone();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/RtViewer/index.html	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,64 @@
+<!doctype html>
+<html lang="en-us">
+  <head>
+    <title>Stone of Orthanc Single Frame Viewer </title>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+    
+    <style>
+      html, body {
+      width: 100%;
+      height: 100%;
+      margin: 0px;
+      border: 0;
+      overflow: hidden; /*  Disable scrollbars */
+      display: block;  /* No floating content on sides */
+      }
+
+      #RtViewer_Axial {
+        position: absolute;
+        left: 0%;
+        top: 0%;
+        background-color: red;
+        width: 50%;
+        height: 100%;
+      }
+
+      #RtViewer_Coronal {
+        position: absolute;
+        left: 50%;
+        top: 0%;
+        background-color: green;
+        width: 50%;
+        height: 50%;
+      }
+
+      #RtViewer_Sagittal {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        background-color: blue;
+        width: 50%;
+        height: 50%;
+      }
+    </style>
+  </head>
+  <body>
+    <canvas id="RtViewer_Axial" oncontextmenu="return false;"></canvas>
+    <canvas id="RtViewer_Coronal" oncontextmenu="return false;"></canvas>
+    <canvas id="RtViewer_Sagittal" oncontextmenu="return false;"></canvas>
+
+    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
+
+    <script src="RtViewerWasmApp.js"></script>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,70 @@
+cmake_minimum_required(VERSION 2.8.3)
+
+project(SingleFrameViewer)
+
+# Configuration of the Emscripten compiler for WebAssembly target
+# ---------------------------------------------------------------
+set(USE_WASM ON CACHE BOOL "")
+
+set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
+
+set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
+endif()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
+add_definitions(
+  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
+)
+
+# Stone of Orthanc configuration
+# ---------------------------------------------------------------
+set(ALLOW_DOWNLOADS ON)
+
+include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
+
+SET(ENABLE_DCMTK OFF)  # Not necessary
+SET(ENABLE_GOOGLE_TEST OFF)
+SET(ENABLE_LOCALE ON)  # Necessary for text rendering
+SET(ENABLE_WASM ON)
+SET(ORTHANC_SANDBOXED ON)
+
+# this will set up the build system for Stone of Orthanc and will
+# populate the ORTHANC_STONE_SOURCES CMake variable
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+# Define the WASM module
+# ---------------------------------------------------------------
+add_executable(SingleFrameViewerWasm
+  SingleFrameViewer.cpp
+  ${ORTHANC_STONE_SOURCES}
+  )
+
+# Declare installation files for the module
+# ---------------------------------------------------------------
+install(
+  TARGETS SingleFrameViewerWasm
+  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
+  )
+
+# Declare installation files for the companion files (web scaffolding)
+# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
+# (the generated JS loader for the WASM module) is handled by the `install1`
+# section above: it is considered to be the binary output of 
+# the linker.
+# ---------------------------------------------------------------
+install(
+  FILES
+  ${CMAKE_SOURCE_DIR}/SingleFrameViewerApp.js
+  ${CMAKE_SOURCE_DIR}/index.html
+  ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm
+  DESTINATION ${CMAKE_INSTALL_PREFIX}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,39 @@
+{
+  "configurations": [
+    {
+      "name": "wasm32-RelWithDebInfo",
+      "generator": "Ninja",
+      "configurationType": "RelWithDebInfo",
+      //"inheritEnvironments": [ "msvc_x64_x64" ],
+      "buildRoot": "${projectDir}\\out\\build\\${name}",
+      "installRoot": "${projectDir}\\out\\install\\${name}",
+      "cmakeCommandArgs": "",
+      "buildCommandArgs": "-v",
+      "ctestCommandArgs": "",
+      "cmakeToolchain": "C:/osi/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
+      "intelliSenseMode": "windows-clang-x64",
+      "variables": [
+        {
+          "name": "CMAKE_BUILD_TYPE",
+          "value": "RelWithDebInfo",
+          "type": "STRING"
+        },
+        {
+          "name": "ALLOW_DOWNLOADS",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "STATIC_BUILD",
+          "value": "True",
+          "type": "BOOL"
+        },
+        {
+          "name": "OPENSSL_NO_CAPIENG",
+          "value": "True",
+          "type": "BOOL"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,169 @@
+/**
+ * Stone of Orthanc
+ * 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 "SingleFrameViewerApplication.h"
+
+#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h"
+#include "../../../Sources/StoneException.h"
+#include "../../../Sources/StoneInitialization.h"
+
+#include <Compatibility.h>  // For std::unique_ptr<>
+#include <Toolbox.h>
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+
+
+#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
+  EM_ASM(                                                       \
+    const customEvent = document.createEvent("CustomEvent");    \
+    customEvent.initCustomEvent(name, false, false, undefined); \
+    window.dispatchEvent(customEvent);                          \
+    );
+
+#define EXTERN_CATCH_EXCEPTIONS                         \
+  catch (Orthanc::OrthancException& e)                  \
+  {                                                     \
+    LOG(ERROR) << "OrthancException: " << e.What();     \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (OrthancStone::StoneException& e)               \
+  {                                                     \
+    LOG(ERROR) << "StoneException: " << e.What();       \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (std::exception& e)                             \
+  {                                                     \
+    LOG(ERROR) << "Runtime error: " << e.what();        \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (...)                                           \
+  {                                                     \
+    LOG(ERROR) << "Native exception";                   \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }
+
+
+
+namespace OrthancStone
+{
+}
+
+static std::unique_ptr<OrthancStone::WebAssemblyLoadersContext>  context_;
+static boost::shared_ptr<OrthancStone::Application>  application_;
+
+extern "C"
+{
+  int main(int argc, char const *argv[]) 
+  {
+    try
+    {
+      Orthanc::Logging::Initialize();
+      Orthanc::Logging::EnableInfoLevel(true);
+      //Orthanc::Logging::EnableTraceLevel(true);
+      LOG(WARNING) << "Initializing native Stone";
+
+      LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__
+                   << "." << __EMSCRIPTEN_minor__
+                   << "." << __EMSCRIPTEN_tiny__;
+
+      LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness());
+      context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
+      context_->SetLocalOrthanc("..");
+      context_->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
+  
+      DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized");
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+
+    return 0;
+  }
+  
+  EMSCRIPTEN_KEEPALIVE
+  void InitializeViewport(const char* canvasId)
+  {
+    try
+    {
+      if (context_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "The loaders context is not available yet");
+      }
+      
+      if (application_.get() != NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "Only one single viewport is available for this application");
+      }
+
+      boost::shared_ptr<OrthancStone::WebGLViewport> viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId));
+      application_ = OrthancStone::Application::Create(*context_, viewport);
+
+      {
+        OrthancStone::WebGLViewportsRegistry::Accessor accessor(
+          OrthancStone::GetWebGLViewportsRegistry(), canvasId);
+
+        if (accessor.IsValid())
+        {
+          accessor.GetViewport().Invalidate();
+        }
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+  
+  EMSCRIPTEN_KEEPALIVE
+  void LoadFromOrthanc(const char* instance,
+                       int frame)
+  {
+    try
+    {
+      if (application_.get() != NULL)
+      {
+        OrthancStone::DicomSource source;
+        application_->LoadOrthancFrame(source, instance, frame);
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+  
+  EMSCRIPTEN_KEEPALIVE
+  void LoadFromDicomWeb(const char* server,
+                        const char* studyInstanceUid,
+                        const char* seriesInstanceUid,
+                        const char* sopInstanceUid,
+                        int frame)
+  {
+    try
+    {
+      if (application_.get() != NULL)
+      {
+        OrthancStone::DicomSource source;
+        source.SetDicomWebThroughOrthancSource(server);
+        application_->LoadDicomWebFrame(source, studyInstanceUid, seriesInstanceUid,
+                                        sopInstanceUid, frame);
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,74 @@
+
+// This object wraps the functions exposed by the wasm module
+
+const WasmModuleWrapper = function() {
+  this._InitializeViewport = undefined;
+  this._LoadFromOrthanc = undefined;
+};
+
+WasmModuleWrapper.prototype.Setup = function(Module) {
+  this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]);
+  this._LoadFromOrthanc = Module.cwrap('LoadFromOrthanc', null, [ 'string', 'int' ]);
+};
+
+WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) {
+  this._InitializeViewport(canvasId);
+};
+
+WasmModuleWrapper.prototype.LoadFromOrthanc = function(instance, frame) {
+  this._LoadFromOrthanc(instance, frame);
+};
+
+var wasmModuleWrapper = new WasmModuleWrapper();
+
+$(document).ready(function() {
+
+  window.addEventListener('WasmModuleInitialized', function() {
+    wasmModuleWrapper.Setup(Module);
+    console.warn('Native C++ module initialized');
+
+    wasmModuleWrapper.InitializeViewport('viewport');
+  });
+
+  window.addEventListener('StoneException', function() {
+    alert('Exception caught in C++ code');
+  });    
+
+  var scriptSource;
+
+  if ('WebAssembly' in window) {
+    console.warn('Loading WebAssembly');
+    scriptSource = 'SingleFrameViewerWasm.js';
+  } else {
+    console.error('Your browser does not support WebAssembly!');
+  }
+
+  // Option 1: Loading script using plain HTML
+  
+  /*
+    var script = document.createElement('script');
+    script.src = scriptSource;
+    script.type = 'text/javascript';
+    document.body.appendChild(script);
+  */
+
+  // Option 2: Loading script using AJAX (gives the opportunity to
+  // report explicit errors)
+  
+  axios.get(scriptSource)
+    .then(function (response) {
+      var script = document.createElement('script');
+      script.innerHTML = response.data;
+      script.type = 'text/javascript';
+      document.body.appendChild(script);
+    })
+    .catch(function (error) {
+      alert('Cannot load the WebAssembly framework');
+    });
+});
+
+
+$('#orthancLoad').click(function() {
+  wasmModuleWrapper.LoadFromOrthanc($('#orthancInstance').val(),
+                    $('#orthancFrame').val());
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,502 @@
+/**
+ * Stone of Orthanc
+ * 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 "../../../Sources/Loaders/DicomResourcesLoader.h"
+#include "../../../Sources/Loaders/ILoadersContext.h"
+#include "../../../Sources/Loaders/SeriesFramesLoader.h"
+#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h"
+#include "../../../Sources/Viewport/IViewport.h"
+
+#include <Compatibility.h>  // For std::unique_ptr<>
+
+#include <boost/make_shared.hpp>
+
+
+namespace OrthancStone
+{
+  class Application : public ObserverBase<Application>
+  {
+  private:
+    ILoadersContext&                         context_;
+    boost::shared_ptr<IViewport>             viewport_;
+    boost::shared_ptr<DicomResourcesLoader>  dicomLoader_;
+    boost::shared_ptr<SeriesFramesLoader>    framesLoader_;
+
+    Application(ILoadersContext& context,
+                boost::shared_ptr<IViewport> viewport) : 
+      context_(context),
+      viewport_(viewport)
+    {
+    }
+
+    void Handle(const SeriesFramesLoader::FrameLoadedMessage& message)
+    {
+      LOG(INFO) << "Frame decoded! "
+                << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight()
+                << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat());
+
+      std::unique_ptr<TextureBaseSceneLayer> layer(
+        message.GetInstanceParameters().CreateTexture(message.GetImage()));
+      layer->SetLinearInterpolation(true);
+
+      {
+        std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+        lock->GetController().GetScene().SetLayer(0, layer.release());
+        lock->GetCompositor().FitContent(lock->GetController().GetScene());
+        lock->Invalidate();
+      }
+    }
+
+    void Handle(const DicomResourcesLoader::SuccessMessage& message)
+    {
+      if (message.GetResources()->GetSize() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+   
+      //message.GetResources()->GetResource(0).Print(stdout);
+
+      {
+        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+        SeriesFramesLoader::Factory f(*message.GetResources());
+
+        framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock));
+        Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle);
+
+        assert(message.HasUserPayload());
+        const Orthanc::SingleValueObject<unsigned int>& payload =
+          dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(message.GetUserPayload());
+
+        LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue();
+        framesLoader_->ScheduleLoadFrame(
+          0, message.GetDicomSource(), payload.GetValue(),
+          message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */,
+          NULL);
+      }
+    }
+
+  public:
+    static boost::shared_ptr<Application> Create(ILoadersContext& context,
+                                                 boost::shared_ptr<IViewport> viewport)
+    {
+      boost::shared_ptr<Application> application(new Application(context, viewport));
+
+      {
+        std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
+        application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
+      }
+
+      application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle);
+
+      return application;
+    }
+
+    void LoadOrthancFrame(const DicomSource& source,
+                          const std::string& instanceId,
+                          unsigned int frame)
+    {
+      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+
+      dicomLoader_->ScheduleLoadOrthancResource(
+        boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 
+        0, source, Orthanc::ResourceType_Instance, instanceId,
+        new Orthanc::SingleValueObject<unsigned int>(frame));
+    }
+
+    void LoadDicomWebFrame(const DicomSource& source,
+                           const std::string& studyInstanceUid,
+                           const std::string& seriesInstanceUid,
+                           const std::string& sopInstanceUid,
+                           unsigned int frame)
+    {
+      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+
+      // We first must load the "/metadata" to know the number of frames
+      dicomLoader_->ScheduleGetDicomWeb(
+        boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source,
+        "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata",
+        new Orthanc::SingleValueObject<unsigned int>(frame));
+    }
+
+    void FitContent()
+    {
+      std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+      lock->GetCompositor().FitContent(lock->GetController().GetScene());
+      lock->Invalidate();
+    }
+  };
+
+
+
+  class IWebViewerLoadersObserver : public boost::noncopyable
+  {
+  public:
+    virtual ~IWebViewerLoadersObserver()
+    {
+    }
+
+    virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0;
+
+    virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid,
+                                       const std::string& seriesInstanceUid,
+                                       SeriesThumbnailType type) = 0;
+  };
+  
+
+  class WebViewerLoaders : public ObserverBase<WebViewerLoaders>
+  {
+  private:
+    static const int PRIORITY_ADD_RESOURCES = 0;
+    static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100;
+
+    enum Type
+    {
+      Type_Orthanc = 1,
+      Type_DicomWeb = 2
+    };
+
+    ILoadersContext&                           context_;
+    std::unique_ptr<IWebViewerLoadersObserver>   observer_;
+    bool                                       loadThumbnails_;
+    DicomSource                                source_;
+    std::set<std::string>                      scheduledSeries_;
+    std::set<std::string>                      scheduledThumbnails_;
+    std::set<std::string>                      scheduledStudies_;
+    boost::shared_ptr<LoadedDicomResources>    loadedSeries_;
+    boost::shared_ptr<LoadedDicomResources>    loadedStudies_;
+    boost::shared_ptr<DicomResourcesLoader>    resourcesLoader_;
+    boost::shared_ptr<SeriesThumbnailsLoader>  thumbnailsLoader_;
+
+    WebViewerLoaders(ILoadersContext& context,
+                     IWebViewerLoadersObserver* observer) :
+      context_(context),
+      observer_(observer),
+      loadThumbnails_(false)
+    {
+      loadedSeries_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
+      loadedStudies_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID);
+    }
+
+    static Orthanc::IDynamicObject* CreatePayload(Type type)
+    {
+      return new Orthanc::SingleValueObject<Type>(type);
+    }
+    
+    void HandleThumbnail(const SeriesThumbnailsLoader::SuccessMessage& message)
+    {
+      if (observer_.get() != NULL)
+      {
+        observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(),
+                                         message.GetSeriesInstanceUid(),
+                                         message.GetType());
+      }
+    }
+    
+    void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message)
+    {
+      LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
+
+      switch (dynamic_cast<const Orthanc::SingleValueObject<Type>&>(message.GetUserPayload()).GetValue())
+      {
+        case Type_DicomWeb:
+        {          
+          for (size_t i = 0; i < loadedSeries_->GetSize(); i++)
+          {
+            std::string study;
+            if (loadedSeries_->GetResource(i).LookupStringValue(
+                  study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
+                loadedStudies_->HasResource(study))
+            {
+              Orthanc::DicomMap m;
+              m.Assign(loadedSeries_->GetResource(i));
+              loadedStudies_->MergeResource(m, study);
+              series.AddResource(m);
+            }
+          }
+
+          break;
+        }
+
+        case Type_Orthanc:
+        {          
+          for (size_t i = 0; i < message.GetResources()->GetSize(); i++)
+          {
+            series.AddResource(message.GetResources()->GetResource(i));
+          }
+
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      if (loadThumbnails_ &&
+          (!source_.IsDicomWeb() ||
+           source_.HasDicomWebRendered()))
+      {
+        for (size_t i = 0; i < series.GetSize(); i++)
+        {
+          std::string patientId, studyInstanceUid, seriesInstanceUid;
+          if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) &&
+              series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
+              series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) &&
+              scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end())
+          {
+            scheduledThumbnails_.insert(seriesInstanceUid);
+            thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid);
+          }
+        }
+      }
+
+      if (observer_.get() != NULL &&
+          series.GetSize() > 0)
+      {
+        observer_->SignalSeriesUpdated(series);
+      }
+    }
+
+    void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      Json::Value body;
+      message.ParseJsonBody(body);
+
+      if (body.type() != Json::arrayValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+      else
+      {
+        for (Json::Value::ArrayIndex i = 0; i < body.size(); i++)
+        {
+          if (body[i].type() == Json::stringValue)
+          {
+            AddOrthancSeries(body[i].asString());
+          }
+          else
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+          }
+        }
+      }
+    }
+
+  public:
+    static boost::shared_ptr<WebViewerLoaders> Create(ILoadersContext& context,
+                                                      const DicomSource& source,
+                                                      bool loadThumbnails,
+                                                      IWebViewerLoadersObserver* observer)
+    {
+      boost::shared_ptr<WebViewerLoaders> application(new WebViewerLoaders(context, observer));
+      application->source_ = source;
+      application->loadThumbnails_ = loadThumbnails;
+
+      {
+        std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
+
+        application->resourcesLoader_ = DicomResourcesLoader::Create(*lock);
+
+        {
+          SeriesThumbnailsLoader::Factory f;
+          f.SetPriority(PRIORITY_THUMBNAILS);
+          application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock));
+        }
+
+        application->Register<OrthancRestApiCommand::SuccessMessage>(
+          lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi);
+
+        application->Register<DicomResourcesLoader::SuccessMessage>(
+          *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources);
+
+        application->Register<SeriesThumbnailsLoader::SuccessMessage>(
+          *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail);
+
+        lock->AddLoader(application);
+      }
+
+      return application;
+    }
+    
+    void AddDicomAllSeries()
+    {
+      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+
+      if (source_.IsDicomWeb())
+      {
+        resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
+                                              "/series", CreatePayload(Type_DicomWeb));
+        resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
+                                              "/studies", CreatePayload(Type_DicomWeb));
+      }
+      else if (source_.IsOrthanc())
+      {
+        std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+        command->SetMethod(Orthanc::HttpMethod_Get);
+        command->SetUri("/series");
+        lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+    
+    void AddDicomStudy(const std::string& studyInstanceUid)
+    {
+      // Avoid adding twice the same study
+      if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
+      {
+        scheduledStudies_.insert(studyInstanceUid);
+
+        if (source_.IsDicomWeb())
+        {
+          Orthanc::DicomMap filter;
+          filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
+          
+          std::set<Orthanc::DicomTag> tags;
+          
+          {
+            std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+            
+            resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
+                                           Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb));
+            
+            resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
+                                           Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb));
+          }
+        }
+        else if (source_.IsOrthanc())
+        {
+          std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+          command->SetMethod(Orthanc::HttpMethod_Post);
+          command->SetUri("/tools/find");
+
+          Json::Value body;
+          body["Level"] = "Series";
+          body["Query"] = Json::objectValue;
+          body["Query"]["StudyInstanceUID"] = studyInstanceUid;
+          command->SetBody(body);
+
+          {
+            std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+            lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
+          }
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }        
+      }
+    }
+    
+    void AddDicomSeries(const std::string& studyInstanceUid,
+                        const std::string& seriesInstanceUid)
+    {
+      std::set<Orthanc::DicomTag> tags;
+
+      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+
+      if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
+      {
+        scheduledStudies_.insert(studyInstanceUid);
+          
+        if (source_.IsDicomWeb())
+        {
+          Orthanc::DicomMap filter;
+          filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
+          
+          resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
+                                         Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb));
+        }
+      }
+
+      if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end())
+      {
+        scheduledSeries_.insert(seriesInstanceUid);
+
+        if (source_.IsDicomWeb())
+        {
+          Orthanc::DicomMap filter;
+          filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
+          filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false);
+          
+          resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
+                                         Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb));
+        }
+        else if (source_.IsOrthanc())
+        {
+          std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+          command->SetMethod(Orthanc::HttpMethod_Post);
+          command->SetUri("/tools/find");
+
+          Json::Value body;
+          body["Level"] = "Series";
+          body["Query"] = Json::objectValue;
+          body["Query"]["StudyInstanceUID"] = studyInstanceUid;
+          body["Query"]["SeriesInstanceUID"] = seriesInstanceUid;
+          command->SetBody(body);
+
+          lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+      }
+    }
+
+    void AddOrthancStudy(const std::string& orthancId)
+    {
+      if (source_.IsOrthanc())
+      {
+        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+        resourcesLoader_->ScheduleLoadOrthancResources(
+          loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
+          Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series,
+          CreatePayload(Type_Orthanc));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
+                                        "Only applicable to Orthanc DICOM sources");
+      }
+    }
+
+    void AddOrthancSeries(const std::string& orthancId)
+    {
+      if (source_.IsOrthanc())
+      {
+        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+        resourcesLoader_->ScheduleLoadOrthancResource(
+          loadedSeries_, PRIORITY_ADD_RESOURCES,
+          source_, Orthanc::ResourceType_Series, orthancId,
+          CreatePayload(Type_Orthanc));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
+                                        "Only applicable to Orthanc DICOM sources");
+      }
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/index.html	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,43 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <title>Stone of Orthanc Single Frame Viewer </title>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
+
+    <style>
+      canvas {
+      background-color: green;
+      width : 100%;
+      height : 512px;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>Viewport</h1>
+
+    <canvas id="viewport" >
+    </canvas>
+
+    <h1>Load from Orthanc</h1>
+    <p>
+      Orthanc instance: <input type="text" id="orthancInstance" size="80"
+                               value="5eb2dd5f-3fca21a8-fa7565fd-63e112ae-344830a4">
+    </p>
+    <p>
+      Frame number: <input type="text" id="orthancFrame" value="0">
+    </p>
+    <p>
+      <button id="orthancLoad">Load</button>
+    </p>
+
+    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
+    
+    <script src="SingleFrameViewerApp.js"></script>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/docker-build.sh	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -ex
+
+IMAGE=jodogne/wasm-builder:1.39.17-upstream
+
+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}/wasm-binaries
+
+docker run -t ${DOCKER_FLAGS} --rm \
+    --user $(id -u):$(id -g) \
+    -v ${ROOT_DIR}:/source:ro \
+    -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \
+    bash /source/OrthancStone/Samples/WebAssembly/docker-internal.sh $1
+
+ls -lR ${ROOT_DIR}/wasm-binaries/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/WebAssembly/docker-internal.sh	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -ex
+
+source /opt/emsdk/emsdk_env.sh
+
+# Use a folder that is writeable by non-root users for the Emscripten cache
+export EM_CACHE=/tmp/emscripten-cache
+
+# Get the Orthanc framework
+cd /tmp/
+hg clone https://hg.orthanc-server.com/orthanc/
+
+# Make a copy of the read-only folder containing the source code into
+# a writeable folder, because of "DownloadPackage.cmake" that writes
+# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt"
+cd /source
+hg clone /source /tmp/source-writeable
+
+mkdir /tmp/build
+cd /tmp/build
+
+cmake /tmp/source-writeable/OrthancStone/Samples/WebAssembly \
+      -DCMAKE_BUILD_TYPE=$1 \
+      -DCMAKE_INSTALL_PREFIX=/target \
+      -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
+      -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \
+      -DSTATIC_BUILD=ON \
+      -G Ninja
+
+ninja -j2 install
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/build-wasm-samples.sh	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# usage:
+# to build the samples in RelWithDebInfo:
+# ./build-wasm-samples.sh
+#
+# to build the samples in Release:
+# ./build-wasm-samples.sh Release
+
+set -e
+
+if [ ! -d "WebAssembly" ]; then
+  echo "This script must be run from the Samples folder one level below orthanc-stone"
+  exit 1
+fi
+
+
+currentDir=$(pwd)
+samplesRootDir=$(pwd)
+devrootDir=$(pwd)/../../
+
+buildType=${1:-RelWithDebInfo}
+buildFolderName="$devrootDir/out/build-stone-wasm-samples-$buildType"
+installFolderName="$devrootDir/out/install-stone-wasm-samples-$buildType"
+
+mkdir -p $buildFolderName
+# change current folder to the build folder
+pushd $buildFolderName
+
+# configure the environment to use Emscripten
+source ~/apps/emsdk/emsdk_env.sh
+
+emcmake cmake -G "Ninja" \
+  -DCMAKE_BUILD_TYPE=$buildType \
+  -DCMAKE_INSTALL_PREFIX=$installFolderName \
+  -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \
+  $samplesRootDir/WebAssembly
+
+# perform build + installation
+ninja
+ninja install
+
+# restore the original working folder
+popd
+
+echo "If all went well, the output files can be found in $installFolderName:"
+
+ls $installFolderName
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/COPYING	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Plugin/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,97 @@
+cmake_minimum_required(VERSION 2.8.3)
+
+project(StoneWebViewerPlugin)
+
+set(ORTHANC_PLUGIN_VERSION "mainline")
+
+if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+
+
+set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../wasm-binaries/StoneWebViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder")
+
+# 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 \"system\", \"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_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked")
+mark_as_advanced(ORTHANC_FRAMEWORK_STATIC)
+
+
+# Download and setup the Orthanc framework
+include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
+
+include_directories(${ORTHANC_FRAMEWORK_ROOT})
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES})
+
+else()
+  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+  set(ENABLE_MODULE_IMAGES OFF)
+  set(ENABLE_MODULE_JOBS OFF)
+  set(ENABLE_MODULE_DICOM OFF)
+  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+endif()
+
+include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginsExports.cmake)
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  include_directories(${CMAKE_SOURCE_DIR}/../Resources/OrthancSdk-1.0.0)
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
+  -DPLUGIN_NAME="stone-webviewer"
+  )
+
+
+EmbedResources(
+  # Folders
+  IMAGES                 ${STONE_BINARIES}/img/
+  WEB_APPLICATION        ${CMAKE_SOURCE_DIR}/../WebApplication
+
+  # Individual files
+  ORTHANC_EXPLORER       ${CMAKE_SOURCE_DIR}/OrthancExplorer.js
+  STONE_WEB_VIEWER_JS    ${STONE_BINARIES}/StoneWebViewer.js
+  STONE_WEB_VIEWER_WASM  ${STONE_BINARIES}/StoneWebViewer.wasm
+  STONE_WRAPPER          ${STONE_BINARIES}/stone.js
+  )
+
+add_library(StoneWebViewer SHARED
+  Plugin.cpp
+  ${AUTOGENERATED_SOURCES}
+  ${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
+  ${ORTHANC_CORE_SOURCES}
+  )
+
+set_target_properties(StoneWebViewer PROPERTIES 
+  VERSION ${ORTHANC_PLUGIN_VERSION} 
+  SOVERSION ${ORTHANC_PLUGIN_VERSION})
+
+install(
+  TARGETS StoneWebViewer
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Plugin/OrthancExplorer.js	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,57 @@
+$('#study').live('pagebeforecreate', function() {
+  var b = $('<a>')
+      .attr('data-role', 'button')
+      .attr('href', '#')
+      .attr('data-icon', 'search')
+      .attr('data-theme', 'e')
+      .text('Stone Web Viewer');
+
+  b.insertBefore($('#study-delete').parent().parent());
+  b.click(function() {
+    if ($.mobile.pageData) {
+      $.ajax({
+        url: '../studies/' + $.mobile.pageData.uuid,
+        dataType: 'json',
+        cache: false,
+        success: function(study) {
+          var studyInstanceUid = study.MainDicomTags.StudyInstanceUID;
+          window.open('../stone-webviewer/index.html?study=' + studyInstanceUid);
+        }
+      });      
+    }
+  });
+});
+
+
+$('#series').live('pagebeforecreate', function() {
+  var b = $('<a>')
+      .attr('data-role', 'button')
+      .attr('href', '#')
+      .attr('data-icon', 'search')
+      .attr('data-theme', 'e')
+      .text('Stone Web Viewer');
+
+  b.insertBefore($('#series-delete').parent().parent());
+  b.click(function() {
+    if ($.mobile.pageData) {
+      $.ajax({
+        url: '../series/' + $.mobile.pageData.uuid,
+        dataType: 'json',
+        cache: false,
+        success: function(series) {
+          $.ajax({
+            url: '../studies/' + series.ParentStudy,
+            dataType: 'json',
+            cache: false,
+            success: function(study) {
+              var studyInstanceUid = study.MainDicomTags.StudyInstanceUID;
+              var seriesInstanceUid = series.MainDicomTags.SeriesInstanceUID;
+              window.open('../stone-webviewer/index.html?study=' + studyInstanceUid +
+                          '&series=' + seriesInstanceUid);
+            }
+          });      
+        }
+      });      
+    }
+  });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Plugin/Plugin.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,241 @@
+/**
+ * Stone of Orthanc
+ * 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 "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+
+#include <EmbeddedResources.h>
+
+#include <SystemToolbox.h>
+#include <Toolbox.h>
+
+OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
+                                        OrthancPluginResourceType resourceType,
+                                        const char* resourceId)
+{
+  try
+  {
+    if (changeType == OrthancPluginChangeType_OrthancStarted)
+    {
+      Json::Value info;
+      if (!OrthancPlugins::RestApiGet(info, "/plugins/dicom-web", false))
+      {
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_InternalError,
+          "The Stone Web viewer requires the DICOMweb plugin to be installed");
+      }
+
+      if (info.type() != Json::objectValue ||
+          !info.isMember("ID") ||
+          !info.isMember("Version") ||
+          info["ID"].type() != Json::stringValue ||
+          info["Version"].type() != Json::stringValue ||
+          info["ID"].asString() != "dicom-web")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                        "The DICOMweb plugin is not properly installed");
+      }
+
+      std::string version = info["Version"].asString();
+      if (version != "mainline")
+      {
+        std::vector<std::string> tokens;
+        Orthanc::Toolbox::TokenizeString(tokens, version, '.');
+        if (tokens.size() != 2)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Bad version of the DICOMweb plugin: " + version);
+        }
+
+        int major, minor;
+        
+        try
+        {
+          major = boost::lexical_cast<int>(tokens[0]);
+          minor = boost::lexical_cast<int>(tokens[1]);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Bad version of the DICOMweb plugin: " + version);
+        }
+
+        if (major <= 0 ||
+            (major == 1 && minor <= 1))
+        {
+          throw Orthanc::OrthancException(
+            Orthanc::ErrorCode_InternalError,
+            "The Stone Web viewer requires DICOMweb plugin with version >= 1.2, found: " + version);
+        }
+
+        if (major <= 0 ||
+            (major == 1 && minor == 2))
+        {
+          /**
+           * DICOMweb 1.3 is better than 1.2 for 2 reasons: (1)
+           * MONOCHROME1 images are not properly rendered in DICOMweb
+           * 1.2, and (2) DICOMweb 1.2 cannot transcode images (this
+           * causes issues on JPEG2k images).
+           **/
+          LOG(WARNING) << "The Stone Web viewer has some incompatibilities "
+                       << "with DICOMweb plugin 1.2, consider upgrading the DICOMweb plugin";
+        }
+      }
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception: " << e.What();
+    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+    
+
+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);
+  }
+}
+
+
+template <enum Orthanc::EmbeddedResources::FileResourceId file>
+void ServeEmbeddedFile(OrthancPluginRestOutput* output,
+                       const char* url,
+                       const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url));
+
+    std::string s;
+    Orthanc::EmbeddedResources::GetFileResource(s, file);
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime);
+  }
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    OrthancPlugins::SetGlobalContext(context);
+
+#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2)
+    Orthanc::Logging::InitializePluginContext(context);
+#else
+    Orthanc::Logging::Initialize(context);
+#endif
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context, info);
+      return -1;
+    }
+
+    try
+    {
+      std::string explorer;
+      Orthanc::EmbeddedResources::GetFileResource(
+        explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
+      OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str());
+      
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_WASM> >
+        ("/stone-webviewer/StoneWebViewer.wasm", true);
+      
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_JS> >
+        ("/stone-webviewer/StoneWebViewer.js", true);
+      
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WRAPPER> >
+        ("/stone-webviewer/stone.js", true);
+      
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFolder<Orthanc::EmbeddedResources::IMAGES> >
+        ("/stone-webviewer/img/(.*)", true);
+
+      OrthancPlugins::RegisterRestCallback
+        <ServeEmbeddedFolder<Orthanc::EmbeddedResources::WEB_APPLICATION> >
+        ("/stone-webviewer/(.*)", true);
+
+      OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
+    }
+    catch (...)
+    {
+      OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin");
+      return -1;
+    }
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return PLUGIN_NAME;
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/GenerateImages.py	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+# Stone of Orthanc
+# 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/>.
+
+
+import os
+from PIL import Image
+
+SOURCE = os.path.dirname(os.path.abspath(__file__))
+TARGET = os.path.join(SOURCE, '..', 'WebApplication', 'img')
+
+try:
+    os.makedirs(TARGET)
+except:  # Directory already exists
+    pass
+    
+color = (217, 217, 217, 255)
+border = 3
+width = 32
+height = 32
+
+
+
+image = Image.new('RGBA', (width, height))
+
+for x in range(0, width):
+    for y in range(0, height):
+        image.putpixel((x, y), color)
+
+image.save(os.path.join(TARGET, 'grid1x1.png'), 'PNG')
+
+
+
+image = Image.new('RGBA', (width, height))
+
+for x in range(0, width / 2 - border):
+    for y in range(0, height / 2 - border):
+        image.putpixel((x, y), color)
+    for y in range(height / 2 + border, height):
+        image.putpixel((x, y), color)
+
+for x in range(width / 2 + border, width):
+    for y in range(0, height / 2 - border):
+        image.putpixel((x, y), color)
+    for y in range(height / 2 + border, height):
+        image.putpixel((x, y), color)
+
+image.save(os.path.join(TARGET, 'grid2x2.png'), 'PNG')
+
+
+
+image = Image.new('RGBA', (width, height))
+
+for y in range(0, height):
+    for x in range(0, width / 2 - border):
+        image.putpixel((x, y), color)
+    for x in range(width / 2 + border, width):
+        image.putpixel((x, y), color)
+
+image.save(os.path.join(TARGET, 'grid2x1.png'), 'PNG')
+
+
+
+image = Image.new('RGBA', (width, height))
+
+for x in range(0, width):
+    for y in range(0, height / 2 - border):
+        image.putpixel((x, y), color)
+    for y in range(height / 2 + border, height):
+        image.putpixel((x, y), color)
+
+image.save(os.path.join(TARGET, 'grid1x2.png'), 'PNG')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/NOTES.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,15 @@
+
+Origin of SCSS
+==============
+
+The "Styles" folder is a copy of:
+https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/styles/
+
+
+
+Generation of CSS from the SCSS
+===============================
+
+$ npm install node-sass
+$ ./node_modules/node-sass/bin/node-sass ./Styles/styles.scss > ../WebApplication/app.css
+$ ./GenerateImages.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,3383 @@
+/**
+ * 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.
+ * 
+ * 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 <boost/algorithm/string/predicate.hpp>
+#include <boost/move/unique_ptr.hpp>
+#include <boost/thread.hpp>
+#include <json/reader.h>
+#include <json/writer.h>
+
+
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
+#endif
+
+
+namespace OrthancPlugins
+{
+  static OrthancPluginContext* globalContext_ = NULL;
+
+
+  void SetGlobalContext(OrthancPluginContext* context)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+    else if (globalContext_ == NULL)
+    {
+      globalContext_ = context;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+  }
+
+
+  bool HasGlobalContext()
+  {
+    return globalContext_ != NULL;
+  }
+
+
+  OrthancPluginContext* GetGlobalContext()
+  {
+    if (globalContext_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      return globalContext_;
+    }
+  }
+
+
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (code == OrthancPluginErrorCode_UnknownResource ||
+             code == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  MemoryBuffer::MemoryBuffer()
+  {
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  MemoryBuffer::MemoryBuffer(const void* buffer,
+                             size_t size)
+  {
+    uint32_t s = static_cast<uint32_t>(size);
+    if (static_cast<size_t>(s) != size)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
+             OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else
+    {
+      memcpy(buffer_.data, buffer, size);
+    }
+  }
+#endif
+
+
+  void MemoryBuffer::Clear()
+  {
+    if (buffer_.data != NULL)
+    {
+      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &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::Swap(MemoryBuffer& other)
+  {
+    std::swap(buffer_.data, other.buffer_.data);
+    std::swap(buffer_.size, other.buffer_.size);
+  }
+
+
+  OrthancPluginMemoryBuffer MemoryBuffer::Release()
+  {
+    OrthancPluginMemoryBuffer result = buffer_;
+
+    buffer_.data = NULL;
+    buffer_.size = 0;
+
+    return result;
+  }
+
+
+  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)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
+
+    Json::Reader reader;
+    if (!reader.parse(tmp, tmp + buffer_.size, target))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
+    }
+  }
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                const std::map<std::string, std::string>& httpHeaders,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    std::vector<const char*> headersKeys;
+    std::vector<const char*> headersValues;
+    
+    for (std::map<std::string, std::string>::const_iterator
+           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
+    {
+      headersKeys.push_back(it->first.c_str());
+      headersValues.push_back(it->second.c_str());
+    }
+
+    return CheckHttp(OrthancPluginRestApiGet2(
+                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
+                       (headersKeys.empty() ? NULL : &headersKeys[0]),
+                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
+  }
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const void* body,
+                                 size_t bodySize,
+                                 bool applyPlugins)
+  {
+    Clear();
+    
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const void* body,
+                                size_t bodySize,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    // Cast for compatibility with Orthanc SDK <= 1.5.6
+    const char* b = reinterpret_cast<const char*>(body);
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const Json::Value& body,
+                                bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(uri, writer.write(body), applyPlugins);
+  }
+
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
+  }
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 const OrthancImage& pixelData,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+
+    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
+  }
+
+
+  void MemoryBuffer::ReadFile(const std::string& path)
+  {
+    Clear();
+    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+  {
+    Clear();
+    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
+  }
+
+
+  void OrthancString::Assign(char* str)
+  {
+    Clear();
+
+    if (str != NULL)
+    {
+      str_ = str;
+    }
+  }
+
+
+  void OrthancString::Clear()
+  {
+    if (str_ != NULL)
+    {
+      OrthancPluginFreeString(GetGlobalContext(), 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)
+    {
+      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Reader reader;
+    if (!reader.parse(str_, target))
+    {
+      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void MemoryBuffer::DicomToJson(Json::Value& target,
+                                 OrthancPluginDicomToJsonFormat format,
+                                 OrthancPluginDicomToJsonFlags flags,
+                                 uint32_t maxStringLength)
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginDicomBufferToJson
+               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
+    str.ToJson(target);
+  }
+
+
+  bool MemoryBuffer::HttpGet(const std::string& url,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPost(const std::string& url,
+                              const std::string& body,
+                              const std::string& username,
+                              const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
+                                           body.c_str(), body.size(),
+                                           username.empty() ? NULL : username.c_str(),
+                                           password.empty() ? NULL : password.c_str()));
+  }
+
+
+  bool MemoryBuffer::HttpPut(const std::string& url,
+                             const std::string& body,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
+                                          body.empty() ? NULL : body.c_str(),
+                                          body.size(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
+  {
+    Clear();
+    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
+  }
+
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password)
+  {
+    OrthancPluginErrorCode error = OrthancPluginHttpDelete
+      (GetGlobalContext(), url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void LogError(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogError(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogWarning(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void LogInfo(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
+  void OrthancConfiguration::LoadConfiguration()
+  {
+    OrthancString str;
+    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
+
+    if (str.GetContent() == NULL)
+    {
+      LogError("Cannot access the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    str.ToJson(configuration_);
+
+    if (configuration_.type() != Json::objectValue)
+    {
+      LogError("Unable to read the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+    
+
+  OrthancConfiguration::OrthancConfiguration()
+  {
+    LoadConfiguration();
+  }
+
+
+  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
+  {
+    if (loadConfiguration)
+    {
+      LoadConfiguration();
+    }
+    else
+    {
+      configuration_ = Json::objectValue;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetPath(const std::string& key) const
+  {
+    if (path_.empty())
+    {
+      return key;
+    }
+    else
+    {
+      return path_ + "." + key;
+    }
+  }
+
+
+  bool OrthancConfiguration::IsSection(const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    return (configuration_.isMember(key) &&
+            configuration_[key].type() == Json::objectValue);
+  }
+
+
+  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
+                                        const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.path_ = GetPath(key);
+
+    if (!configuration_.isMember(key))
+    {
+      target.configuration_ = Json::objectValue;
+    }
+    else
+    {
+      if (configuration_[key].type() != Json::objectValue)
+      {
+        LogError("The configuration section \"" + target.path_ +
+                 "\" is not an associative array as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(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)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                        const std::string& key) const
+  {
+    int tmp;
+    if (!LookupIntegerValue(tmp, key))
+    {
+      return false;
+    }
+
+    if (tmp < 0)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a positive integer as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a Boolean as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(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 = static_cast<float>(configuration_[key].asInt());
+        return true;
+
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
+
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
+                                                 const std::string& key,
+                                                 bool allowSingleString) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::arrayValue:
+      {
+        bool ok = true;
+
+        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+        {
+          if (configuration_[key][i].type() == Json::stringValue)
+          {
+            target.push_back(configuration_[key][i].asString());
+          }
+          else
+          {
+            ok = false;
+          }
+        }
+
+        if (ok)
+        {
+          return true;
+        }
+
+        break;
+      }
+
+      case Json::stringValue:
+        if (allowSingleString)
+        {
+          target.push_back(configuration_[key].asString());
+          return true;
+        }
+
+        break;
+
+      default:
+        break;
+    }
+
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a list of strings as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+
+  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
+                                                const std::string& key,
+                                                bool allowSingleString) const
+  {
+    std::list<std::string> lst;
+
+    if (LookupListOfStrings(lst, key, allowSingleString))
+    {
+      target.clear();
+
+      for (std::list<std::string>::const_iterator
+             it = lst.begin(); it != lst.end(); ++it)
+      {
+        target.insert(*it);
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  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 OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
+                                           const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return;
+    }
+
+    if (configuration_[key].type() != Json::objectValue)
+    {
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a string as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    Json::Value::Members members = configuration_[key].getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& value = configuration_[key][members[i]];
+
+      if (value.type() == Json::stringValue)
+      {
+        target[members[i]] = value.asString();
+      }
+      else
+      {
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not a dictionary mapping strings to strings");
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+      }
+    }
+  }
+
+
+  void OrthancImage::Clear()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(GetGlobalContext(), image_);
+      image_ = NULL;
+    }
+  }
+
+
+  void OrthancImage::CheckImageAvailable() const
+  {
+    if (image_ == NULL)
+    {
+      LogError("Trying to access a NULL image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage() :
+    image_(NULL)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
+    image_(image)
+  {
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height)
+  {
+    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height,
+                             uint32_t                  pitch,
+                             void*                     buffer)
+  {
+    image_ = OrthancPluginCreateImageAccessor
+      (GetGlobalContext(), format, width, height, pitch, buffer);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot create an image accessor");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+  void OrthancImage::UncompressPngImage(const void* data,
+                                        size_t size)
+  {
+    Clear();
+
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
+
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a PNG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::UncompressJpegImage(const void* data,
+                                         size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a JPEG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::DecodeDicomImage(const void* data,
+                                      size_t size,
+                                      unsigned int frame)
+  {
+    Clear();
+    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
+    if (image_ == NULL)
+    {
+      LogError("Cannot uncompress a DICOM image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetWidth() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetHeight() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
+  }
+
+
+  unsigned int OrthancImage::GetPitch() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
+  }
+
+
+  void* OrthancImage::GetBuffer() const
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
+  }
+
+
+  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                       uint8_t quality) const
+  {
+    CheckImageAvailable();
+
+    OrthancPlugins::MemoryBuffer answer;
+    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
+                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+
+    target.Swap(answer);
+  }
+
+
+  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
+                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+  }
+
+
+  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                     uint8_t quality) const
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
+                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+  }
+
+
+  OrthancPluginImage* OrthancImage::Release()
+  {
+    CheckImageAvailable();
+    OrthancPluginImage* tmp = image_;
+    image_ = NULL;
+    return tmp;
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
+    matcher_(NULL),
+    worklist_(worklist)
+  {
+    if (worklist_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void FindMatcher::SetupDicom(const void*  query,
+                               uint32_t     size)
+  {
+    worklist_ = NULL;
+
+    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
+    if (matcher_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  FindMatcher::~FindMatcher()
+  {
+    // The "worklist_" field
+
+    if (matcher_ != NULL)
+    {
+      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
+    }
+  }
+
+
+
+  bool FindMatcher::IsMatch(const void*  dicom,
+                            uint32_t     size) const
+  {
+    int32_t result;
+
+    if (matcher_ != NULL)
+    {
+      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
+    }
+    else if (worklist_ != NULL)
+    {
+      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (result == 0)
+    {
+      return false;
+    }
+    else if (result == 1)
+    {
+      return true;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output
+    )
+  {
+    Json::StyledWriter writer;
+    std::string bodyString = writer.write(value);
+
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
+  }
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output
+    )
+  {
+    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
+  }
+
+  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
+  {
+    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
+  }
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
+  {
+    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins)
+  {
+    MemoryBuffer answer;
+    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToString(result);
+      return true;
+    }
+  }
+
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiGet(uri, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        result.assign(answer.GetData(), answer.GetSize());
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(result, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(result, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins)
+  {
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
+    }
+
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision)
+  {
+    LogError("Your version of the Orthanc core (" +
+             std::string(GetGlobalContext()->orthancVersion) +
+             ") is too old to run this plugin (version " +
+             boost::lexical_cast<std::string>(major) + "." +
+             boost::lexical_cast<std::string>(minor) + "." +
+             boost::lexical_cast<std::string>(revision) +
+             " is required)");
+  }
+
+
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (!HasGlobalContext())
+    {
+      LogError("Bad Orthanc context in the plugin");
+      return false;
+    }
+
+    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
+    {
+      // Assume compatibility with the mainline
+      return true;
+    }
+
+    // Parse the version of the Orthanc core
+    int aa, bb, cc;
+    if (
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
+    {
+      return false;
+    }
+
+    unsigned int a = static_cast<unsigned int>(aa);
+    unsigned int b = static_cast<unsigned int>(bb);
+    unsigned int c = static_cast<unsigned int>(cc);
+
+    // Check the major version number
+
+    if (a > major)
+    {
+      return true;
+    }
+
+    if (a < major)
+    {
+      return false;
+    }
+
+
+    // Check the minor version number
+    assert(a == major);
+
+    if (b > minor)
+    {
+      return true;
+    }
+
+    if (b < minor)
+    {
+      return false;
+    }
+
+    // Check the patch level version number
+    assert(a == major && b == minor);
+
+    if (c >= revision)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path)
+  {
+    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
+
+    if (mime == NULL)
+    {
+      // Should never happen, just for safety
+      return "application/octet-stream";
+    }
+    else
+    {
+      return mime;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
+  {
+    size_t index;
+    if (LookupName(index, name))
+    {
+      return index;
+    }
+    else
+    {
+      LogError("Inexistent peer: " + name);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
+    }
+  }
+
+
+  OrthancPeers::OrthancPeers() :
+    peers_(NULL),
+    timeout_(0)
+  {
+    peers_ = OrthancPluginGetPeers(GetGlobalContext());
+
+    if (peers_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+
+    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
+
+    for (uint32_t i = 0; i < count; i++)
+    {
+      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
+      if (name == NULL)
+      {
+        OrthancPluginFreePeers(GetGlobalContext(), peers_);
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+
+      index_[name] = i;
+    }
+  }
+
+
+  OrthancPeers::~OrthancPeers()
+  {
+    if (peers_ != NULL)
+    {
+      OrthancPluginFreePeers(GetGlobalContext(), peers_);
+    }
+  }
+
+
+  bool OrthancPeers::LookupName(size_t& target,
+                                const std::string& name) const
+  {
+    Index::const_iterator found = index_.find(name);
+
+    if (found == index_.end())
+    {
+      return false;
+    }
+    else
+    {
+      target = found->second;
+      return true;
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerName(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(size_t index) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+      if (s == NULL)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+      }
+      else
+      {
+        return s;
+      }
+    }
+  }
+
+
+  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
+  {
+    return GetPeerUrl(GetPeerIndex(name));
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        size_t index,
+                                        const std::string& key) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
+      if (s == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        value.assign(s);
+        return true;
+      }
+    }
+  }
+
+
+  bool OrthancPeers::LookupUserProperty(std::string& value,
+                                        const std::string& peer,
+                                        const std::string& key) const
+  {
+    return LookupUserProperty(value, GetPeerIndex(peer), key);
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(MemoryBuffer& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoGet(target, index, uri));
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           size_t index,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, index, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoGet(Json::Value& target,
+                           const std::string& name,
+                           const std::string& uri) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoGet(buffer, name, uri))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPost(target, index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, index, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            const std::string& name,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, name, uri, body))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      target.Swap(answer);
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(size_t index,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoPut(const std::string& name,
+                           const std::string& uri,
+                           const std::string& body) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoPut(index, uri, body));
+  }
+
+
+  bool OrthancPeers::DoDelete(size_t index,
+                              const std::string& uri) const
+  {
+    if (index >= index_.size())
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    OrthancPlugins::MemoryBuffer answer;
+    uint16_t status;
+    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return (status == 200);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool OrthancPeers::DoDelete(const std::string& name,
+                              const std::string& uri) const
+  {
+    size_t index;
+    return (LookupName(index, name) &&
+            DoDelete(index, uri));
+  }
+#endif
+
+
+
+
+
+  /******************************************************************
+   ** JOBS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  void OrthancJob::CallbackFinalize(void* job)
+  {
+    if (job != NULL)
+    {
+      delete reinterpret_cast<OrthancJob*>(job);
+    }
+  }
+
+
+  float OrthancJob::CallbackGetProgress(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->progress_;
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetContent(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  const char* OrthancJob::CallbackGetSerialized(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
+
+      if (tmp.hasSerialized_)
+      {
+        return tmp.serialized_.c_str();
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+    catch (...)
+    {
+      return 0;
+    }
+  }
+
+
+  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      return reinterpret_cast<OrthancJob*>(job)->Step();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+    catch (...)
+    {
+      return OrthancPluginJobStepStatus_Failure;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
+                                                  OrthancPluginJobStopReason reason)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
+  {
+    assert(job != NULL);
+
+    try
+    {
+      reinterpret_cast<OrthancJob*>(job)->Reset();
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+  void OrthancJob::ClearContent()
+  {
+    Json::Value empty = Json::objectValue;
+    UpdateContent(empty);
+  }
+
+
+  void OrthancJob::UpdateContent(const Json::Value& content)
+  {
+    if (content.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      Json::FastWriter writer;
+      content_ = writer.write(content);
+    }
+  }
+
+
+  void OrthancJob::ClearSerialized()
+  {
+    hasSerialized_ = false;
+    serialized_.clear();
+  }
+
+
+  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
+  {
+    if (serialized.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+    }
+    else
+    {
+      Json::FastWriter writer;
+      serialized_ = writer.write(serialized);
+      hasSerialized_ = true;
+    }
+  }
+
+
+  void OrthancJob::UpdateProgress(float progress)
+  {
+    if (progress < 0 ||
+        progress > 1)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+    }
+
+    progress_ = progress;
+  }
+
+
+  OrthancJob::OrthancJob(const std::string& jobType) :
+    jobType_(jobType),
+    progress_(0)
+  {
+    ClearContent();
+    ClearSerialized();
+  }
+
+
+  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
+      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+      CallbackStep, CallbackStop, CallbackReset);
+
+    if (orthanc == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      return orthanc;
+    }
+  }
+
+
+  std::string OrthancJob::Submit(OrthancJob* job,
+                                 int priority)
+  {
+    if (job == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
+    }
+
+    OrthancPluginJob* orthanc = Create(job);
+
+    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
+
+    if (id == NULL)
+    {
+      LogError("Plugin cannot submit job");
+      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      std::string tmp(id);
+      tmp.assign(id);
+      OrthancPluginFreeString(GetGlobalContext(), id);
+
+      return tmp;
+    }
+  }
+
+
+  void OrthancJob::SubmitAndWait(Json::Value& result,
+                                 OrthancJob* job /* takes ownership */,
+                                 int priority)
+  {
+    std::string id = Submit(job, priority);
+
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+      Json::Value status;
+      if (!RestApiGet(status, "/jobs/" + id, false) ||
+          !status.isMember("State") ||
+          status["State"].type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
+      }
+
+      const std::string state = status["State"].asString();
+      if (state == "Success")
+      {
+        if (status.isMember("Content"))
+        {
+          result = status["Content"];
+        }
+        else
+        {
+          result = Json::objectValue;
+        }
+
+        return;
+      }
+      else if (state == "Running")
+      {
+        continue;
+      }
+      else if (!status.isMember("ErrorCode") ||
+               status["ErrorCode"].type() != Json::intValue)
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
+      }
+      else
+      {
+        if (!status.isMember("ErrorDescription") ||
+            status["ErrorDescription"].type() != Json::stringValue)
+        {
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
+        }
+        else
+        {
+#if HAS_ORTHANC_EXCEPTION == 1
+          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
+                                          status["ErrorDescription"].asString());
+#else
+          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
+#endif
+        }
+      }
+    }
+  }
+
+
+  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                         const Json::Value& body,
+                                         OrthancJob* job)
+  {
+    static const char* KEY_SYNCHRONOUS = "Synchronous";
+    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
+    static const char* KEY_PRIORITY = "Priority";
+
+    boost::movelib::unique_ptr<OrthancJob> protection(job);
+  
+    if (body.type() != Json::objectValue)
+    {
+#if HAS_ORTHANC_EXCEPTION == 1
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Expected a JSON object in the body");
+#else
+      LogError("Expected a JSON object in the body");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+    }
+
+    bool synchronous = true;
+  
+    if (body.isMember(KEY_SYNCHRONOUS))
+    {
+      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = body[KEY_SYNCHRONOUS].asBool();
+      }
+    }
+
+    if (body.isMember(KEY_ASYNCHRONOUS))
+    {
+      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
+      }
+    }
+
+    int priority = 0;
+
+    if (body.isMember(KEY_PRIORITY))
+    {
+      if (body[KEY_PRIORITY].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_PRIORITY) +
+                                        "\" must be an integer");
+#else
+        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        priority = !body[KEY_PRIORITY].asInt();
+      }
+    }
+  
+    Json::Value result;
+
+    if (synchronous)
+    {
+      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
+    }
+    else
+    {
+      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
+
+      result = Json::objectValue;
+      result["ID"] = id;
+      result["Path"] = "/jobs/" + id;
+    }
+
+    std::string s = result.toStyledString();
+    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
+                              s.size(), "application/json");
+  }
+
+#endif
+
+
+
+
+  /******************************************************************
+   ** METRICS
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  MetricsTimer::MetricsTimer(const char* name) :
+    name_(name)
+  {
+    start_ = boost::posix_time::microsec_clock::universal_time();
+  }
+  
+  MetricsTimer::~MetricsTimer()
+  {
+    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
+    const boost::posix_time::time_duration diff = stop - start_;
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
+                                 OrthancPluginMetricsType_Timer);
+  }
+#endif
+
+
+
+
+  /******************************************************************
+   ** HTTP CLIENT
+   ******************************************************************/
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient::RequestBodyWrapper : public boost::noncopyable
+  {
+  private:
+    static RequestBodyWrapper& GetObject(void* body)
+    {
+      assert(body != NULL);
+      return *reinterpret_cast<RequestBodyWrapper*>(body);
+    }
+
+    IRequestBody&  body_;
+    bool           done_;
+    std::string    chunk_;
+
+  public:
+    RequestBodyWrapper(IRequestBody& body) :
+      body_(body),
+      done_(false)
+    {
+    }      
+
+    static uint8_t IsDone(void* body)
+    {
+      return GetObject(body).done_;
+    }
+    
+    static const void* GetChunkData(void* body)
+    {
+      return GetObject(body).chunk_.c_str();
+    }
+    
+    static uint32_t GetChunkSize(void* body)
+    {
+      return static_cast<uint32_t>(GetObject(body).chunk_.size());
+    }
+
+    static OrthancPluginErrorCode Next(void* body)
+    {
+      RequestBodyWrapper& that = GetObject(body);
+        
+      if (that.done_)
+      {
+        return OrthancPluginErrorCode_BadSequenceOfCalls;
+      }
+      else
+      {
+        try
+        {
+          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
+          return OrthancPluginErrorCode_Success;
+        }
+        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+        {
+          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+        }
+        catch (...)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+      }
+    }    
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
+                                                        const char* key,
+                                                        const char* value)
+  {
+    assert(answer != NULL && key != NULL && value != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
+                                                       const void* data,
+                                                       uint32_t size)
+  {
+    assert(answer != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+  HttpClient::HttpClient() :
+    httpStatus_(0),
+    method_(OrthancPluginHttpMethod_Get),
+    timeout_(0),
+    pkcs11_(false),
+    chunkedBody_(NULL),
+    allowChunkedTransfers_(true)
+  {
+  }
+
+
+  void HttpClient::AddHeaders(const HttpHeaders& headers)
+  {
+    for (HttpHeaders::const_iterator it = headers.begin();
+         it != headers.end(); ++it)
+    {
+      headers_[it->first] = it->second;
+    }
+  }
+
+  
+  void HttpClient::SetCredentials(const std::string& username,
+                                  const std::string& password)
+  {
+    username_ = username;
+    password_ = password;
+  }
+
+  
+  void HttpClient::ClearCredentials()
+  {
+    username_.empty();
+    password_.empty();
+  }
+
+
+  void HttpClient::SetCertificate(const std::string& certificateFile,
+                                  const std::string& keyFile,
+                                  const std::string& keyPassword)
+  {
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = keyFile;
+    certificateKeyPassword_ = keyPassword;
+  }
+
+  
+  void HttpClient::ClearCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+  void HttpClient::ClearBody()
+  {
+    fullBody_.clear();
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SwapBody(std::string& body)
+  {
+    fullBody_.swap(body);
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(const std::string& body)
+  {
+    fullBody_ = body;
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(IRequestBody& body)
+  {
+    fullBody_.clear();
+    chunkedBody_ = &body;
+  }
+
+
+  namespace
+  {
+    class HeadersWrapper : public boost::noncopyable
+    {
+    private:
+      std::vector<const char*>  headersKeys_;
+      std::vector<const char*>  headersValues_;
+
+    public:
+      HeadersWrapper(const HttpClient::HttpHeaders& headers)
+      {
+        headersKeys_.reserve(headers.size());
+        headersValues_.reserve(headers.size());
+
+        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+        {
+          headersKeys_.push_back(it->first.c_str());
+          headersValues_.push_back(it->second.c_str());
+        }
+      }
+
+      void AddStaticString(const char* key,
+                           const char* value)
+      {
+        headersKeys_.push_back(key);
+        headersValues_.push_back(value);
+      }
+
+      uint32_t GetCount() const
+      {
+        return headersKeys_.size();
+      }
+
+      const char* const* GetKeys() const
+      {
+        return headersKeys_.empty() ? NULL : &headersKeys_[0];
+      }
+
+      const char* const* GetValues() const
+      {
+        return headersValues_.empty() ? NULL : &headersValues_[0];
+      }
+    };
+
+
+    class MemoryRequestBody : public HttpClient::IRequestBody
+    {
+    private:
+      std::string  body_;
+      bool         done_;
+
+    public:
+      MemoryRequestBody(const std::string& body) :
+        body_(body),
+        done_(false)
+      {
+        if (body_.empty())
+        {
+          done_ = true;
+        }
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk)
+      {
+        if (done_)
+        {
+          return false;
+        }
+        else
+        {
+          chunk.swap(body_);
+          done_ = true;
+          return true;
+        }
+      }
+    };
+
+
+    // This class mimics Orthanc::ChunkedBuffer
+    class ChunkedBuffer : public boost::noncopyable
+    {
+    private:
+      typedef std::list<std::string*>  Content;
+
+      Content  content_;
+      size_t   size_;
+
+    public:
+      ChunkedBuffer() :
+        size_(0)
+      {
+      }
+
+      ~ChunkedBuffer()
+      {
+        Clear();
+      }
+
+      void Clear()
+      {
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          delete *it;
+        }
+
+        content_.clear();
+      }
+
+      void Flatten(std::string& target) const
+      {
+        target.resize(size_);
+
+        size_t pos = 0;
+
+        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          size_t s = (*it)->size();
+
+          if (s != 0)
+          {
+            memcpy(&target[pos], (*it)->c_str(), s);
+            pos += s;
+          }
+        }
+
+        assert(size_ == 0 ||
+               pos == target.size());
+      }
+
+      void AddChunk(const void* data,
+                    size_t size)
+      {
+        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
+        size_ += size;
+      }
+
+      void AddChunk(const std::string& chunk)
+      {
+        content_.push_back(new std::string(chunk));
+        size_ += chunk.size();
+      }
+    };
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    class MemoryAnswer : public HttpClient::IAnswer
+    {
+    private:
+      HttpClient::HttpHeaders  headers_;
+      ChunkedBuffer            body_;
+
+    public:
+      const HttpClient::HttpHeaders& GetHeaders() const
+      {
+        return headers_;
+      }
+
+      const ChunkedBuffer& GetBody() const
+      {
+        return body_;
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value)
+      {
+        headers_[key] = value;
+      }
+
+      virtual void AddChunk(const void* data,
+                            size_t size)
+      {
+        body_.AddChunk(data, size);
+      }
+    };
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
+                                     IAnswer& answer,
+                                     IRequestBody& body) const
+  {
+    HeadersWrapper h(headers_);
+
+    if (method_ == OrthancPluginHttpMethod_Post ||
+        method_ == OrthancPluginHttpMethod_Put)
+    {
+      // Automatically set the "Transfer-Encoding" header if absent
+      bool found = false;
+
+      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        if (boost::iequals(it->first, "Transfer-Encoding"))
+        {
+          found = true;
+          break;
+        }
+      }
+
+      if (!found)
+      {
+        h.AddStaticString("Transfer-Encoding", "chunked");
+      }
+    }
+
+    RequestBodyWrapper request(body);
+        
+    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
+      GetGlobalContext(),
+      &answer,
+      AnswerAddChunkCallback,
+      AnswerAddHeaderCallback,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      h.GetCount(),
+      h.GetKeys(),
+      h.GetValues(),
+      &request,
+      RequestBodyWrapper::IsDone,
+      RequestBodyWrapper::GetChunkData,
+      RequestBodyWrapper::GetChunkSize,
+      RequestBodyWrapper::Next,
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+#endif    
+
+
+  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
+                                        HttpHeaders& answerHeaders,
+                                        std::string& answerBody,
+                                        const std::string& body) const
+  {
+    HeadersWrapper headers(headers_);
+
+    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
+
+    OrthancPluginErrorCode error = OrthancPluginHttpClient(
+      GetGlobalContext(),
+      *answerBodyBuffer,
+      *answerHeadersBuffer,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      headers.GetCount(),
+      headers.GetKeys(),
+      headers.GetValues(),
+      body.empty() ? NULL : body.c_str(),
+      body.size(),
+      username_.empty() ? NULL : username_.c_str(),
+      password_.empty() ? NULL : password_.c_str(),
+      timeout_,
+      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+      pkcs11_ ? 1 : 0);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+
+    Json::Value v;
+    answerHeadersBuffer.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    answerHeaders.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        answerHeaders[members[i]] = h.asString();
+      }
+    }
+
+    answerBodyBuffer.ToString(answerBody);
+  }
+
+
+  void HttpClient::Execute(IAnswer& answer)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      if (chunkedBody_ != NULL)
+      {
+        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
+      }
+      else
+      {
+        MemoryRequestBody wrapper(fullBody_);
+        ExecuteWithStream(httpStatus_, answer, wrapper);
+      }
+
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the answer body are sent at once)
+
+    HttpHeaders answerHeaders;
+    std::string answerBody;
+    Execute(answerHeaders, answerBody);
+
+    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+         it != answerHeaders.end(); ++it)
+    {
+      answer.AddHeader(it->first, it->second);      
+    }
+
+    if (!answerBody.empty())
+    {
+      answer.AddChunk(answerBody.c_str(), answerBody.size());
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           std::string& answerBody /* out */)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    if (allowChunkedTransfers_)
+    {
+      MemoryAnswer answer;
+      Execute(answer);
+      answerHeaders = answer.GetHeaders();
+      answer.GetBody().Flatten(answerBody);
+      return;
+    }
+#endif
+    
+    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
+    // transfers are disabled. This results in higher memory usage
+    // (all chunks from the request body are sent at once)
+
+    if (chunkedBody_ != NULL)
+    {
+      ChunkedBuffer buffer;
+      
+      std::string chunk;
+      while (chunkedBody_->ReadNextChunk(chunk))
+      {
+        buffer.AddChunk(chunk);
+      }
+
+      std::string body;
+      buffer.Flatten(body);
+
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
+    }
+    else
+    {
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
+    }
+  }
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           Json::Value& answerBody /* out */)
+  {
+    std::string body;
+    Execute(answerHeaders, body);
+    
+    Json::Reader reader;
+    if (!reader.parse(body, answerBody))
+    {
+      LogError("Cannot convert HTTP answer body to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void HttpClient::Execute()
+  {
+    HttpHeaders answerHeaders;
+    std::string body;
+    Execute(answerHeaders, body);
+  }
+
+#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
+
+
+
+
+
+  /******************************************************************
+   ** CHUNKED HTTP SERVER
+   ******************************************************************/
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request)
+    {
+    }
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request)
+    {
+      return NULL;
+    }
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader)
+    {
+      if (reader != NULL)
+      {
+        delete reinterpret_cast<IChunkedRequestReader*>(reader);
+      }
+    }
+
+#else
+    
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback         GetHandler,
+                                                    ChunkedRestCallback  PostHandler,
+                                                    RestCallback         DeleteHandler,
+                                                    ChunkedRestCallback  PutHandler)
+    {
+      try
+      {
+        std::string allowed;
+
+        if (GetHandler != Internals::NullRestCallback)
+        {
+          allowed += "GET";
+        }
+
+        if (PostHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "POST";
+        }
+
+        if (DeleteHandler != Internals::NullRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "DELETE";
+        }
+
+        if (PutHandler != Internals::NullChunkedRestCallback)
+        {
+          if (!allowed.empty())
+          {
+            allowed += ",";
+          }
+        
+          allowed += "PUT";
+        }
+      
+        switch (request->method)
+        {
+          case OrthancPluginHttpMethod_Get:
+            if (GetHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              GetHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Post:
+            if (PostHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Delete:
+            if (DeleteHandler == Internals::NullRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              DeleteHandler(output, url, request);
+            }
+
+            break;
+
+          case OrthancPluginHttpMethod_Put:
+            if (PutHandler == Internals::NullChunkedRestCallback)
+            {
+              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
+            }
+            else
+            {
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
+              if (reader.get() == NULL)
+              {
+                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+              }
+              else
+              {
+                reader->AddChunk(request->body, request->bodySize);
+                reader->Execute(output);
+              }
+            }
+
+            break;
+
+          default:
+            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* rawHandler,
+    const char* sopClassUid,
+    const char* sopInstanceUid)
+  {
+    assert(target != NULL &&
+           rawHandler != NULL);
+      
+    try
+    {
+      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+      *target = handler.Lookup(sopClassUid, sopInstanceUid);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
+  {
+    assert(rawHandler != NULL);
+    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#else
+  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance::DicomInstance(const void* buffer,
+                               size_t size) :
+    toFree_(true),
+    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
+  {
+    if (instance_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+  }
+#endif
+
+
+  DicomInstance::~DicomInstance()
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    if (toFree_ &&
+        instance_ != NULL)
+    {
+      OrthancPluginFreeDicomInstance(
+        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
+    }
+#endif
+  }
+
+  
+  std::string DicomInstance::GetRemoteAet() const
+  {
+    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return std::string(s);
+    }
+  }
+
+
+  void DicomInstance::GetJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+  
+
+  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  std::string DicomInstance::GetTransferSyntaxUid() const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
+
+    std::string result;
+    s.ToString(result);
+    return result;
+  }
+#endif
+
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  bool DicomInstance::HasPixelData() const
+  {
+    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
+    if (result < 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  void DicomInstance::GetRawFrame(std::string& target,
+                                  unsigned int frameIndex) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
+      GetGlobalContext(), *buffer, instance_, frameIndex);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
+  {
+    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
+      GetGlobalContext(), instance_, frameIndex);
+
+    if (image == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return new OrthancImage(image);
+    }
+  }
+#endif  
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void DicomInstance::Serialize(std::string& target) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
+      GetGlobalContext(), *buffer, instance_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+  
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance* DicomInstance::Transcode(const void* buffer,
+                                          size_t size,
+                                          const std::string& transferSyntax)
+  {
+    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
+      GetGlobalContext(), buffer, size, transferSyntax.c_str());
+
+    if (instance == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
+      result->toFree_ = true;
+      return result.release();
+    }
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,1228 @@
+/**
+ * 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.
+ * 
+ * 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 "OrthancPluginException.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <boost/noncopyable.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <json/value.h>
+#include <vector>
+#include <list>
+#include <set>
+#include <map>
+
+
+
+/**
+ * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
+ * backward compatibility with Orthanc SDK <= 1.3.0.
+ * 
+ *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
+ *
+ **/
+#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
+
+
+#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
+#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
+  (ORTHANC_VERSION_MAJOR > major ||                                     \
+   (ORTHANC_VERSION_MAJOR == major &&                                   \
+    (ORTHANC_VERSION_MINOR > minor ||                                   \
+     (ORTHANC_VERSION_MINOR == minor &&                                 \
+      ORTHANC_VERSION_REVISION >= revision))))
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
+#  define HAS_ORTHANC_PLUGIN_PEERS  1
+#  define HAS_ORTHANC_PLUGIN_JOB    1
+#else
+#  define HAS_ORTHANC_PLUGIN_PEERS  0
+#  define HAS_ORTHANC_PLUGIN_JOB    0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
+#  define HAS_ORTHANC_PLUGIN_METRICS  1
+#else
+#  define HAS_ORTHANC_PLUGIN_METRICS  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
+#else
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
+#endif
+
+
+
+namespace OrthancPlugins
+{
+  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
+                                const char* url,
+                                const OrthancPluginHttpRequest* request);
+
+  void SetGlobalContext(OrthancPluginContext* context);
+
+  bool HasGlobalContext();
+
+  OrthancPluginContext* GetGlobalContext();
+
+  
+  class OrthancImage;
+
+
+  class MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Check(OrthancPluginErrorCode code);
+
+    bool CheckHttp(OrthancPluginErrorCode code);
+
+  public:
+    MemoryBuffer();
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    // This constructor makes a copy of the given buffer in the memory
+    // handled by the Orthanc core
+    MemoryBuffer(const void* buffer,
+                 size_t size);
+#endif
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    OrthancPluginMemoryBuffer* operator*()
+    {
+      return &buffer_;
+    }
+
+    // This transfers ownership from "other" to "this"
+    void Assign(OrthancPluginMemoryBuffer& other);
+
+    void Swap(MemoryBuffer& other);
+
+    OrthancPluginMemoryBuffer Release();
+
+    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;
+    }
+
+    bool IsEmpty() const
+    {
+      return GetSize() == 0 || GetData() == NULL;
+    }
+
+    void Clear();
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+
+    bool RestApiGet(const std::string& uri,
+                    bool applyPlugins);
+
+    bool RestApiGet(const std::string& uri,
+                    const std::map<std::string, std::string>& httpHeaders,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const void* body,
+                     size_t bodySize,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const void* body,
+                    size_t bodySize,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const Json::Value& body,
+                    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);
+    }
+
+    void CreateDicom(const Json::Value& tags,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void CreateDicom(const Json::Value& tags,
+                     const OrthancImage& pixelData,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void ReadFile(const std::string& path);
+
+    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
+
+    void DicomToJson(Json::Value& target,
+                     OrthancPluginDicomToJsonFormat format,
+                     OrthancPluginDicomToJsonFlags flags,
+                     uint32_t maxStringLength);
+
+    bool HttpGet(const std::string& url,
+                 const std::string& username,
+                 const std::string& password);
+
+    bool HttpPost(const std::string& url,
+                  const std::string& body,
+                  const std::string& username,
+                  const std::string& password);
+
+    bool HttpPut(const std::string& url,
+                 const std::string& body,
+                 const std::string& username,
+                 const std::string& password);
+
+    void GetDicomInstance(const std::string& instanceId);
+  };
+
+
+  class OrthancString : public boost::noncopyable
+  {
+  private:
+    char*   str_;
+
+    void Clear();
+
+  public:
+    OrthancString() :
+      str_(NULL)
+    {
+    }
+
+    ~OrthancString()
+    {
+      Clear();
+    }
+
+    // This transfers ownership, warning: The string must have been
+    // allocated by the Orthanc core
+    void Assign(char* str);
+
+    const char* GetContent() const
+    {
+      return str_;
+    }
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+  };
+
+
+  class OrthancConfiguration : public boost::noncopyable
+  {
+  private:
+    Json::Value  configuration_;  // Necessarily a Json::objectValue
+    std::string  path_;
+
+    std::string GetPath(const std::string& key) const;
+
+    void LoadConfiguration();
+    
+  public:
+    OrthancConfiguration();
+
+    explicit OrthancConfiguration(bool load);
+
+    const Json::Value& GetJson() const
+    {
+      return configuration_;
+    }
+
+    bool IsSection(const std::string& key) const;
+
+    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;
+
+    bool LookupListOfStrings(std::list<std::string>& target,
+                             const std::string& key,
+                             bool allowSingleString) const;
+
+    bool LookupSetOfStrings(std::set<std::string>& target,
+                            const std::string& key,
+                            bool allowSingleString) 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;
+
+    void GetDictionary(std::map<std::string, std::string>& target,
+                       const std::string& key) const;
+  };
+
+  class OrthancImage : public boost::noncopyable
+  {
+  private:
+    OrthancPluginImage*    image_;
+
+    void Clear();
+
+    void CheckImageAvailable() const;
+
+  public:
+    OrthancImage();
+
+    explicit OrthancImage(OrthancPluginImage* image);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height);
+
+    OrthancImage(OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height,
+                 uint32_t                  pitch,
+                 void*                     buffer);
+
+    ~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() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    unsigned int GetPitch() const;
+    
+    void* GetBuffer() const;
+
+    const OrthancPluginImage* GetObject() const
+    {
+      return image_;
+    }
+
+    void CompressPngImage(MemoryBuffer& target) const;
+
+    void CompressJpegImage(MemoryBuffer& target,
+                           uint8_t quality) const;
+
+    void AnswerPngImage(OrthancPluginRestOutput* output) const;
+
+    void AnswerJpegImage(OrthancPluginRestOutput* output,
+                         uint8_t quality) const;
+    
+    void* GetWriteableBuffer();
+
+    OrthancPluginImage* Release();
+  };
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  class FindMatcher : public boost::noncopyable
+  {
+  private:
+    OrthancPluginFindMatcher*          matcher_;
+    const OrthancPluginWorklistQuery*  worklist_;
+
+    void SetupDicom(const void*            query,
+                    uint32_t               size);
+
+  public:
+    explicit FindMatcher(const OrthancPluginWorklistQuery*  worklist);
+
+    FindMatcher(const void*  query,
+                uint32_t     size)
+    {
+      SetupDicom(query, size);
+    }
+
+    explicit FindMatcher(const MemoryBuffer&  dicom)
+    {
+      SetupDicom(dicom.GetData(), dicom.GetSize());
+    }
+
+    ~FindMatcher();
+
+    bool IsMatch(const void*  dicom,
+                 uint32_t     size) const;
+
+    bool IsMatch(const MemoryBuffer& dicom) const
+    {
+      return IsMatch(dicom.GetData(), dicom.GetSize());
+    }
+  };
+#endif
+
+
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        bool applyPlugins);
+
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins);
+
+  bool RestApiPost(std::string& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const void* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins);
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const std::string& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
+                       body.size(), applyPlugins);
+  }
+
+  inline bool RestApiPost(Json::Value& result,
+                          const std::string& uri,
+                          const MemoryBuffer& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, uri, body.GetData(),
+                       body.GetSize(), applyPlugins);
+  }
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const void* body,
+                  size_t bodySize,
+                  bool applyPlugins);
+
+  bool RestApiPut(Json::Value& result,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins);
+
+  inline bool RestApiPut(Json::Value& result,
+                         const std::string& uri,
+                         const std::string& body,
+                         bool applyPlugins)
+  {
+    return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
+                      body.size(), applyPlugins);
+  }
+
+  bool RestApiDelete(const std::string& uri,
+                     bool applyPlugins);
+
+  bool HttpDelete(const std::string& url,
+                  const std::string& username,
+                  const std::string& password);
+
+  void AnswerJson(const Json::Value& value,
+                  OrthancPluginRestOutput* output);
+
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output);
+
+  void AnswerHttpError(uint16_t httpError,
+                       OrthancPluginRestOutput* output);
+
+  void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path);
+#endif
+
+  void LogError(const std::string& message);
+
+  void LogWarning(const std::string& message);
+
+  void LogInfo(const std::string& message);
+
+  void ReportMinimalOrthancVersion(unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision);
+  
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision);
+
+
+  namespace Internals
+  {
+    template <RestCallback Callback>
+    static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                          const char* url,
+                                          const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        Callback(output, url, request);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+  }
+
+  
+  template <RestCallback Callback>
+  void RegisterRestCallback(const std::string& uri,
+                            bool isThreadSafe)
+  {
+    if (isThreadSafe)
+    {
+      OrthancPluginRegisterRestCallbackNoLock
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+    else
+    {
+      OrthancPluginRegisterRestCallback
+        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
+    }
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+  class OrthancPeers : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, uint32_t>   Index;
+
+    OrthancPluginPeers   *peers_;
+    Index                 index_;
+    uint32_t              timeout_;
+
+    size_t GetPeerIndex(const std::string& name) const;
+
+  public:
+    OrthancPeers();
+
+    ~OrthancPeers();
+
+    uint32_t GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void SetTimeout(uint32_t timeout)
+    {
+      timeout_ = timeout;
+    }
+
+    bool LookupName(size_t& target,
+                    const std::string& name) const;
+
+    std::string GetPeerName(size_t index) const;
+
+    std::string GetPeerUrl(size_t index) const;
+
+    std::string GetPeerUrl(const std::string& name) const;
+
+    size_t GetPeersCount() const
+    {
+      return index_.size();
+    }
+
+    bool LookupUserProperty(std::string& value,
+                            size_t index,
+                            const std::string& key) const;
+
+    bool LookupUserProperty(std::string& value,
+                            const std::string& peer,
+                            const std::string& key) const;
+
+    bool DoGet(MemoryBuffer& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(MemoryBuffer& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               size_t index,
+               const std::string& uri) const;
+
+    bool DoGet(Json::Value& target,
+               const std::string& name,
+               const std::string& uri) const;
+
+    bool DoPost(MemoryBuffer& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(MemoryBuffer& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPost(Json::Value& target,
+                const std::string& name,
+                const std::string& uri,
+                const std::string& body) const;
+
+    bool DoPut(size_t index,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoPut(const std::string& name,
+               const std::string& uri,
+               const std::string& body) const;
+
+    bool DoDelete(size_t index,
+                  const std::string& uri) const;
+
+    bool DoDelete(const std::string& name,
+                  const std::string& uri) const;
+  };
+#endif
+
+
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+  class OrthancJob : public boost::noncopyable
+  {
+  private:
+    std::string   jobType_;
+    std::string   content_;
+    bool          hasSerialized_;
+    std::string   serialized_;
+    float         progress_;
+
+    static void CallbackFinalize(void* job);
+
+    static float CallbackGetProgress(void* job);
+
+    static const char* CallbackGetContent(void* job);
+
+    static const char* CallbackGetSerialized(void* job);
+
+    static OrthancPluginJobStepStatus CallbackStep(void* job);
+
+    static OrthancPluginErrorCode CallbackStop(void* job,
+                                               OrthancPluginJobStopReason reason);
+
+    static OrthancPluginErrorCode CallbackReset(void* job);
+
+  protected:
+    void ClearContent();
+
+    void UpdateContent(const Json::Value& content);
+
+    void ClearSerialized();
+
+    void UpdateSerialized(const Json::Value& serialized);
+
+    void UpdateProgress(float progress);
+    
+  public:
+    OrthancJob(const std::string& jobType);
+    
+    virtual ~OrthancJob()
+    {
+    }
+
+    virtual OrthancPluginJobStepStatus Step() = 0;
+
+    virtual void Stop(OrthancPluginJobStopReason reason) = 0;
+    
+    virtual void Reset() = 0;
+
+    static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
+
+    static std::string Submit(OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    static void SubmitAndWait(Json::Value& result,
+                              OrthancJob* job /* takes ownership */,
+                              int priority);
+
+    // Submit a job from a POST on the REST API with the same
+    // conventions as in the Orthanc core (according to the
+    // "Synchronous" and "Priority" options)
+    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                      const Json::Value& body,
+                                      OrthancJob* job);
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_METRICS == 1
+  inline void SetMetricsValue(char* name,
+                              float value)
+  {
+    OrthancPluginSetMetricsValue(GetGlobalContext(), name,
+                                 value, OrthancPluginMetricsType_Default);
+  }
+
+  class MetricsTimer : public boost::noncopyable
+  {
+  private:
+    std::string               name_;
+    boost::posix_time::ptime  start_;
+
+  public:
+    explicit MetricsTimer(const char* name);
+
+    ~MetricsTimer();
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
+  class HttpClient : public boost::noncopyable
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class IRequestBody : public boost::noncopyable
+    {
+    public:
+      virtual ~IRequestBody()
+      {
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk) = 0;
+    };
+
+
+    class IAnswer : public boost::noncopyable
+    {
+    public:
+      virtual ~IAnswer()
+      {
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value) = 0;
+
+      virtual void AddChunk(const void* data,
+                            size_t size) = 0;
+    };
+
+
+  private:
+    class RequestBodyWrapper;
+
+    uint16_t                 httpStatus_;
+    OrthancPluginHttpMethod  method_;
+    std::string              url_;
+    HttpHeaders              headers_;
+    std::string              username_;
+    std::string              password_;
+    uint32_t                 timeout_;
+    std::string              certificateFile_;
+    std::string              certificateKeyFile_;
+    std::string              certificateKeyPassword_;
+    bool                     pkcs11_;
+    std::string              fullBody_;
+    IRequestBody*            chunkedBody_;
+    bool                     allowChunkedTransfers_;
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
+    void ExecuteWithStream(uint16_t& httpStatus,  // out
+                           IAnswer& answer,       // out
+                           IRequestBody& body) const;
+#endif
+
+    void ExecuteWithoutStream(uint16_t& httpStatus,        // out
+                              HttpHeaders& answerHeaders,  // out
+                              std::string& answerBody,     // out
+                              const std::string& body) const;
+    
+  public:
+    HttpClient();
+
+    uint16_t GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    void SetMethod(OrthancPluginHttpMethod method)
+    {
+      method_ = method;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    void SetHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    void AddHeaders(const HttpHeaders& headers);
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password);
+
+    void ClearCredentials();
+
+    void SetTimeout(unsigned int timeout)  // 0 for default timeout
+    {
+      timeout_ = timeout;
+    }
+
+    void SetCertificate(const std::string& certificateFile,
+                        const std::string& keyFile,
+                        const std::string& keyPassword);
+
+    void ClearCertificate();
+
+    void SetPkcs11(bool pkcs11)
+    {
+      pkcs11_ = pkcs11;
+    }
+
+    void ClearBody();
+
+    void SwapBody(std::string& body);
+
+    void SetBody(const std::string& body);
+
+    void SetBody(IRequestBody& body);
+
+    // This function can be used to disable chunked transfers if the
+    // remote server is Orthanc with a version <= 1.5.6.
+    void SetChunkedTransfersAllowed(bool allow)
+    {
+      allowChunkedTransfers_ = allow;
+    }
+
+    bool IsChunkedTransfersAllowed() const
+    {
+      return allowChunkedTransfers_;
+    }
+
+    void Execute(IAnswer& answer);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 std::string& answerBody /* out */);
+
+    void Execute(HttpHeaders& answerHeaders /* out */,
+                 Json::Value& answerBody /* out */);
+
+    void Execute();
+  };
+#endif
+
+
+
+  class IChunkedRequestReader : public boost::noncopyable
+  {
+  public:
+    virtual ~IChunkedRequestReader()
+    {
+    }
+
+    virtual void AddChunk(const void* data,
+                          size_t size) = 0;
+
+    virtual void Execute(OrthancPluginRestOutput* output) = 0;
+  };
+
+
+  typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
+                                                         const OrthancPluginHttpRequest* request);
+
+
+  namespace Internals
+  {
+    void NullRestCallback(OrthancPluginRestOutput* output,
+                          const char* url,
+                          const OrthancPluginHttpRequest* request);
+  
+    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
+                                                   const OrthancPluginHttpRequest* request);
+
+
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+    template <ChunkedRestCallback Callback>
+    static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
+                                                const char* url,
+                                                const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+        else
+        {
+          *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
+          if (*reader == NULL)
+          {
+            return OrthancPluginErrorCode_Plugin;
+          }
+          else
+          {
+            return OrthancPluginErrorCode_Success;
+          }
+        }
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size);
+
+    OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output);
+
+    void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader);
+
+#else  
+
+    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                    const char* url,
+                                                    const OrthancPluginHttpRequest* request,
+                                                    RestCallback GetHandler,
+                                                    ChunkedRestCallback PostHandler,
+                                                    RestCallback DeleteHandler,
+                                                    ChunkedRestCallback PutHandler);
+
+    template<
+      RestCallback         GetHandler,
+      ChunkedRestCallback  PostHandler,
+      RestCallback         DeleteHandler,
+      ChunkedRestCallback  PutHandler
+      >
+    inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                                           const char* url,
+                                                           const OrthancPluginHttpRequest* request)
+    {
+      return ChunkedRestCompatibility(output, url, request, GetHandler,
+                                      PostHandler, DeleteHandler, PutHandler);
+    }
+#endif
+  }
+
+
+
+  // NB: We use a templated class instead of a templated function, because
+  // default values are only available in functions since C++11
+  template<
+    RestCallback         GetHandler    = Internals::NullRestCallback,
+    ChunkedRestCallback  PostHandler   = Internals::NullChunkedRestCallback,
+    RestCallback         DeleteHandler = Internals::NullRestCallback,
+    ChunkedRestCallback  PutHandler    = Internals::NullChunkedRestCallback
+    >
+  class ChunkedRestRegistration : public boost::noncopyable
+  {
+  public:
+    static void Apply(const std::string& uri)
+    {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+      OrthancPluginRegisterChunkedRestCallback(
+        GetGlobalContext(), uri.c_str(),
+        GetHandler == Internals::NullRestCallback         ? NULL : Internals::Protect<GetHandler>,
+        PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
+        DeleteHandler == Internals::NullRestCallback      ? NULL : Internals::Protect<DeleteHandler>,
+        PutHandler == Internals::NullChunkedRestCallback  ? NULL : Internals::ChunkedProtect<PutHandler>,
+        Internals::ChunkedRequestReaderAddChunk,
+        Internals::ChunkedRequestReaderExecute,
+        Internals::ChunkedRequestReaderFinalize);
+#else
+      OrthancPluginRegisterRestCallbackNoLock(
+        GetGlobalContext(), uri.c_str(), 
+        Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
+#endif
+    }
+  };
+
+  
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  class IStorageCommitmentScpHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentScpHandler()
+    {
+    }
+    
+    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                               const std::string& sopInstanceUid) = 0;
+    
+    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
+                                         void* rawHandler,
+                                         const char* sopClassUid,
+                                         const char* sopInstanceUid);
+
+    static void Destructor(void* rawHandler);
+  };
+#endif
+
+
+  class DicomInstance : public boost::noncopyable
+  {
+  private:
+    bool toFree_;
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    const OrthancPluginDicomInstance*  instance_;
+#else
+    OrthancPluginDicomInstance*  instance_;
+#endif
+    
+  public:
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    explicit DicomInstance(const OrthancPluginDicomInstance* instance);
+#else
+    explicit DicomInstance(OrthancPluginDicomInstance* instance);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    DicomInstance(const void* buffer,
+                  size_t size);
+#endif
+
+    ~DicomInstance();
+
+    std::string GetRemoteAet() const;
+
+    const void* GetBuffer() const
+    {
+      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
+    }
+
+    size_t GetSize() const
+    {
+      return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
+    }
+
+    void GetJson(Json::Value& target) const;
+
+    void GetSimplifiedJson(Json::Value& target) const;
+
+    OrthancPluginInstanceOrigin GetOrigin() const
+    {
+      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
+    }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    std::string GetTransferSyntaxUid() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    bool HasPixelData() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    unsigned int GetFramesCount() const
+    {
+      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void GetRawFrame(std::string& target,
+                     unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void Serialize(std::string& target) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    static DicomInstance* Transcode(const void* buffer,
+                                    size_t size,
+                                    const std::string& transferSyntax);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-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.
+ * 
+ * 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(HAS_ORTHANC_EXCEPTION)
+#  error The macro HAS_ORTHANC_EXCEPTION must be defined
+#endif
+
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include <OrthancException.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
+#else
+#  include <orthanc/OrthancCPlugin.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
+#endif
+
+
+#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
+
+
+#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
+                                                  
+
+#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
+  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
+  {                                                                 \
+    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
+  }
+
+
+namespace OrthancPlugins
+{
+#if HAS_ORTHANC_EXCEPTION == 0
+  class PluginException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode GetErrorCode() const
+    {
+      return code_;
+    }
+
+    const char* What(OrthancPluginContext* context) const
+    {
+      const char* description = OrthancPluginGetErrorDescription(context, code_);
+      if (description)
+      {
+        return description;
+      }
+      else
+      {
+        return "No description available";
+      }
+    }
+  };
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,31 @@
+# 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.
+# 
+# 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/>.
+
+
+# In Orthanc <= 1.7.1, the instructions below were part of
+# "Compiler.cmake", and were protected by the (now unused) option
+# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map")
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list")
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,12 @@
+# This is a version-script for Orthanc plugins
+
+{
+global:
+  OrthancPluginInitialize;
+  OrthancPluginFinalize;
+  OrthancPluginGetName;
+  OrthancPluginGetVersion;
+
+local:
+  *;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,4740 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup Worklists Worklists
+ * @brief Functions to register and manage worklists.
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
+#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 already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _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 worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       remoteAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, it is up to the plugin to
+   * implement the required locking mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * script.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), 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 char*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const char*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  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);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_button.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,205 @@
+// clean icon buttons
+.wvButton {
+    // Remove <a> default styles. Take care - this class may either be used
+    // with <a> or <button>.
+    &:hover {
+        text-decoration: none;
+        color: white;
+    }
+
+    // Remove <button> default styles.
+    outline: none;
+    background-color: transparent;
+    border: none;
+    border-radius: 0;
+
+    // Set relative to position button absolutely
+    position: relative;
+
+    // Style button
+    display: inline-block;
+    cursor: pointer;
+    font-variant: small-caps;
+    text-transform: lowercase;
+    text-align: center;
+    font-size: 1.3rem;
+    font-weight: 400;
+    color: hsl(0, 0%, 85%);
+    transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
+
+    // Position button
+    margin: 0;
+    min-width: 3rem;
+    padding: 0 10px;
+    line-height: 3.6rem;
+    &.wvLargeButton{
+        font-size: 2rem;
+        line-height: 6.2rem;
+        padding: 0 20px;
+    }
+}
+
+.wvButton--rotate {
+    @extend .wvButton;
+    // Rotate only the icon
+    &:before, &:after{
+        transform: rotate(90deg);
+        display: inline-block;
+    }
+
+}
+
+.wvButton--vflip {
+    @extend .wvButton;
+    // flip only the icon
+    &:before, &:after{
+        transform: scaleX(-1);
+        display: inline-block;
+    }
+
+}
+
+// button w/ blue underline
+.wvButton--underline, .fa.wvButton--underline {
+    @extend .wvButton;
+
+    position: relative;
+
+    background-color:inherit;
+    text-decoration: none;
+    text-align:left;
+    font-size: 1.2rem;
+    &.wvLargeButton{
+        font-size: 2rem;
+        width: 6.4rem;
+    }
+    * {
+        pointer-events: none;
+    }
+    &:hover, &:active, &:focus{
+        outline:0;
+    }
+
+    width: 3.2rem;
+    vertical-align: middle;
+    color:white;
+    opacity: 0.75;
+    border:none;
+    border-bottom: 2px solid rgba(255,255,255,0.1);
+    &:hover, &:focus{
+        border-color: rgba(255,255,255,1);
+        opacity:1;
+        .wvButton__bottomTriangle{
+            border-left-color: rgba(255,255,255,1);
+            &.toggled{
+                // border-color: rgba(255, 255, 255, 1);
+            }
+        }
+    }
+    &.active{
+        opacity: 1;
+        border-color: $primary;
+    }
+
+    // Make sure the 2px border is not hidden by viewports and other parts of
+    // the layout (.glyphicon class sets top to 1px)
+    top: 0px;
+
+    // Compensate glyphicon whitespace
+    &::before {
+        position: relative;
+        top: -1px;
+    }
+
+    // Adapt font-awesome icon to glyphicon styles
+    &.fa {
+        top: 0px;
+        font-weight: 800;
+    }
+}
+
+.wvButton__bottomTriangle{
+    transition: 0.3s border ease, 0.3s opacity ease;
+
+    display:block;
+    position: absolute;
+    bottom:0;
+    left:0;
+
+    width: 0;
+    height: 0;
+    border-style: solid;
+    border-width: 10px 0 0 10px;
+    border-color: transparent transparent transparent rgba(255,255,255,0.1);
+    &.active{
+        border-color: transparent transparent transparent $primary !important;
+        &.toggled{
+            border-left-color: $primary !important;
+        }
+    }
+    &.toggled{
+        // border-width: 15px 0 0 15px;
+    }
+}
+
+// button w/ border
+.wvButton--border {
+    @extend .wvButton;
+
+    // Prevent multi line buttons.
+    max-height: calc(2.8rem - 3px);
+    max-width: 100%;
+    overflow: hidden;
+
+    // Set margin
+    margin: 0.6rem;
+    margin-left: 0rem;
+    margin-right: 0rem;
+    & + & {
+        margin-left: 0.7rem;
+    }
+
+    // Set button size
+    line-height: 2rem;
+
+    // Align text
+    padding-top: 0.1rem;
+    padding-bottom: 0.5rem;
+
+    // Style button
+    font-size: 1.4rem;
+    border: 1px solid hsl(0, 0%, 27%);
+
+    // Set best looking font with small-caps.
+    font-family: Arial;
+
+    // Change background on hover
+    background-color: hsl(0, 0%, 0%);
+    &:hover {
+        background-color: hsl(0, 0%, 10%);
+    }
+
+    & > .glyphicon { // used with the same element as glyphicons
+        // Position button
+        position: relative;
+        display: inline-block;
+        top: 3px;
+        margin-right: 4px;
+    }
+}
+
+// button w/ border + white modifier to use when the background is white.
+.wvButton--borderAndWhite {
+    @extend .wvButton--border;
+
+    // Text color
+    color: hsl(0, 0%, 10%);
+    border: 1px solid hsl(0, 0%, 73%);
+
+    // Change background on hover
+    background-color: hsl(0, 0%, 100%);
+    &:hover {
+        color: hsl(0, 0%, 10%);
+        background-color: hsl(0, 0%, 90%);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_exitButton.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,28 @@
+.wvExitButton {
+    margin-left: 1rem;
+    margin-top: .25rem;
+    font-size: 1.25em;
+    color: white;
+    opacity: .66;
+
+    transition: .3s opacity ease;
+    background-color: inherit;
+    border: none;
+    text-decoration: none;
+    text-align: left;
+    padding: 0;
+    cursor: pointer;
+    font-family: inherit;
+    line-height: inherit;
+
+    &:hover, &:focus {
+        opacity: 1;
+        outline: 0;
+    }
+}
+
+.wvExitButton__text {
+    // Align text with glyph-icon.
+    position: relative;
+    top: -1px;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_helpers.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,1115 @@
+/*
+ *  Source code taken from private Osimis' frontend toolbox 3.2.1.
+ */
+
+/**
+    _overlay.scss
+ */
+.overlay__transparent{
+    position:fixed;
+    top:0;
+    left:0;
+    width:100%;
+    height:100%;
+    z-index: 1;
+}
+
+/** _transition.scss **/
+.transition,
+%transition{
+    &{
+        transition: 0.3s all ease;
+    }
+}
+
+.transition--long,
+%transition--long{
+    &{
+        transition: 0.6s all ease;
+    }
+}
+
+
+/** _list.scss **/
+// This is used in the study information panel.
+dd+dt{
+    clear:both;
+}
+.listDefinition{
+    width:100%;
+    line-height: 1.3;
+}
+.listDefinition__term{
+    clear:both;
+    float:left;
+    text-align: right;
+    padding-right:10px;
+    width:50%;
+    @extend .font__light;
+
+}
+.listDefinition__data{
+    text-align: left;
+    padding-left:10px;
+    float:right;
+    width:50%;
+    @extend .font__normal;
+
+}
+
+
+/** _animation.scss **/
+@keyframes blink__primary {
+    0% { color: $primary; }
+    100% { color: $text-color; }
+}
+
+.blink__primary{
+  animation: blink__primary 0.8s linear infinite;
+}
+
+[translate-cloak]{
+    transition: 0.3s all ease;
+    opacity: 1;
+    &.translate-cloak{
+        opacity: 0;
+    }
+}
+
+/** _button.scss **/
+.button__unstyled,
+%button__unstyled{
+    background-color:inherit;
+    border:none;
+    text-decoration: none;
+    text-align:left;
+    padding:0;
+    cursor: pointer;
+    *{
+        pointer-events: none;
+    }
+    &:hover, &:active, &:focus{
+        outline:0;
+    }
+}
+.button__base,
+%button__base{
+    @extend .button__unstyled;
+    transition: 0.3s all ease;
+    //& *,& *:before,& *:after{
+    //  @extend .transition;
+    //}
+}
+.button__state--active{
+    @extend .button__base;
+    opacity:1;
+    &:hover{
+        opacity:0.9;
+        color:$primary;
+    }
+}
+.button__state--inactive{
+    @extend .button__base;
+    opacity:0.333;
+    &:hover{
+        opacity:0.4333;
+        color:$primary;
+    }
+}
+.button__iconed{
+    @extend .button__base;
+    opacity: 1;
+    &:hover, &:focus, &.active{
+        opacity: 0.75;
+        color:$primary;
+    }
+}
+.button__switch--base,
+%button__switch--base{
+    @extend .button__base;
+    padding:5px 0px;
+    display:inline-block;
+    text-align: center;
+    border-top: 1px solid;
+    border-bottom: 1px solid;
+    border-color: $primary;
+    background-color: white;
+    overflow: hidden;
+    &:hover, &:focus{
+        background-color: lighten($primary, 10);
+        color:white;
+    }
+    &.active{
+        background-color:$primary;
+        color:white;
+    }
+}
+.button__switch{
+    @extend .button__switch--base;
+}
+.button__switch--first{
+    @extend .button__switch--base;
+    border-left: 1px solid $primary;
+    border-radius: 5px 0 0 5px;
+}
+.button__switch--last{
+    @extend .button__switch--base;
+    border-right: 1px solid $primary;
+    border-radius: 0 5px 5px 0;
+}
+.button__lightgrey--hover{
+    @extend .button__base;
+    &:hover, &:focus, &.active{
+        background-color: $lightGrey;
+    }
+}
+.button__text-danger--hover{
+    @extend .button__base;
+    &:hover, &:focus, &.active{
+        color: $dangerColor;
+    }
+}
+.button__text-primary--hover{
+    @extend .button__base;
+    &:hover, &:focus, &.active{
+        color: $primary;
+    }
+}
+.button__danger--hover{
+    @extend .button__base;
+    &:hover, &:focus, &.active{
+        background-color: $dangerColor;
+        color:white;
+    }
+}
+.button__text{
+    @extend .button__base;
+    opacity:0.66;
+    &:hover, &.active, &:focus{
+        opacity:1;
+    }
+}
+.button__text--underlined{
+    @extend .button__base;
+    text-decoration: underline;
+    &:hover, &.active, &:focus{
+        text-decoration:none;
+    }
+}
+.button__bordered{
+    @extend .button__base;
+    border-bottom: 2px solid $text-color;
+    &:hover, &:focus, &.active{
+        border-color: $primary;
+    }
+}
+.button__bordered--inverted{
+    @extend .button__base;
+    border-bottom: 2px solid white;
+}
+.button__close{
+    @extend .button__base;
+    position:absolute;
+    top:0;
+    right:0;
+    opacity:0.6;
+    z-index:10;
+    &:hover, &:focus{
+        opacity:1;
+    }
+}
+
+
+/** _block.scss **/
+.block{
+    display: block !important;
+}
+
+
+/** _boxsizing.scss **/
+.boxsizing__borderbox{
+    box-sizing:border-box;
+}
+.boxsizing__contentbox{
+    box-sizing:content-box;
+}
+
+
+/** _scrollable.scss **/
+.scrollable{
+    overflow-y:auto;
+}
+.scrollable--x{
+    overflow-x:auto;
+}
+.no-scroll{
+    overflow:hidden;
+}
+
+
+/** _float.scss **/
+.float__right,
+%float__right{
+    float:right;
+}
+.float__left,
+%float__left{
+    float:left;
+}
+
+
+/** _fonts.scss **/
+.font__bold,
+%font__bold{
+    font-weight:600;
+}
+.font__normal,
+%font__normal{
+    font-weight: 400;
+}
+.font__light,
+%font__light{
+    font-weight: 200;
+}
+.fontColor__primary{
+    color:$primary;
+}
+.fontColor__lightGrey{
+    color:$lightGrey;
+}
+.fontColor__normal{
+    color:$text-color;
+}
+.fontColor__darker{
+    color:$darkGrey;
+}
+.fontColor__white{
+    color:white;
+}
+
+
+/** _forms.scss **/
+.textarea__unstyled{
+    border:none;
+    outline:none;
+    background-color:transparent;
+    color:inherit;
+    resize:none;
+    padding:0;
+    margin:0;
+    width:100%;
+}
+
+
+/** _position.scss **/
+.position__relative{
+    position: relative;
+}
+
+
+/** _margin.scss **/
+.margin__auto{
+    margin:auto;
+}
+
+
+/** _helpers.scss **/
+$steps: (
+    0,5,8,10,
+    11,12,13,14,15,16,17,18,19,20,
+    21,22,23,24,25,26,27,28,29,30,
+    31,32,33,34,35,36,37,38,39,40,
+    41,42,43,44,45,46,47,48,49,50,
+    55,60,64,65,70,72,75,80,85,90,95,96,100,
+    110,120,130,140,150,160,170,180,190,200,
+    210,220,230,240,250,260,270,280,290,300,
+    310,320,330,340,350,360,370,380,390,400,
+    410,420,430,440,450,460,470,480,490,500
+);
+
+
+/*************HELPERS**************/
+/**********************************/
+
+/*** identical width and height ***/
+@each $step in $steps {
+    .wh__#{$step} {
+        width: $step + px !important;
+        height: $step + px !important;
+    }
+    .lh__#{$step} {
+        line-height: $step + px !important;
+    }
+}
+.lh__1{
+    line-height: 1 !important;
+}
+.no-wrap{
+    white-space: nowrap;
+}
+
+.ov-h{
+    overflow: hidden;
+}
+.va-m{
+    vertical-align: middle;
+}
+.bg-inherit{
+    background-color:inherit;
+}
+.bg-black{
+    background-color: black;
+}
+.v-center{
+    &:before {
+        content: '';
+        display: inline-block;
+        height: 100%;
+        vertical-align: middle;
+        margin-top: -0.25em; /* Adjusts for spacing */
+    }
+}
+.fluid-height{
+    height:100%;
+}
+.visibility__hidden{
+    visibility: hidden;
+}
+
+.pointerEvents__none{
+    pointer-events: none;
+}
+
+/* Padding Helpers */
+.pn {
+  padding: 0 !important;
+}
+.p1 {
+  padding: 1px !important;
+}
+.p2 {
+  padding: 2px !important;
+}
+.p3 {
+  padding: 3px !important;
+}
+.p4 {
+  padding: 4px !important;
+}
+.p5 {
+  padding: 5px !important;
+}
+.p6 {
+  padding: 6px !important;
+}
+.p7 {
+  padding: 7px !important;
+}
+.p8 {
+  padding: 8px !important;
+}
+.p10 {
+  padding: 10px !important;
+}
+.p12 {
+  padding: 12px !important;
+}
+.p15 {
+  padding: 15px !important;
+}
+.p20 {
+  padding: 20px !important;
+}
+.p25 {
+  padding: 25px !important;
+}
+.p30 {
+  padding: 30px !important;
+}
+.p35 {
+  padding: 35px !important;
+}
+.p40 {
+  padding: 40px !important;
+}
+.p50 {
+  padding: 50px !important;
+}
+.ptn {
+  padding-top: 0 !important;
+}
+.pt5 {
+  padding-top: 5px !important;
+}
+.pt10 {
+  padding-top: 10px !important;
+}
+.pt15 {
+  padding-top: 15px !important;
+}
+.pt20 {
+  padding-top: 20px !important;
+}
+.pt25 {
+  padding-top: 25px !important;
+}
+.pt30 {
+  padding-top: 30px !important;
+}
+.pt35 {
+  padding-top: 35px !important;
+}
+.pt40 {
+  padding-top: 40px !important;
+}
+.pt50 {
+  padding-top: 50px !important;
+}
+.prn {
+  padding-right: 0 !important;
+}
+.pr5 {
+  padding-right: 5px !important;
+}
+.pr10 {
+  padding-right: 10px !important;
+}
+.pr15 {
+  padding-right: 15px !important;
+}
+.pr20 {
+  padding-right: 20px !important;
+}
+.pr25 {
+  padding-right: 25px !important;
+}
+.pr30 {
+  padding-right: 30px !important;
+}
+.pr35 {
+  padding-right: 35px !important;
+}
+.pr40 {
+  padding-right: 40px !important;
+}
+.pr50 {
+  padding-right: 50px !important;
+}
+.pbn {
+  padding-bottom: 0 !important;
+}
+.pb5 {
+  padding-bottom: 5px !important;
+}
+.pb10 {
+  padding-bottom: 10px !important;
+}
+.pb15 {
+  padding-bottom: 15px !important;
+}
+.pb20 {
+  padding-bottom: 20px !important;
+}
+.pb25 {
+  padding-bottom: 25px !important;
+}
+.pb30 {
+  padding-bottom: 30px !important;
+}
+.pb35 {
+  padding-bottom: 35px !important;
+}
+.pb40 {
+  padding-bottom: 40px !important;
+}
+.pb50 {
+  padding-bottom: 50px !important;
+}
+.pln {
+  padding-left: 0 !important;
+}
+.pl5 {
+  padding-left: 5px !important;
+}
+.pl10 {
+  padding-left: 10px !important;
+}
+.pl15 {
+  padding-left: 15px !important;
+}
+.pl20 {
+  padding-left: 20px !important;
+}
+.pl25 {
+  padding-left: 25px !important;
+}
+.pl30 {
+  padding-left: 30px !important;
+}
+.pl35 {
+  padding-left: 35px !important;
+}
+.pl40 {
+  padding-left: 40px !important;
+}
+.pl50 {
+  padding-left: 50px !important;
+}
+
+/* Axis Padding (both top/bottom or left/right) */
+.pv5 {
+  padding-top: 5px !important;
+  padding-bottom: 5px !important;
+}
+.pv8 {
+  padding-top: 8px !important;
+  padding-bottom: 8px !important;
+}
+.pv10 {
+  padding-top: 10px !important;
+  padding-bottom: 10px !important;
+}
+.pv15 {
+  padding-top: 15px !important;
+  padding-bottom: 15px !important;
+}
+.pv20 {
+  padding-top: 20px !important;
+  padding-bottom: 20px !important;
+}
+.pv25 {
+  padding-top: 25px !important;
+  padding-bottom: 25px !important;
+}
+.pv30 {
+  padding-top: 30px !important;
+  padding-bottom: 30px !important;
+}
+.pv40 {
+  padding-top: 40px !important;
+  padding-bottom: 40px !important;
+}
+.pv50 {
+  padding-top: 50px !important;
+  padding-bottom: 50px !important;
+}
+.ph5 {
+  padding-left: 5px !important;
+  padding-right: 5px !important;
+}
+.ph8 {
+  padding-left: 8px !important;
+  padding-right: 8px !important;
+}
+.ph10 {
+  padding-left: 10px !important;
+  padding-right: 10px !important;
+}
+.ph15 {
+  padding-left: 15px !important;
+  padding-right: 15px !important;
+}
+.ph20 {
+  padding-left: 20px !important;
+  padding-right: 20px !important;
+}
+.ph25 {
+  padding-left: 25px !important;
+  padding-right: 25px !important;
+}
+.ph30 {
+  padding-left: 30px !important;
+  padding-right: 30px !important;
+}
+.ph40 {
+  padding-left: 40px !important;
+  padding-right: 40px !important;
+}
+.ph50 {
+  padding-left: 50px !important;
+  padding-right: 50px !important;
+}
+
+/* margin center helper */
+.mauto {
+  margin-left: auto;
+  margin-right: auto;
+}
+.mn {
+  margin: 0 !important;
+}
+.m1 {
+  margin: 1px !important;
+}
+.m2 {
+  margin: 2px !important;
+}
+.m3 {
+  margin: 3px !important;
+}
+.m4 {
+  margin: 4px !important;
+}
+.m5 {
+  margin: 5px !important;
+}
+.m8 {
+  margin: 8px !important;
+}
+.m10 {
+  margin: 10px !important;
+}
+.m15 {
+  margin: 15px !important;
+}
+.m20 {
+  margin: 20px !important;
+}
+.m25 {
+  margin: 25px !important;
+}
+.m30 {
+  margin: 30px !important;
+}
+.m35 {
+  margin: 35px !important;
+}
+.m40 {
+  margin: 40px !important;
+}
+.m50 {
+  margin: 50px !important;
+}
+.mtn {
+  margin-top: 0 !important;
+}
+.mt5 {
+  margin-top: 5px !important;
+}
+.mt10 {
+  margin-top: 10px !important;
+}
+.mt15 {
+  margin-top: 15px !important;
+}
+.mt20 {
+  margin-top: 20px !important;
+}
+.mt25 {
+  margin-top: 25px !important;
+}
+.mt30 {
+  margin-top: 30px !important;
+}
+.mt35 {
+  margin-top: 35px !important;
+}
+.mt40 {
+  margin-top: 40px !important;
+}
+.mt50 {
+  margin-top: 50px !important;
+}
+.mt70 {
+  margin-top: 70px !important;
+}
+.mrn {
+  margin-right: 0 !important;
+}
+.mr5 {
+  margin-right: 5px !important;
+}
+.mr10 {
+  margin-right: 10px !important;
+}
+.mr15 {
+  margin-right: 15px !important;
+}
+.mr20 {
+  margin-right: 20px !important;
+}
+.mr25 {
+  margin-right: 25px !important;
+}
+.mr30 {
+  margin-right: 30px !important;
+}
+.mr35 {
+  margin-right: 35px !important;
+}
+.mr40 {
+  margin-right: 40px !important;
+}
+.mr50 {
+  margin-right: 50px !important;
+}
+.mbn {
+  margin-bottom: 0 !important;
+}
+.mb5 {
+  margin-bottom: 5px !important;
+}
+.mb10 {
+  margin-bottom: 10px !important;
+}
+.mb15 {
+  margin-bottom: 15px !important;
+}
+.mb20 {
+  margin-bottom: 20px !important;
+}
+.mb25 {
+  margin-bottom: 25px !important;
+}
+.mb30 {
+  margin-bottom: 30px !important;
+}
+.mb35 {
+  margin-bottom: 35px !important;
+}
+.mb40 {
+  margin-bottom: 40px !important;
+}
+.mb50 {
+  margin-bottom: 50px !important;
+}
+.mb70 {
+  margin-bottom: 70px !important;
+}
+.mln {
+  margin-left: 0 !important;
+}
+.ml5 {
+  margin-left: 5px !important;
+}
+.ml10 {
+  margin-left: 10px !important;
+}
+.ml15 {
+  margin-left: 15px !important;
+}
+.ml20 {
+  margin-left: 20px !important;
+}
+.ml25 {
+  margin-left: 25px !important;
+}
+.ml30 {
+  margin-left: 30px !important;
+}
+.ml35 {
+  margin-left: 35px !important;
+}
+.ml40 {
+  margin-left: 40px !important;
+}
+.ml50 {
+  margin-left: 50px !important;
+}
+
+/* Axis Margins (both top/bottom or left/right) */
+.mv5 {
+  margin-top: 5px !important;
+  margin-bottom: 5px !important;
+}
+.mv10 {
+  margin-top: 10px !important;
+  margin-bottom: 10px !important;
+}
+.mv15 {
+  margin-top: 15px !important;
+  margin-bottom: 15px !important;
+}
+.mv20 {
+  margin-top: 20px !important;
+  margin-bottom: 20px !important;
+}
+.mv25 {
+  margin-top: 25px !important;
+  margin-bottom: 25px !important;
+}
+.mv30 {
+  margin-top: 30px !important;
+  margin-bottom: 30px !important;
+}
+.mv40 {
+  margin-top: 40px !important;
+  margin-bottom: 40px !important;
+}
+.mv50 {
+  margin-top: 50px !important;
+  margin-bottom: 50px !important;
+}
+.mv70 {
+  margin-top: 70px !important;
+  margin-bottom: 70px !important;
+}
+.mh5 {
+  margin-left: 5px !important;
+  margin-right: 5px !important;
+}
+.mh10 {
+  margin-left: 10px !important;
+  margin-right: 10px !important;
+}
+.mh15 {
+  margin-left: 15px !important;
+  margin-right: 15px !important;
+}
+.mh20 {
+  margin-left: 20px !important;
+  margin-right: 20px !important;
+}
+.mh25 {
+  margin-left: 25px !important;
+  margin-right: 25px !important;
+}
+.mh30 {
+  margin-left: 30px !important;
+  margin-right: 30px !important;
+}
+.mh40 {
+  margin-left: 40px !important;
+  margin-right: 40px !important;
+}
+.mh50 {
+  margin-left: 50px !important;
+  margin-right: 50px !important;
+}
+.mh70 {
+  margin-left: 70px !important;
+  margin-right: 70px !important;
+}
+
+/* Negative Margin Helpers */
+.mtn5 {
+  margin-top: -5px !important;
+}
+.mtn10 {
+  margin-top: -10px !important;
+}
+.mtn15 {
+  margin-top: -15px !important;
+}
+.mtn20 {
+  margin-top: -20px !important;
+}
+.mtn30 {
+  margin-top: -30px !important;
+}
+.mrn5 {
+  margin-right: -5px !important;
+}
+.mrn10 {
+  margin-right: -10px !important;
+}
+.mrn15 {
+  margin-right: -15px !important;
+}
+.mrn20 {
+  margin-right: -20px !important;
+}
+.mrn30 {
+  margin-right: -30px !important;
+}
+.mbn5 {
+  margin-bottom: -5px !important;
+}
+.mbn10 {
+  margin-bottom: -10px !important;
+}
+.mbn15 {
+  margin-bottom: -15px !important;
+}
+.mbn20 {
+  margin-bottom: -20px !important;
+}
+.mbn30 {
+  margin-bottom: -30px !important;
+}
+.mln5 {
+  margin-left: -5px !important;
+}
+.mln10 {
+  margin-left: -10px !important;
+}
+.mln15 {
+  margin-left: -15px !important;
+}
+.mln20 {
+  margin-left: -20px !important;
+}
+.mln30 {
+  margin-left: -30px !important;
+}
+
+/* Vertical Negative Margin "mv" + "n" + "x" */
+.mvn5 {
+  margin-top: -5px !important;
+  margin-bottom: -5px !important;
+}
+.mvn10 {
+  margin-top: -10px !important;
+  margin-bottom: -10px !important;
+}
+.mvn15 {
+  margin-top: -15px !important;
+  margin-bottom: -15px !important;
+}
+.mvn20 {
+  margin-top: -20px !important;
+  margin-bottom: -20px !important;
+}
+.mvn30 {
+  margin-top: -30px !important;
+  margin-bottom: -30px !important;
+}
+
+/* Horizontal Negative Margin "mh" + "n" + "x" */
+.mhn5 {
+  margin-left: -5px !important;
+  margin-right: -5px !important;
+}
+.mhn10 {
+  margin-left: -10px !important;
+  margin-right: -10px !important;
+}
+.mhn15 {
+  margin-left: -15px !important;
+  margin-right: -15px !important;
+}
+.mhn20 {
+  margin-left: -20px !important;
+  margin-right: -20px !important;
+}
+.mhn30 {
+  margin-left: -30px !important;
+  margin-right: -30px !important;
+}
+
+/* Vertical Align Helpers */
+.va-t {
+  vertical-align: top !important;
+}
+.va-m {
+  vertical-align: middle !important;
+}
+.va-b {
+  vertical-align: bottom !important;
+}
+.va-s {
+  vertical-align: super !important;
+}
+
+/* Text Helpers */
+.text-left {
+  text-align: left !important;
+}
+.text-right {
+  text-align: right !important;
+}
+.text-center {
+  text-align: center !important;
+}
+.text-justify {
+  text-align: justify !important;
+}
+.text-nowrap {
+  white-space: nowrap !important;
+}
+
+/* Inline Block Helper */
+.ib,
+.inline-object {
+  display: inline-block !important;
+}
+
+.clear {
+  clear: both;
+}
+
+// warning popup
+
+.wvWarning {
+  position: relative;
+  width: 320px;
+  min-height: 130px;
+  z-index: 999;
+  left: calc(50% - 160px);
+  border: #000 solid 1px;
+  -webkit-border-radius: 7px;
+  -moz-border-radius: 7px;
+  border-radius: 7px;
+  color: #FF5722;
+  box-shadow: 0px 3px 23px #ff980078;
+  -webkit-animation-name: example;  /* Safari 4.0 - 8.0 */
+  -webkit-animation-duration: 3s;  /* Safari 4.0 - 8.0 */    
+  -webkit-animation-fill-mode: both; /* Safari 4.0 - 8.0 */
+  animation-name: example;
+  animation-duration: 2s;    
+  animation-fill-mode: both;
+  animation-timing-function: ease-out;
+}
+
+@-webkit-keyframes example {
+    from {top: 0vh;opacity: 0;background: #868686}
+    to {top: 10vh;opacity: 1;background: #ffffff}
+}
+
+@keyframes example {
+  from {top: 0vh;opacity: 0;background: #868686}
+    to {top: 10vh;opacity: 1;background: #ffffff}
+}
+
+.wvWarning-content{
+  position: relative;
+  width: 190px;
+  min-height: 88px;
+  max-height: 80vh;
+  margin: auto;
+}
+.wvWarning-icon{
+  font-size: 32px;
+}
+.wvWarning-text{
+  position: relative;
+}
+.wvWarning-button{
+  background-color: #f1ededcc;
+  color: #607D8B;
+  width: 50px;
+  font-weight: 600;
+  margin-top: 2px;
+  margin-right: 30px;
+}
+
+.wvScreenToSmallWarning {
+  position: fixed;
+  display: block;
+  top: 0;
+  left: 0;
+  background-color: white;
+  color: #333;
+  width: 100%;
+  height: 100%;
+  z-index: 1000;
+}
+
+.wvScreenToSmallWarning-content {
+  padding: 10px;
+  text-align: center;
+}
+
+/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
+@media only screen and (min-width: 550px) and (min-height: 280px) { 
+  .wvScreenToSmallWarning {
+    display: none;
+  }
+}
+
+/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
+@media only screen and (min-width: 280px) and (min-height: 550px)  {
+  .wvScreenToSmallWarning {
+    display: none;
+  }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_layout.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,498 @@
+$topHeight: 42px;
+$bottomHeightSmall: 7rem; // On small width, we provide two-lines bottom zone to compensate the smaller width
+$bottomHeightLarge: 5rem; // On large width, we provide one-line bottom zone
+
+$asideWidth: 32rem;
+$asideMinifyWidth: 12rem;
+
+$asideRightMinifyWidth: 85px; // eq. 7.5rem * 12px - ( $asideRightPadding / 2 )
+$asideRightPadding: 10px;
+
+/* layout: left section */
+
+// Adapt left aside based on state (opened/closed).
+.wvLayoutLeft {
+    // Set general properties.
+    position:absolute;
+    z-index:2;
+    background-color:black;
+    width: $asideWidth;
+
+    // Position the left side below the top zone if it is shown
+    &.wvLayoutLeft--toppadding {
+        top: $topHeight;
+    }
+    &:not(.wvLayoutLeft--toppadding) {
+        top: 0;
+    }
+
+    // Position the left section over the bottom one if the latter is shown
+    &.wvLayoutLeft--bottompadding {
+        @media screen and (max-device-width: 374px) {
+            bottom: $bottomHeightSmall;
+        }
+        @media screen and (min-device-width: 375px) {
+            bottom: $bottomHeightLarge;
+        }
+    }
+    &:not(.wvLayoutLeft--bottompadding) {
+        bottom: 0;
+    }
+
+    // Position the left side on the left
+    left: 0;
+
+    // When layout left is shown, nothing special happens (default state)
+    &:not(.wvLayoutLeft--closed) {
+    }
+
+    // When layout left is closed, move it aside
+    &.wvLayoutLeft--closed {
+        transform: translateX(- $asideWidth); // Move all out of the screen
+        &.wvLayoutLeft--small {
+            transform: translateX(-$asideMinifyWidth);
+        }
+    }
+    &.wvLayoutLeft--small{
+        width: $asideMinifyWidth;
+        & .wvLayoutLeft__contentTop, & .wvLayoutLeft__contentMiddle, & .wvLayoutLeft__contentBottom{
+            width: 100%;
+        }
+    }
+}
+
+// Layout-Left Flexbox containers for the content.
+.wvLayoutLeft__content {
+    border-right: 1px solid #AAA;
+
+    // Display flex mode so optional actions can be positionned at the bottom
+    // side.
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+
+    // Make it scrollable.
+    overflow-y: auto;
+    height: 100%;
+}
+
+.wvLayoutLeft__contentTop {
+    // We have to set a static height since we use floating to make white space
+    // disapear in nested content.
+    // note: could be deactivate with the clearfix so we can have a dynamic height
+    // max-height: 6rem;
+    padding: 0rem 1rem 0rem 1rem;
+
+    // Enforce width even if there is a scrollbar on win/IE11 (-1 px for
+    // border).
+    width: $asideWidth - 0.1rem;
+
+    &:after{
+        content: "";
+        display:block;
+        height:0;
+        width:0;
+        clear:both;
+    }
+}
+
+.wvLayoutLeft__contentMiddle {
+    // Let the middle zone take as much space as required.
+    flex: 1 0 auto;
+
+    // Enforce width even if there is a scrollbar on win/IE11 (-1 px for
+    // border).
+    width: $asideWidth - 0.1rem;
+
+}
+
+.wvLayoutLeft__contentBottom {
+    // Enforce width even if there is a scrollbar on win/IE11 (-1 px for
+    // border).
+    width: $asideWidth - 0.1rem;
+}
+.wvLayout__leftBottom.wvLayout__leftBottom--enabled {
+    border-top: 1px solid hsla(0, 0%, 100%, 0.2);
+    margin-top: 1rem;
+    padding: 1rem;
+
+    // Prevent from taking more space than intended.
+    // flex-grow: 0;
+}
+
+.wvLayoutLeft__actions,
+%wvLayoutLeft__actions{
+    display:block;
+    position:absolute;
+    right:1px; // border
+    top: 50%;
+    transform: translateY(-50%);
+    width:25px;
+}
+.wvLayoutLeft__actions--outside{
+    @extend.wvLayoutLeft__actions;
+    right:-25px; // width + border
+}
+.wvLayoutLeft__action{
+    background-color:$primary;
+    opacity: 0.5;
+    color:white;
+    transition: none;
+    &:hover, &:focus{
+        opacity: 1;
+    }
+}
+
+
+/* layout: right section */
+
+// Adapt right aside based on state (opened/closed).
+.wvLayout__right {
+    display:block;
+    position:absolute;
+    z-index:2;
+    background-color:black;
+    width: $asideRightMinifyWidth;
+
+    // Position the left side below the top zone if it is shown
+    &.wvLayout__right--toppadding {
+        top: $topHeight;
+    }
+    &:not(.wvLayout__right--toppadding) {
+        top: 0;
+    }
+
+    // Position the right section over the bottom one if the latter is shown
+    &.wvLayout__right--bottompadding {
+        @media screen and (max-device-width: 374px) {
+            bottom: $bottomHeightSmall;
+        }
+        @media screen and (min-device-width: 375px) {
+            bottom: $bottomHeightLarge;
+        }
+    }
+    &:not(.wvLayout__right--bottompadding) {
+        bottom: 0;
+    }
+
+    // Position the right side on the right
+    right: 0;
+
+    // When layout right is shown, nothing special happens (default state)
+    &:not(.wvLayout__right--closed) {
+    }
+
+    // When layout right is closed, move it aside
+    &.wvLayout__right--closed {
+        transform: translateX(+ $asideRightMinifyWidth); // Move all out of the screen
+    }
+
+    // Set childrens to full height (so border-left appears at 100% height)
+    & > wv-layout-right,
+    & > wv-layout-right > .wvViewer__asideRight
+    {
+        display: block;
+        height: 100%;
+        width: 100%;
+    }
+}
+
+.wvAsideRight__content {
+    height: 100%;
+    float: left;
+
+    border-left: 1px solid #AAA;
+
+    padding: 0 $asideRightPadding/2;
+    width: $asideWidth;
+}
+
+.wvAsideRight__actions,
+%wvAsideRight__actions{
+    display:block;
+    position:absolute;
+    left:1px; // border
+    top: 50%;
+    transform: translateY(-50%);
+    width:25px;
+
+    // Compensate aside z-index to let user click on button when another button
+    // is behind the actions.
+    z-index: 3;
+}
+.wvAsideRight__actions--outside{
+    @extend.wvAsideRight__actions;
+    left:-25px; // width + border
+}
+.wvAsideRight__action{
+    background-color:$primary;
+    opacity: 0.5;
+    color:white;
+    transition: none;
+    &:hover, &:focus{
+        opacity: 1;
+    }
+}
+.wvAsideRight__fixOpenFullyTooltip + .tooltip { // Fix the "open fully" bad tooltip placement of the asideRight
+    left: -6.633em !important;
+    top: 1px !important;
+}
+
+
+/* layout: bottom section */
+
+// Set bottom section size & position
+.wvLayout__bottom {
+    position: absolute;
+
+    // Display the bottom bar in the bottom side
+    @media screen and (max-device-width: 374px) {
+        height: $bottomHeightSmall;
+    }
+    @media screen and (min-device-width: 375px) {
+        height: $bottomHeightLarge;
+    }
+
+    left: 0;
+    bottom: 0;
+    right: 0;
+
+    // Set grey background color (as it is only used to display notices)
+    background-color: hsl(0, 0%, 10%);
+}
+
+
+/* layout: main section */
+
+// Set main section size & position
+.wvLayout__main {
+    position: absolute;
+
+    // Align content (such as toolbar)
+    text-align: center;
+
+    // Position splitpane considering the toolbar when toolbar is present.
+    & .wvLayout__splitpane--toolbarAtTop {
+        display: block;
+        height: calc(100% - #{$toolbarHeight});
+        width: 100%;
+
+        position: relative;
+        top: $toolbarHeight;
+    }
+    & .wvLayout__splitpane--toolbarAtRight {
+        display: block;
+        height: 100%;
+        width: calc(100% - #{$toolbarHeight});
+    }
+
+    & .wvLayout__splitpane--bigToolbarAtTop {
+        display: block;
+        height: calc(100% - 68px);
+        width: 100%;
+
+        position: relative;
+        top: 68px;
+    }
+    & .wvLayout__splitpane--bigToolbarAtRight {
+        display: block;
+        height: 100%;
+        width: calc(100% - 68px);
+    }
+
+    // Position the main section below the top zone if the latter is shown
+    &.wvLayout__main--toppadding {
+        top: $topHeight;
+    }
+    &:not(.wvLayout__main--toppadding) {
+        top: 0;
+    }
+
+    // Position the main section over the bottom one if the latter is shown
+    &.wvLayout__main--bottompadding {
+        bottom:440px;
+        @media screen and (max-device-width: 374px) {
+            bottom: $bottomHeightSmall;
+        }
+        @media screen and (min-device-width: 375px) {
+            bottom: $bottomHeightLarge;
+        }
+    }
+    &:not(.wvLayout__main--bottompadding) {
+        bottom: 0;
+    }
+
+    // Make the main content fill the screen by default
+    // Depending on the browser, the left and right attributes are more
+    // optimized than padding's ones. The reason is that they based upon
+    // absolute positioning. The require no contextual positioning calculation
+    // and are less performance intensive, especially concidering transition
+    // animation.
+    right: 0;
+    left: 0;
+
+    // transition: 0.6s left ease, 0.6s right ease;
+
+    // Adapt main section's size based on aside left's state (opened/closed)
+    // 1. When aside left is not hidden , move the main section 300 pixel to
+    //   the right
+    &.wvLayout__main--leftpadding {
+        left: $asideWidth;
+    }
+    // 2. When aside left is hidden, let the main take 100% of the place
+    &:not(.wvLayout__main--leftpadding, .wvLayout__main--smallleftpadding) {
+        left: 0px;
+    }
+    &.wvLayout__main--smallleftpadding {
+        left: $asideMinifyWidth;
+    }
+
+    // Adapt main section's size based on aside right's state (opened/closed)
+    // 1. When aside right is not hidden , move the main section 84 pixel to
+    //   the left
+    &.wvLayout__main--rightpadding {
+        right: $asideRightMinifyWidth;
+    }
+    // 2. When aside right is hidden, let the main take 100% of the place
+    &:not(.wvLayout__main--rightpadding) {
+        right: 0px;
+    }
+}
+
+/* global */
+.popover {
+    // Set back black as default popover text color
+    color: black;
+}
+
+.wvViewer__editor--full{
+    position:absolute;
+    top:0;
+    right:0;
+    z-index:10;
+    opacity:0;
+    transform: translateX(100%);
+    width:100%;
+    height:100%;
+    background-color:white;
+    color:$text-color;
+    &.opened{
+        opacity:1;
+        transform: translateX(0);
+    }
+}
+
+.wvViewer__topBar{
+    width:100%;
+    // margin-top: 0.5rem;
+
+    // Allow user to scroll through the toolbar if screen is too small. Note we
+    // can't use z-index properly to show buttons on top of the viewer, as any
+    // popover will appear behind them (even with higher z-index) due to an
+    // overflow property hidden somewhere.
+    overflow-y: auto;
+    white-space: nowrap;
+    max-width: 100%;
+}
+.wvViewer__buttonGroup{
+    display:inline-block;
+}
+.wvViewer__buttonGroup--asideWidth{
+    width: $asideWidth;
+    padding-right: 1rem;
+}
+.wvViewer__buttonGroup--contentWidth{
+    width: calc(100% - #{$asideWidth});
+    padding-left: 1rem;
+    max-height: 4.2rem; // Make sure mobile keeps the menubar below a certain size
+}
+.wvViewer__iframe{
+    position:absolute;
+    left:0;
+    top:0;
+}
+
+/* bottom bar */
+.wvViewer__bottomBar,
+%wvViewer__bottomBar{
+    position:absolute;
+    left:0;
+    bottom:0;
+    width:100%;
+    background-color:#111111;
+}
+
+.wvViewer__bottomBar--expanded{
+    @extend .wvViewer__bottomBar;
+    height: 80px; //total height of the last serieList cell (64 + 10(margin bottom previous item) + item padding bottom +1 border-width (top item)
+    //border-top: 1px solid rgba(255,255,255,0.1);
+    color:white;
+
+    .wvViewer__timeline{
+        width: calc(100% - 80px);
+    }
+    .wvTimeline__hotspots{
+        bottom: -40px;
+    }
+}
+
+.wvViewer__bottomBar--minimized{
+    @extend .wvViewer__bottomBar;
+    color: white;
+
+    padding-top: 0.5rem;
+    padding-bottom: 0.5rem;
+    padding-left: 2.5rem;
+
+    .wvTimeline__hotspot{
+        top: -40px;
+        opacity:0;
+        visibility:hidden;
+        z-index:-1;
+        // transition: all 0.3s ease 0.6s; //adding a delay when mouse leave
+        // transition-property: opacity, visibility, z-index;
+    }
+    &:hover .wvTimeline__hotspot{
+        opacity:1;
+        visibility: visible;
+        z-index:5;
+        transition-delay: 0s;
+    }
+}
+
+.wvViewer__timeline{
+    height:24px;
+    //background-color:rgba(1,1,1,0.2);
+    line-height: 24px;
+    vertical-align: middle;
+    width:100%;
+}
+
+.wvViewer__trademark{
+    display:inline-block;
+    float:right;
+    width:80px;
+    height:80px;
+    float:right;
+    line-height: 80px;
+    vertical-align: middle;
+    text-align: center;
+}
+.wvTimeline__action--text{
+
+}
+.wvTimeline__input{
+    border-radius: 3px;
+    &:focus{
+        outline:none;
+    }
+    margin-top:2px;
+    border: 1px solid $border-color;
+}
+
+.wvTimeline__actions{
+    display:inline-block;
+    border-right: 1px solid $border-color;
+}
+.wvTimeline__wrapper{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_notice.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,55 @@
+wv-notice {
+    display: block;
+    height: 100%;
+    width: 100%;
+}
+
+.wvNotice {
+    // Set padding
+    padding: 0.5rem 0.25rem;
+
+    // Fill parent element so text can be centered
+    height: 100%;
+}
+
+.wvNotice__text {
+    // Center text 
+    position: relative;
+    top: 50%;
+    transform: translateY(-50%);
+    text-align: center;
+    margin-left: 1rem;
+
+    // Text style
+    font-weight: 400;
+    color: hsl(0, 0%, 70%);
+
+    // Keep space for button
+    float: left;
+    width: calc(100% - 7rem); // 3.5 rem + 3 rem margin (incl. button margin + text margin)
+}
+
+.wvNotice__closeButton {
+    // Position button on right
+    float: right;
+    margin-right: 0.5em;
+
+    // Center button vertically
+    position: relative;
+    top: 50%;
+    transform: translateY(-50%);
+
+    // Set button size
+    width: 3.5rem;
+    height: 2.5rem; // half the bottom zone height
+
+    // Configure button icon
+    text-align: center;
+    font-size: 1em;
+    font-weight: 100;
+    line-height: 2.2rem;
+
+    // Set button style
+    cursor: pointer;
+    border: 1px solid hsl(0, 0%, 27%);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_print.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,56 @@
+.wvPrintExclude{
+    display:none;
+}
+
+.wvPrintFullPage{
+    width: 100% !important;
+    height: 100% !important;
+    position: absolute !important;
+    top: 0 !important;
+    left: 0 !important;
+    display:block !important;
+}
+
+.wvLayout__main{
+    top: 0 !important;
+    right: 0 !important;
+    left: 0 !important;
+    bottom: 0 !important;
+}
+
+.wvPrintViewer{
+    width: 100%;
+    height:100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.wvPrintViewer canvas{
+    max-width: 100% !important;
+    max-height: 100% !important;
+    margin:auto;
+}
+
+.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft{
+    &, & * {
+        background-color: black !important;
+        -webkit-print-color-adjust: exact !important; 
+        color-adjust: exact !important;
+        color: orange !important;
+    }
+}
+.tooltip{
+    display: none !important;
+}
+body{
+    margin: 0;
+    padding: 0;
+    position: relative;
+    &, *{
+        background-color: black !important;
+        -webkit-print-color-adjust: exact !important; 
+    }
+    width: 8.5in;
+    height: 11in;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_selectionActionlist.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,9 @@
+.wvSelectionActionlist {
+    display: block;
+
+    text-align: center;
+}
+
+.wvSelectionActionlist__bottom {
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_serieslist.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,182 @@
+$gray: gray;
+$blue: hsla(204, 70%, 53%, 0.7);
+$red: rgba(206, 0, 0, 0.7);
+$green: rgba(0, 160, 27, .7);
+$yellow: rgba(220, 200  , 0, .9);
+$violet: rgba(255, 31, 255, .7);
+
+$borderColor: rgba(255, 255, 255, 0.8);
+$borderColorActive: rgba(255, 255, 255, 0.6);
+$borderColorHighlighted: rgba(255, 255, 255, 1);
+$pictureSize: 6.5rem;
+
+.wvSerieslist {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.wvSerieslist__seriesItem--selectable {
+    // Pointer cursor (for `ng-click`)
+    cursor: pointer !important;
+
+    // Lighten up the icon on hover
+    &:hover {
+        color: white;
+    }
+}
+
+.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa { // Make sure it has precedence over .fa class {
+    position: absolute;
+
+    // Fill the li element
+    width: 100%;
+    height: 100%;
+
+    // Fill the li element with the fontawesome icon
+    font-size: $pictureSize/2;
+    line-height: $pictureSize;
+    text-align: center;
+}
+
+.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa { // Make sure it has precedence over .fa class
+    // Grey out (since no report is available)
+    color: #c3c3c3;
+
+    // Diagonal line crossing report icon (to tell none are available)
+    // position: relative;
+
+    &::after { // use after to not conflicts with font-awesome :before
+        position: absolute;
+
+        left: 0;
+        top: 50%;
+        right: 0;
+
+        transform: rotate(-45deg) scaleX(0.9);
+
+        border-top: 5px solid;
+        border-color: inherit;
+
+        content: "";
+    }
+}
+
+.wvSerieslist__picture{
+    display: inline-block;
+    font-size: 14px;
+    width: $pictureSize;
+    height: $pictureSize;
+    position: relative;
+
+    // Move picture behind the `toggle layout@ left` button.
+    z-index: -1;
+}
+.wvSerieslist__badge {
+    position: absolute;
+    bottom:5px;
+    right:5px;
+    font-size:10px;
+    line-height:15px;
+    width:15px;
+    height:15px;
+    border-radius: 100%;
+    background-color: $gray;
+    vertical-align: middle;
+    text-align: center;
+    font-weight: bold;
+}
+.wvSerieslist__information{
+    font-size: 14px;
+    float: right;
+    padding-left: 1rem;
+    width: calc(100% - #{$pictureSize});
+    height: $pictureSize;
+}
+.wvSerieslist__label{
+    white-space: nowrap;
+    width:calc(100% - 10px);
+    overflow:hidden;
+    height:$pictureSize/2;
+    line-height:$pictureSize/2;
+    vertical-align: middle;
+}
+.wvSerieslist__timeline{
+    //border-top: 0.1rem solid rgba(255,255,255,0.2);
+    height:$pictureSize/2;
+    line-height:$pictureSize/2;
+    vertical-align: middle;
+}
+
+.wvSerieslist__seriesItem {
+    // anchor
+    position: relative;
+
+    // unstyle list
+    padding-left: 0;
+    list-style: none;
+    font-size: 0;
+
+    // mimic on hover border for draggable
+    border-right: 0.2rem solid transparent;
+    border-left: 0.2rem solid transparent;
+    border-top: 0.2rem solid transparent;
+    border-bottom: 0.2rem solid transparent;
+    border-corner-shape: notch;
+
+    line-height: 0px;
+    margin: 0.1rem;
+
+    &.active{
+        border-color: $borderColorActive;
+        border-style: solid;
+    }
+
+    &.highlighted{
+        border-color: $borderColorHighlighted;
+        border-style: solid;
+    }
+
+    &:hover, &:focus, &.focused{
+        border-style: dashed;
+        border-color: $borderColor;
+    }
+}
+
+.wvSerieslist__seriesItem--list {
+    display: block;
+}
+.wvSerieslist__seriesItem--grid {
+    display: inline-block;
+}
+.wvSerieslist__seriesItem--oneCol{
+    text-align: center;
+}
+
+.wvSerieslist__seriesItem--activated,
+.wvSerieslist__videoItem--activated,
+.wvSerieslist__pdfItem--activated {
+    border: 0.2rem solid hsla(204, 70%, 53%, 1) !important;
+}
+
+// Color related modifiers
+.wvSerieslist__badge--blue {
+    @extend .wvSerieslist__badge;
+    background-color: $blue;
+}
+.wvSerieslist__badge--red {
+    @extend .wvSerieslist__badge;
+    background-color: $red;
+}
+.wvSerieslist__badge--green {
+    @extend .wvSerieslist__badge;
+    background-color: $green;
+}
+.wvSerieslist__badge--yellow {
+    @extend .wvSerieslist__badge;
+    background-color: $yellow;
+}
+.wvSerieslist__badge--violet {
+    @extend .wvSerieslist__badge;
+    background-color: $violet;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_studyInformationBreadcrumb.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,39 @@
+.wvStudyInformationBreadcrumb {
+}
+
+.wvStudyInformationBreadcrumb__patient {
+    display: inline-block;
+    background-color: rgba(255, 255, 255, 0.15);
+    padding: 0.2rem 1rem 0.3rem 1rem;
+    text-align: center;
+    font-size: 1em;
+    margin: 0.6rem;
+    font-weight: 400;
+    line-height: 2.3rem;
+
+    // Prevent doubled margin with the next item
+    margin-right: 0;
+}
+.wvStudyInformationBreadcrumb__patientName {
+
+}
+.wvStudyInformationBreadcrumb__patientBirthDate {
+
+}
+
+.wvStudyInformationBreadcrumb__study {
+    display: inline-block;
+    background-color: rgba(255, 255, 255, 0.15);
+    padding: 0.2rem 1rem 0.3rem 1rem;
+    text-align: center;
+    font-size: 1em;
+    margin: 0.6rem;
+    font-weight: 400;
+    line-height: 2.3rem;
+}
+.wvStudyInformationBreadcrumb__studyDescription {
+
+}
+.wvStudyInformationBreadcrumb__studyDate {
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_studyIsland.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,90 @@
+$gray: gray;
+$blue: hsla(204, 70%, 53%, 0.7);
+$red: rgba(206, 0, 0, 0.7);
+$green: rgba(0, 160, 27, .7);
+$yellow: rgba(220, 200  , 0, .9);
+$violet: rgba(255, 31, 255, .7);
+
+
+%wvStudyIsland {
+    margin: 1rem 1rem 1rem 1rem;
+    border: 0.3rem solid $gray;
+}
+
+%wvStudyIsland__header {
+    background-color: $gray;
+    padding: 0.5rem 0.5rem 0.8rem 0.5rem;
+    line-height: 1.35rem;
+    width: 100%;
+}
+.wvStudyIsland__actions {
+    float: right;
+
+    // Compensate header padding (since the inner download study button
+    // actually has margin).
+    margin-top: -0.8rem;
+    margin-right: -0.8rem;
+}
+
+.wvStudyIsland__actions--oneCol {
+    float: none;
+    text-align: center;
+}
+
+.wvStudyIsland__main {
+    padding: 0.4rem; // 0.7rem - 0.3rem (2px transparent border + 1px margin)
+    color: hsl(0, 0%, 100%);
+    width: 100%; // let some space for the 4-columns based items
+}
+
+
+// Color modifiers
+.wvStudyIsland--blue {
+    @extend %wvStudyIsland;
+    border-color: $blue;
+}
+
+.wvStudyIsland__header--blue {
+    @extend %wvStudyIsland__header;
+    background-color: $blue;
+}
+
+.wvStudyIsland--red {
+    @extend %wvStudyIsland;
+    border-color: $red;
+}
+
+.wvStudyIsland__header--red {
+    @extend %wvStudyIsland__header;
+    background-color: $red;
+}
+
+.wvStudyIsland--green {
+    @extend %wvStudyIsland;
+    border-color: $green;
+}
+
+.wvStudyIsland__header--green {
+    @extend %wvStudyIsland__header;
+    background-color: $green;
+}
+
+.wvStudyIsland--yellow {
+    @extend %wvStudyIsland;
+    border-color: $yellow;
+}
+
+.wvStudyIsland__header--yellow {
+    @extend %wvStudyIsland__header;
+    background-color: $yellow;
+}
+
+.wvStudyIsland--violet {
+    @extend %wvStudyIsland;
+    border-color: $violet;
+}
+
+.wvStudyIsland__header--violet {
+    @extend %wvStudyIsland__header;
+    background-color: $violet;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_toolbar.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,178 @@
+.wvToolbar {
+    position: absolute;
+}
+
+.wvToolbar--top {
+    top: 0;
+    height: $toolbarHeight;
+
+    // Position the toolbar to the right (even if it's positioned
+    // horizontally).
+    right: 0;
+    text-align: right;
+
+    // Allow user to scroll through the toolbar if screen is too small. Note we
+    // can't use z-index properly to show buttons on top of the viewer, as any
+    // popover will appear behind them (even with higher z-index) due to an
+    // overflow property hidden somewhere.
+    // overflow-y: auto;
+    white-space: nowrap;
+    max-width: 100%;
+}
+
+.wvToolbar--right {
+    right: 0;
+    width: 42px; // != $toolbarHeight since we're in the reverse order.
+
+    // Allow user to scroll through the toolbar if screen is too small.
+    // overflow-x: auto;
+    height: 100%;
+    z-index: 2;
+    &.wvToolbar--big{
+        width: 68px;
+    }
+}
+
+/* Splitpane Grid Configuration */
+
+.wvToolbar__splitpaneConfigPopover {
+    // Prevent white space between buttons.
+    font-size: 0;
+}
+
+.wvToolbar__splitpaneConfigNotice {
+    font-size: 1.25rem;
+    font-style: italic;
+    text-align: center;
+
+    color: #333;
+}
+
+input[type="radio"].wvToolbar__splitpaneConfigButtonInput {
+    // Hide the radio input, but make it fit the label, so we can rely on its
+    // html caracteristics without having to suffer from its design.
+    position: absolute;
+    width: 0;
+    height: 0;
+    left: 0;
+    top: 0;
+    bottom: 2px;
+    right: 0;
+    opacity: 0;
+}
+
+/* Windowing Preset */
+
+.wvToolbar__windowingPresetConfigPopover {
+
+}
+.wvToolbar__windowingPresetConfigNotice {
+    font-size: 1.25rem;
+    font-style: italic;
+    text-align: center;
+
+    color: #333;
+}
+
+.wvToolbar__windowingPresetList {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+
+    font-size: 1.5rem;
+}
+.wvToolbar__windowingPresetListItem {
+    // Remove <a> default styles. Take care - this class may either be used
+    // with <a> or <button>.
+    &:hover {
+        text-decoration: none;
+        color: white;
+    }
+
+    // Remove <button> default styles.
+    outline: none;
+    background-color: transparent;
+    border: none;
+
+    // Set relative to position button absolutely
+    position: relative;
+
+    // Style button
+    display: inline-block;
+    cursor: pointer;
+    font-variant: small-caps;
+    text-transform: lowercase;
+    text-align: center;
+    font-size: 1.3rem;
+    font-weight: 400;
+    line-height: 2.2rem;
+    color: hsl(0, 0%, 85%);
+    transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
+
+    // Position button
+    margin: 0;
+    min-width: 3rem;
+    padding: 0 10px;
+    line-height: 3.6rem;
+
+
+
+    // Prevent multi line buttons.
+    max-height: 2.8rem;
+    max-width: 100%;
+    overflow: hidden;
+
+    // Set margin
+    margin: 0.6rem;
+    margin-left: 0rem;
+    margin-right: 0rem;
+    & + & {
+        margin-left: 0.7rem;
+    }
+
+    // Set button size
+    line-height: 2rem;
+
+    // Align text
+    padding-top: 0.1rem;
+    padding-bottom: 0.5rem;
+
+    // Style button
+    font-size: 1.4rem;
+    border: 1px solid hsl(0, 0%, 27%);
+
+    // Set best looking font with small-caps.
+    font-family: Arial;
+
+    // Change background on hover
+    background-color: hsl(0, 0%, 0%);
+    &:hover {
+        background-color: hsl(0, 0%, 10%);
+    }
+
+    & > .glyphicon { // used with the same element as glyphicons
+        // Position button
+        position: relative;
+        display: inline-block;
+        top: 3px;
+        margin-right: 4px;
+    }
+
+    // Text color
+    color: hsl(0, 0%, 10%);
+    border: 1px solid hsl(0, 0%, 73%);
+
+    // Change background on hover
+    background-color: hsl(0, 0%, 100%);
+    &:hover {
+        color: hsl(0, 0%, 10%);
+        background-color: hsl(0, 0%, 90%);
+    }
+
+
+    width: 100%;
+    margin: 0;
+    margin-left: 0 !important;
+    border-top: none;
+    border-bottom: none;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_variable.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,14 @@
+$primary-lighten: #57aae1;
+$primary: #3498db;
+$dangerColor: #E63F24;
+
+$panel-header: #fafafa;
+$border-color: #e7e7e7;
+$lightGrey: #cccccc;
+$text-color: #666666;
+
+$darkGrey: #333333;
+$darkBlue: #203A6F;
+$blueGrey: #303E4D;
+
+$toolbarHeight: 42px;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/_video.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,9 @@
+.wvVideo {
+    // Align component vertically & horizontally
+    position: absolute;
+    top:50%;
+    left:0;
+    width: 100%;
+    height: auto;
+    transform: translateY(-50%);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/styles.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,32 @@
+// bower:scss
+// endbower
+
+@import "webviewer.main.scss";
+@import "webviewer.components.scss";
+
+
+@media print {
+    @import "print";
+}
+
+.closePrintButton{
+    display:none;
+}
+
+body.print{
+    @import "print";
+
+    @media screen {
+        .closePrintButton{
+            display:block;
+            position: fixed;
+            top: 0;
+            right: 0;
+            padding: 10px;
+            font-size: 24px;
+            background-color: black;
+            color: white;
+            border: none;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/tb-group.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,36 @@
+.tbGroup{
+    position:relative;
+}
+.tbGroup__buttons--base,
+%tbGroup__buttons--base{
+    z-index: 5;
+    background-color: black;
+    position: absolute;
+}
+
+.tbGroup__buttons--bottom{
+    @extend .tbGroup__buttons--base;
+    right:0;  // let the element at it's initial position but align him in right (natural position is below the toggl button element)
+    display:block;
+}
+.tbGroup__buttons--left{
+    @extend .tbGroup__buttons--base;
+    right:100%;
+    top:0;
+    display:block;
+}
+.tbGroup__icon{
+    display:block;
+    position: absolute;
+    bottom:0;
+    left:0;
+
+    width: 0;
+    height: 0;
+    border-style: solid;
+    border-width: 10px 0 0 10px;
+    border-color: transparent transparent transparent rgba(255,255,255,0.1);
+    &.active{
+        border-color: transparent transparent transparent $primary;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/webviewer.components.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,59 @@
+/* wvp-ui stuffs */
+wv-webviewer {
+    display: block;
+    height: 100%;
+    overflow: hidden;
+}
+
+@import "variable";
+@import "button";
+@import "exitButton";
+@import "studyIsland";
+@import "helpers";
+@import "notice";
+@import "layout";
+@import "serieslist";
+@import "toolbar";
+@import "video";
+@import "studyInformationBreadcrumb";
+@import "selectionActionlist";
+
+/* wvb-ui stuffs */
+@import "wv-overlay.scss";
+@import "wv-pdf-viewer.scss";
+@import "wv-splitpane.scss";
+@import "wv-timeline.scss";
+@import "wv-timeline-controls.scss";
+@import "wv-loadingbar.scss";
+@import "wv-disclaimer";
+@import "tb-group";
+
+wv-viewport { // make sure the element is sized when using with drag & drop
+  display: inline-block;
+  width: 100%;
+  height: 100%;
+
+  > div {
+    position: relative;
+    width: 100%;
+    height: 100%;
+  }
+
+  // We don't set 100% width/height to the canvas element, as it would stretch
+  // the pixels. Instead, we center it for more fluid transition when pane's 
+  // width changes (at least the content is kept centered even if the js hasn't
+  // yet reacted to layout reflow).
+  > div > .wv-cornerstone-enabled-image {
+    width: 100%;
+    height: 100%;
+    text-align: center;
+  }
+}
+
+.wv-draggable-clone {
+  width: 150px;
+  height: 150px;
+  background-color: rgba(255,255,255,0.25);
+
+  // No need to set z-index (already done by jquery ui lib).
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/webviewer.main.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,90 @@
+.browsehappy {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0;
+}
+
+.wv-html, .wv-body {
+    height: 100%;
+    width: 100%;
+
+    margin: 0;
+    padding: 0;
+    
+    overflow: hidden;
+}
+.wv-body {
+    background-color: black;
+    color: white;
+    position: relative;
+    overflow: hidden;
+
+    font-family: "Open Sans", Helvetica, Arial, sans-serif;
+    -webkit-tap-highlight-color: hsla(0, 0%, 0%, 0);
+    font-size: 13px;
+    font-weight: 400;
+    line-height: 1.49;
+    font-size-adjust: 100%;
+
+    // Smooth text
+    -moz-osx-font-smoothing: grayscale !important;
+    font-smoothing: antialiased !important;
+    -webkit-font-smoothing: antialiased !important;
+}
+
+.wvLoadingScreen {
+    width: 100%;
+    height: 100%;
+    background-color: black;
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 9999;
+
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.wvLoadingSpinner {
+    margin: 100px auto 0;
+    width: 70px;
+    text-align: center;
+}
+  
+.wvLoadingSpinner > div {
+    width: 18px;
+    height: 18px;
+    background-color: #FFF;
+
+    border-radius: 100%;
+    display: inline-block;
+    -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+    animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+}
+
+.wvLoadingSpinner .bounce1 {
+    -webkit-animation-delay: -0.32s;
+    animation-delay: -0.32s;
+}
+
+.wvLoadingSpinner .bounce2 {
+    -webkit-animation-delay: -0.16s;
+    animation-delay: -0.16s;
+}
+
+@-webkit-keyframes sk-bouncedelay {
+    0%, 80%, 100% { -webkit-transform: scale(0) }
+    40% { -webkit-transform: scale(1.0) }
+}
+
+@keyframes sk-bouncedelay {
+    0%, 80%, 100% { 
+        -webkit-transform: scale(0);
+        transform: scale(0);
+    } 40% { 
+        -webkit-transform: scale(1.0);
+        transform: scale(1.0);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-disclaimer.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,7 @@
+.disclaimer{
+    color: $dangerColor;
+    background-color: #303030;
+    padding:5px;
+    text-align: center;
+    font-weight: bold;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-loadingbar.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,33 @@
+.wv-loadingbar-image-bar {
+    cursor: pointer;
+}
+.wv-loadingbar-not-loaded {
+    fill: rgba(255, 255, 255, 0.1);
+}
+.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality {
+    transition: none;
+}
+.wv-loadingbar-not-loaded:hover {
+    fill: rgba(255, 255, 255, 0.2);
+}
+.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality {
+    fill:rgba(0, 255, 0, 0.7);
+}
+.wv-loadingbar-LOSSLESS-quality:hover,
+.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active,
+.wv-loadingbar-PIXELDATA-quality:hover,
+.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active {
+    fill:rgba(0, 255, 0, 1);
+}
+.wv-loadingbar-LOW-quality {
+    fill:rgba(255, 0, 0, 0.7);
+}
+.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active {
+    fill:rgba(255, 0, 0, 1);
+}
+.wv-loadingbar-MEDIUM-quality {
+    fill:rgba(255, 95, 0, 0.7);
+}
+.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active {
+    fill:rgba(255, 95, 0, 1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-overlay.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,135 @@
+$gray: gray;
+$blue: hsla(204, 70%, 53%, 0.7);
+$red: rgba(206, 0, 0, 0.7);
+$green: rgba(0, 160, 27, .7);
+$yellow: rgba(220, 200  , 0, .9);
+$violet: rgba(255, 31, 255, .7);
+
+.wv-overlay {
+    // width&height is 0x0 to avoid capturing viewport events
+    color: orange;
+}
+
+.wv-overlay-icon {
+    width: 64px;    
+}
+
+.wvOverlay__studyBadge {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 1.5rem;
+    height: 1.5rem;
+    background-color: $gray;
+    z-index: 1;
+}
+
+.wv-overlay-topleft {
+    position: absolute;
+    top: 0rem;
+    left: 0rem;
+    text-align: left;
+}
+
+.wv-overlay-topright {
+    position: absolute;
+    top: 0rem;
+    right: 0rem;
+    text-align: right;
+}
+
+.wv-overlay-bottomright {
+    position: absolute;
+    bottom: 2em; // save 2em for the timeline
+    right: 0rem;
+    text-align: right;
+}
+
+.wv-overlay-bottomleft {
+    position: absolute;
+    bottom: 2em; // save 2em for the timeline
+    left: 0rem;
+    text-align: left;
+}
+
+.wv-overlay-timeline-wrapper {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1; // Make sure the representation of the selected image on the timeline appear on top of other overlay panels
+}
+
+.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft {
+    padding: 2rem;
+    transition: color 500ms, background-color 500ms;
+    background-color: rgba(0, 0, 0, 0.66);
+}
+
+.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover {
+    background-color: rgba(0, 0, 0, 0.9);
+}
+
+.wvPaneOverlay {
+    position: absolute;
+    top: 50%;
+    width: 100%;
+    transform: translateY(-50%);
+
+    font-weight: 100;
+    text-align: center;
+    color: white;
+    font-size: 2rem;
+}
+
+.wv-overlay-scrollbar-loaded {
+    position: absolute;
+    bottom:0;
+    left:0;
+    height: 5px;
+    background-color: red;
+    will-change: right;
+    transform-origin: 0% 50%;
+}
+
+.wv-overlay-scrollbar-loading {
+    position: absolute;
+    bottom:0;
+    left:0;
+    height: 5px;
+    background-color: #660000;
+    will-change: right;
+    transform-origin: 0% 50%;
+}
+
+.wv-overlay-scrollbar-text {
+    position: absolute;
+    bottom: calc(1em + 5px);
+    left: 5px;
+    height: 1em;
+    color: red;
+    font-size: 0.8em;
+    font-family: helvetica;
+}
+
+// Color related modifiers
+.wvOverlay__studyBadge--blue {
+    @extend .wvOverlay__studyBadge;
+    background-color: $blue;
+}
+.wvOverlay__studyBadge--red {
+    @extend .wvOverlay__studyBadge;
+    background-color: $red;
+}
+.wvOverlay__studyBadge--green {
+    @extend .wvOverlay__studyBadge;
+    background-color: $green;
+}
+.wvOverlay__studyBadge--yellow {
+    @extend .wvOverlay__studyBadge;
+    background-color: $yellow;
+}
+.wvOverlay__studyBadge--violet {
+    @extend .wvOverlay__studyBadge;
+    background-color: $violet;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-pdf-viewer.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,1761 @@
+wv-pdf-viewer {
+    display: block;
+    width: 100%;
+    height: 100%;
+}
+
+#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton { // We need high priority, !important keywords don't work
+    background-color: inherit;
+    color: hsl(0, 0%, 100%);
+    border: none;
+
+    padding: 2px;
+    margin-left: 4px;
+    margin-right: 2px;
+
+    &:hover {
+        color: black;
+    }
+}
+
+.fa.fa-window-close.wv-pdf-viewer-closebuttonicon { // We need high priority
+    font-size: 2rem;
+    line-height: 28px; // pdf.js toolbar size (- closebutton margin)
+}
+
+// The following code has been generated via:
+// 
+// ```bash
+// cd bower_components/pdf.js-viewer/
+// lessc --global-var='pdfjsImagePath="../images/pdf.js-viewer"' viewer.less viewer.css
+// ```
+
+.pdfjs .textLayer {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+  opacity: 0.2;
+}
+.pdfjs .textLayer > div {
+  color: transparent;
+  position: absolute;
+  white-space: pre;
+  cursor: text;
+  -webkit-transform-origin: 0 0;
+  -moz-transform-origin: 0 0;
+  -o-transform-origin: 0 0;
+  -ms-transform-origin: 0 0;
+  transform-origin: 0 0;
+}
+.pdfjs .textLayer .highlight {
+  margin: -1px;
+  padding: 1px;
+  background-color: #b400aa;
+  border-radius: 4px;
+}
+.pdfjs .textLayer .highlight.begin {
+  border-radius: 4px 0 0 4px;
+}
+.pdfjs .textLayer .highlight.end {
+  border-radius: 0 4px 4px 0;
+}
+.pdfjs .textLayer .highlight.middle {
+  border-radius: 0;
+}
+.pdfjs .textLayer .highlight.selected {
+  background-color: #006400;
+}
+.pdfjs .textLayer ::selection {
+  background: #00f;
+}
+.pdfjs .textLayer ::-moz-selection {
+  background: #00f;
+}
+.pdfjs .pdfViewer .canvasWrapper {
+  overflow: hidden;
+}
+.pdfjs .pdfViewer .page {
+  direction: ltr;
+  width: 816px;
+  height: 1056px;
+  margin: 1px auto -8px;
+  position: relative;
+  overflow: visible;
+  border: 9px solid transparent;
+  background-clip: content-box;
+  border-image: url('../images/pdf.js-viewer/shadow.png') 9 9 repeat;
+  background-color: #fff;
+}
+body {
+  height: 100%;
+}
+.pdfjs .pdfViewer.removePageBorders .page {
+  margin: 0 auto 10px;
+  border: none;
+}
+.pdfjs .pdfViewer .page canvas {
+  margin: 0;
+  display: block;
+}
+.pdfjs .pdfViewer .page .loadingIcon {
+  position: absolute;
+  display: block;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: url('../images/pdf.js-viewer/loading-icon.gif') center no-repeat;
+}
+.pdfjs .pdfViewer .page .annotLink > a:hover {
+  opacity: .2;
+  background: #ff0;
+  box-shadow: 0 2px 10px #ff0;
+}
+.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0;
+}
+.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0;
+}
+.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
+  margin-bottom: 100%!important;
+  border: 0;
+}
+.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0;
+}
+.pdfjs .pdfViewer .page .annotText > img {
+  position: absolute;
+  cursor: pointer;
+}
+.pdfjs .pdfViewer .page .annotTextContentWrapper {
+  position: absolute;
+  width: 20em;
+}
+.pdfjs .pdfViewer .page .annotTextContent {
+  z-index: 200;
+  float: left;
+  max-width: 20em;
+  background-color: #FF9;
+  box-shadow: 0 2px 5px #333;
+  border-radius: 2px;
+  padding: .6em;
+  cursor: pointer;
+}
+.pdfjs .pdfViewer .page .annotTextContent > h1 {
+  font-size: 1em;
+  border-bottom: 1px solid #000;
+  padding-bottom: 0.2em;
+}
+.pdfjs .pdfViewer .page .annotTextContent > p {
+  padding-top: 0.2em;
+}
+.pdfjs .pdfViewer .page .annotLink > a {
+  position: absolute;
+  font-size: 1em;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+.pdfjs .pdfViewer .page .annotLink > a {
+  background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA\ LAAAAAABAAEAAAIBRAA7") 0 0 repeat;
+}
+.pdfjs * {
+  padding: 0;
+  margin: 0;
+}
+html {
+  height: 100%;
+  font-size: 10px;
+}
+.pdfjs input,
+.pdfjs button,
+.pdfjs select {
+  font: message-box;
+  outline: none;
+}
+.pdfjs .hidden {
+  display: none !important;
+}
+.pdfjs [hidden] {
+  display: none !important;
+}
+.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen {
+  top: 0;
+  border-top: 2px solid transparent;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -webkit-user-select: none;
+}
+.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen {
+  top: 0;
+  border-top: 2px solid transparent;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -moz-user-select: none;
+}
+.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen {
+  top: 0!important;
+  border-top: 2px solid transparent;
+  width: 100%;
+  height: 100%;
+  overflow: hidden!important;
+  cursor: none;
+  -ms-user-select: none;
+}
+.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop {
+  background-color: #000;
+}
+.pdfjs #viewerContainer.pdfPresentationMode:fullscreen {
+  top: 0;
+  border-top: 2px solid transparent;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+}
+.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) {
+  display: none;
+}
+.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) {
+  display: none;
+}
+.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) {
+  display: none !important;
+}
+.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) {
+  display: none;
+}
+.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div {
+  cursor: none;
+}
+.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div {
+  cursor: none;
+}
+.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div {
+  cursor: none;
+}
+.pdfjs .pdfPresentationMode:fullscreen .textLayer > div {
+  cursor: none;
+}
+.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *,
+.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
+  cursor: default;
+}
+.pdfjs .outerCenter {
+  pointer-events: none;
+  position: relative;
+}
+html[dir='ltr'] .pdfjs .outerCenter {
+  float: right;
+  right: 50%;
+}
+html[dir='rtl'] .pdfjs .outerCenter {
+  float: left;
+  left: 50%;
+}
+.pdfjs .innerCenter {
+  pointer-events: auto;
+  position: relative;
+}
+html[dir='ltr'] .pdfjs .innerCenter {
+  float: right;
+  right: -50%;
+}
+html[dir='rtl'] .pdfjs .innerCenter {
+  float: left;
+  left: -50%;
+}
+.pdfjs #outerContainer {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  background-color: #404040;
+  background-image: url('../images/pdf.js-viewer/texture.png');
+}
+.pdfjs #sidebarContainer {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  width: 200px;
+  visibility: hidden;
+  -webkit-transition-duration: 200ms;
+  -webkit-transition-timing-function: ease;
+  transition-duration: 200ms;
+  transition-timing-function: ease;
+}
+html[dir='ltr'] .pdfjs #sidebarContainer {
+  -webkit-transition-property: left;
+  transition-property: left;
+  left: -200px;
+}
+html[dir='rtl'] .pdfjs #sidebarContainer {
+  -webkit-transition-property: right;
+  transition-property: right;
+  right: -200px;
+}
+.pdfjs #outerContainer.sidebarMoving > #sidebarContainer,
+.pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
+  visibility: visible;
+}
+html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
+  left: 0;
+}
+html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
+  right: 0;
+}
+.pdfjs #mainContainer {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  min-width: 320px;
+  -webkit-transition-duration: 200ms;
+  -webkit-transition-timing-function: ease;
+  transition-duration: 200ms;
+  transition-timing-function: ease;
+}
+html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+  -webkit-transition-property: left;
+  transition-property: left;
+  left: 200px;
+}
+html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+  -webkit-transition-property: right;
+  transition-property: right;
+  right: 200px;
+}
+.pdfjs #sidebarContent {
+  top: 32px;
+  bottom: 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  position: absolute;
+  width: 200px;
+  background-color: rgba(0, 0, 0, 0.1);
+}
+html[dir='ltr'] .pdfjs #sidebarContent {
+  left: 0;
+  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25);
+}
+html[dir='rtl'] .pdfjs #sidebarContent {
+  right: 0;
+  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25);
+}
+.pdfjs #viewerContainer {
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  position: absolute;
+  top: 32px;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  outline: none;
+}
+html[dir='ltr'] .pdfjs #viewerContainer {
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05);
+}
+html[dir='rtl'] .pdfjs #viewerContainer {
+  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05);
+}
+.pdfjs .toolbar {
+  position: relative;
+  left: 0;
+  right: 0;
+  // z-index: 9999;
+  cursor: default;
+}
+.pdfjs #toolbarContainer {
+  width: 100%;
+}
+.pdfjs #toolbarSidebar {
+  width: 200px;
+  height: 32px;
+  background-color: #424242;
+  background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95));
+}
+html[dir='ltr'] .pdfjs #toolbarSidebar {
+  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1);
+}
+html[dir='rtl'] .pdfjs #toolbarSidebar {
+  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1);
+}
+.pdfjs #toolbarContainer,
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  position: relative;
+  height: 32px;
+  background-color: #474747;
+  background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95));
+}
+html[dir='ltr'] .pdfjs #toolbarContainer,
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
+}
+html[dir='rtl'] .pdfjs #toolbarContainer,
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
+}
+.pdfjs #toolbarViewer {
+  height: 32px;
+}
+.pdfjs #loadingBar {
+  position: relative;
+  width: 100%;
+  height: 4px;
+  background-color: #333;
+  border-bottom: 1px solid #333;
+}
+.pdfjs #loadingBar .progress {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 0;
+  height: 100%;
+  background-color: #ddd;
+  overflow: hidden;
+  -webkit-transition: width 200ms;
+  transition: width 200ms;
+}
+@-webkit-keyframes progressIndeterminate {
+  0% {
+    left: 0;
+  }
+  50% {
+    left: 100%;
+  }
+  100% {
+    left: 100%;
+  }
+}
+@keyframes progressIndeterminate {
+  0% {
+    left: 0;
+  }
+  50% {
+    left: 100%;
+  }
+  100% {
+    left: 100%;
+  }
+}
+.pdfjs #loadingBar .progress.indeterminate {
+  background-color: #999;
+  -webkit-transition: none;
+  transition: none;
+}
+.pdfjs #loadingBar .indeterminate .glimmer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 50px;
+  background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  -webkit-animation: progressIndeterminate 2s linear infinite;
+  animation: progressIndeterminate 2s linear infinite;
+}
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  top: 32px;
+  position: absolute;
+  z-index: 10000;
+  height: 32px;
+  min-width: 16px;
+  padding: 0 6px;
+  margin: 4px 2px;
+  color: #d9d9d9;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: left;
+  cursor: default;
+}
+html[dir='ltr'] .pdfjs .findbar {
+  left: 68px;
+}
+html[dir='rtl'] .pdfjs .findbar {
+  right: 68px;
+}
+.pdfjs .findbar label {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+}
+.pdfjs #findInput[data-status="pending"] {
+  background-image: url('../images/pdf.js-viewer/loading-small.png');
+  background-repeat: no-repeat;
+  background-position: right;
+}
+html[dir='rtl'] .pdfjs #findInput[data-status="pending"] {
+  background-position: left;
+}
+.pdfjs .secondaryToolbar {
+  padding: 6px;
+  height: auto;
+  z-index: 30000;
+}
+html[dir='ltr'] .pdfjs .secondaryToolbar {
+  right: 4px;
+}
+html[dir='rtl'] .pdfjs .secondaryToolbar {
+  left: 4px;
+}
+.pdfjs #secondaryToolbarButtonContainer {
+  max-width: 200px;
+  max-height: 400px;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+  margin-bottom: -4px;
+}
+.pdfjs .doorHanger,
+.pdfjs .doorHangerRight {
+  border: 1px solid rgba(0, 0, 0, 0.5);
+  border-radius: 2px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+}
+.pdfjs .doorHanger:after,
+.pdfjs .doorHanger:before,
+.pdfjs .doorHangerRight:after,
+.pdfjs .doorHangerRight:before {
+  bottom: 100%;
+  border: solid transparent;
+  content: " ";
+  height: 0;
+  width: 0;
+  position: absolute;
+  pointer-events: none;
+}
+.pdfjs .doorHanger:after,
+.pdfjs .doorHangerRight:after {
+  border-bottom-color: rgba(82, 82, 82, 0.99);
+  border-width: 8px;
+}
+.pdfjs .doorHanger:before,
+.pdfjs .doorHangerRight:before {
+  border-bottom-color: rgba(0, 0, 0, 0.5);
+  border-width: 9px;
+}
+html[dir='ltr'] .pdfjs .doorHanger:after,
+html[dir='rtl'] .pdfjs .doorHangerRight:after {
+  left: 13px;
+  margin-left: -8px;
+}
+html[dir='ltr'] .pdfjs .doorHanger:before,
+html[dir='rtl'] .pdfjs .doorHangerRight:before {
+  left: 13px;
+  margin-left: -9px;
+}
+html[dir='rtl'] .pdfjs .doorHanger:after,
+html[dir='ltr'] .pdfjs .doorHangerRight:after {
+  right: 13px;
+  margin-right: -8px;
+}
+html[dir='rtl'] .pdfjs .doorHanger:before,
+html[dir='ltr'] .pdfjs .doorHangerRight:before {
+  right: 13px;
+  margin-right: -9px;
+}
+.pdfjs #findMsg {
+  font-style: italic;
+  color: #A6B7D0;
+}
+.pdfjs #findInput.notFound {
+  background-color: #f66;
+}
+html[dir='ltr'] .pdfjs #toolbarViewerLeft {
+  margin-left: -1px;
+}
+html[dir='rtl'] .pdfjs #toolbarViewerRight {
+  margin-right: -1px;
+}
+html[dir='ltr'] .pdfjs #toolbarViewerLeft,
+html[dir='rtl'] .pdfjs #toolbarViewerRight {
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+html[dir='ltr'] .pdfjs #toolbarViewerRight,
+html[dir='rtl'] .pdfjs #toolbarViewerLeft {
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+html[dir='ltr'] .pdfjs #toolbarViewerLeft > *,
+html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *,
+html[dir='ltr'] .pdfjs #toolbarViewerRight > *,
+html[dir='ltr'] .pdfjs .findbar > * {
+  position: relative;
+  float: left;
+}
+html[dir='rtl'] .pdfjs #toolbarViewerLeft > *,
+html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *,
+html[dir='rtl'] .pdfjs #toolbarViewerRight > *,
+html[dir='rtl'] .pdfjs .findbar > * {
+  position: relative;
+  float: right;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButton {
+  margin: 3px 2px 4px 0;
+  display: inline-block;
+}
+html[dir='rtl'] .pdfjs .splitToolbarButton {
+  margin: 3px 0 4px 2px;
+  display: inline-block;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton {
+  border-radius: 0;
+  float: left;
+}
+html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton {
+  border-radius: 0;
+  float: right;
+}
+.pdfjs .toolbarButton,
+.pdfjs .secondaryToolbarButton,
+.pdfjs .overlayButton {
+  border: 0 none;
+  background: none;
+  width: 32px;
+  height: 25px;
+}
+.pdfjs .toolbarButton > span {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  overflow: hidden;
+}
+.pdfjs .toolbarButton[disabled],
+.pdfjs .secondaryToolbarButton[disabled],
+.pdfjs .overlayButton[disabled] {
+  opacity: 0.5;
+}
+.pdfjs .toolbarButton.group {
+  margin-right: 0;
+}
+.pdfjs .splitToolbarButton.toggled .toolbarButton {
+  margin: 0;
+}
+.pdfjs .splitToolbarButton:hover > .toolbarButton,
+.pdfjs .splitToolbarButton:focus > .toolbarButton,
+.pdfjs .splitToolbarButton.toggled > .toolbarButton,
+.pdfjs .toolbarButton.textButton {
+  background-color: rgba(0, 0, 0, 0.12);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.35);
+  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 150ms;
+  -webkit-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
+}
+.pdfjs .splitToolbarButton > .toolbarButton:hover,
+.pdfjs .splitToolbarButton > .toolbarButton:focus,
+.pdfjs .dropdownToolbarButton:hover,
+.pdfjs .overlayButton:hover,
+.pdfjs .toolbarButton.textButton:hover,
+.pdfjs .toolbarButton.textButton:focus {
+  background-color: rgba(0, 0, 0, 0.2);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05);
+  z-index: 199;
+}
+.pdfjs .splitToolbarButton > .toolbarButton {
+  position: relative;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child {
+  position: relative;
+  margin: 0;
+  margin-right: -1px;
+  border-top-left-radius: 2px;
+  border-bottom-left-radius: 2px;
+  border-right-color: transparent;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child {
+  position: relative;
+  margin: 0;
+  margin-left: -1px;
+  border-top-right-radius: 2px;
+  border-bottom-right-radius: 2px;
+  border-left-color: transparent;
+}
+.pdfjs .splitToolbarButtonSeparator {
+  padding: 8px 0;
+  width: 1px;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 99;
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
+  display: inline-block;
+  margin: 5px 0;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator {
+  float: left;
+}
+html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator {
+  float: right;
+}
+.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator,
+.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator {
+  padding: 12px 0;
+  margin: 1px 0;
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03);
+  -webkit-transition-property: padding;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: ease;
+  transition-property: padding;
+  transition-duration: 10ms;
+  transition-timing-function: ease;
+}
+.pdfjs .toolbarButton,
+.pdfjs .dropdownToolbarButton,
+.pdfjs .secondaryToolbarButton,
+.pdfjs .overlayButton {
+  min-width: 16px;
+  padding: 2px 6px 0;
+  border: 1px solid transparent;
+  border-radius: 2px;
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 12px;
+  line-height: 14px;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  cursor: default;
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 150ms;
+  -webkit-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
+}
+html[dir='ltr'] .pdfjs .toolbarButton,
+html[dir='ltr'] .pdfjs .overlayButton,
+html[dir='ltr'] .pdfjs .dropdownToolbarButton {
+  margin: 3px 2px 4px 0;
+}
+html[dir='rtl'] .pdfjs .toolbarButton,
+html[dir='rtl'] .pdfjs .overlayButton,
+html[dir='rtl'] .pdfjs .dropdownToolbarButton {
+  margin: 3px 0 4px 2px;
+}
+.pdfjs .toolbarButton:hover,
+.pdfjs .toolbarButton:focus,
+.pdfjs .dropdownToolbarButton,
+.pdfjs .overlayButton,
+.pdfjs .secondaryToolbarButton:hover,
+.pdfjs .secondaryToolbarButton:focus {
+  background-color: rgba(0, 0, 0, 0.12);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.35);
+  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+}
+.pdfjs .toolbarButton:hover:active,
+.pdfjs .overlayButton:hover:active,
+.pdfjs .dropdownToolbarButton:hover:active,
+.pdfjs .secondaryToolbarButton:hover:active {
+  background-color: rgba(0, 0, 0, 0.2);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: linear;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 10ms;
+  transition-timing-function: linear;
+}
+.pdfjs .toolbarButton.toggled,
+.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled,
+.pdfjs .secondaryToolbarButton.toggled {
+  background-color: rgba(0, 0, 0, 0.3);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: linear;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 10ms;
+  transition-timing-function: linear;
+}
+.pdfjs .toolbarButton.toggled:hover:active,
+.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active,
+.pdfjs .secondaryToolbarButton.toggled:hover:active {
+  background-color: rgba(0, 0, 0, 0.4);
+  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+}
+.pdfjs .dropdownToolbarButton {
+  width: 120px;
+  max-width: 120px;
+  padding: 0;
+  overflow: hidden;
+  background: url('../images/pdf.js-viewer/toolbarButton-menuArrows.png') no-repeat;
+}
+html[dir='ltr'] .pdfjs .dropdownToolbarButton {
+  background-position: 95%;
+}
+html[dir='rtl'] .pdfjs .dropdownToolbarButton {
+  background-position: 5%;
+}
+.pdfjs .dropdownToolbarButton > select {
+  min-width: 140px;
+  font-size: 12px;
+  color: #f2f2f2;
+  margin: 0;
+  padding: 3px 2px 2px;
+  border: none;
+  background: rgba(0, 0, 0, 0);
+}
+.pdfjs .dropdownToolbarButton > select > option {
+  background: #3d3d3d;
+}
+.pdfjs #customScaleOption {
+  display: none;
+}
+.pdfjs #pageWidthOption {
+  border-bottom: 1px rgba(255, 255, 255, 0.5) solid;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButton:first-child,
+html[dir='ltr'] .pdfjs .toolbarButton:first-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton:last-child,
+html[dir='rtl'] .pdfjs .toolbarButton:last-child {
+  margin-left: 4px;
+}
+html[dir='ltr'] .pdfjs .splitToolbarButton:last-child,
+html[dir='ltr'] .pdfjs .toolbarButton:last-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton:first-child,
+html[dir='rtl'] .pdfjs .toolbarButton:first-child {
+  margin-right: 4px;
+}
+.pdfjs .toolbarButtonSpacer {
+  width: 30px;
+  display: inline-block;
+  height: 1px;
+}
+.pdfjs .toolbarButtonFlexibleSpacer {
+  -webkit-box-flex: 1;
+  -moz-box-flex: 1;
+  min-width: 30px;
+}
+html[dir='ltr'] .pdfjs #findPrevious {
+  margin-left: 3px;
+}
+html[dir='ltr'] .pdfjs #findNext {
+  margin-right: 3px;
+}
+html[dir='rtl'] .pdfjs #findPrevious {
+  margin-right: 3px;
+}
+html[dir='rtl'] .pdfjs #findNext {
+  margin-left: 3px;
+}
+.pdfjs .toolbarButton::before,
+.pdfjs .secondaryToolbarButton::before {
+  position: absolute;
+  display: inline-block;
+  top: 4px;
+  left: 7px;
+}
+html[dir="ltr"] .pdfjs .secondaryToolbarButton::before {
+  left: 4px;
+}
+html[dir="rtl"] .pdfjs .secondaryToolbarButton::before {
+  right: 4px;
+}
+html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle.png');
+}
+html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png');
+}
+html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png');
+}
+html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png');
+}
+html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
+  content: url('../images/pdf.js-viewer/findbarButton-previous.png');
+}
+html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
+  content: url('../images/pdf.js-viewer/findbarButton-previous-rtl.png');
+}
+html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
+  content: url('../images/pdf.js-viewer/findbarButton-next.png');
+}
+html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
+  content: url('../images/pdf.js-viewer/findbarButton-next-rtl.png');
+}
+html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-pageUp.png');
+}
+html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png');
+}
+html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-pageDown.png');
+}
+html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png');
+}
+.pdfjs .toolbarButton.zoomOut::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-zoomOut.png');
+}
+.pdfjs .toolbarButton.zoomIn::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-zoomIn.png');
+}
+.pdfjs .toolbarButton.presentationMode::before,
+.pdfjs .secondaryToolbarButton.presentationMode::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-presentationMode.png');
+}
+.pdfjs .toolbarButton.print::before,
+.pdfjs .secondaryToolbarButton.print::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-print.png');
+}
+.pdfjs .toolbarButton.openFile::before,
+.pdfjs .secondaryToolbarButton.openFile::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-openFile.png');
+}
+.pdfjs .toolbarButton.download::before,
+.pdfjs .secondaryToolbarButton.download::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-download.png');
+}
+.pdfjs .toolbarButton.bookmark,
+.pdfjs .secondaryToolbarButton.bookmark {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  outline: none;
+  padding-top: 4px;
+  text-decoration: none;
+}
+.pdfjs .secondaryToolbarButton.bookmark {
+  padding-top: 5px;
+}
+.pdfjs .bookmark[href='#'] {
+  opacity: .5;
+  pointer-events: none;
+}
+.pdfjs .toolbarButton.bookmark::before,
+.pdfjs .secondaryToolbarButton.bookmark::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-bookmark.png');
+}
+.pdfjs #viewThumbnail.toolbarButton::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail.png');
+}
+html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-viewOutline.png');
+}
+html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png');
+}
+.pdfjs #viewAttachments.toolbarButton::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments.png');
+}
+.pdfjs #viewFind.toolbarButton::before {
+  content: url('../images/pdf.js-viewer/toolbarButton-search.png');
+}
+.pdfjs .secondaryToolbarButton {
+  position: relative;
+  margin: 0 0 4px;
+  padding: 3px 0 1px;
+  height: auto;
+  min-height: 25px;
+  width: auto;
+  min-width: 100%;
+  white-space: normal;
+}
+html[dir="ltr"] .pdfjs .secondaryToolbarButton {
+  padding-left: 24px;
+  text-align: left;
+}
+html[dir="rtl"] .pdfjs .secondaryToolbarButton {
+  padding-right: 24px;
+  text-align: right;
+}
+html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark {
+  padding-left: 27px;
+}
+html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark {
+  padding-right: 27px;
+}
+html[dir="ltr"] .pdfjs .secondaryToolbarButton > span {
+  padding-right: 4px;
+}
+html[dir="rtl"] .pdfjs .secondaryToolbarButton > span {
+  padding-left: 4px;
+}
+.pdfjs .secondaryToolbarButton.firstPage::before {
+  content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png');
+}
+.pdfjs .secondaryToolbarButton.lastPage::before {
+  content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png');
+}
+.pdfjs .secondaryToolbarButton.rotateCcw::before {
+  content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png');
+}
+.pdfjs .secondaryToolbarButton.rotateCw::before {
+  content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png');
+}
+.pdfjs .secondaryToolbarButton.handTool::before {
+  content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool.png');
+}
+.pdfjs .secondaryToolbarButton.documentProperties::before {
+  content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png');
+}
+.pdfjs .verticalToolbarSeparator {
+  display: block;
+  padding: 8px 0;
+  margin: 8px 4px;
+  width: 1px;
+  background-color: rgba(0, 0, 0, 0.5);
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
+}
+html[dir='ltr'] .pdfjs .verticalToolbarSeparator {
+  margin-left: 2px;
+}
+html[dir='rtl'] .pdfjs .verticalToolbarSeparator {
+  margin-right: 2px;
+}
+.pdfjs .horizontalToolbarSeparator {
+  display: block;
+  margin: 0 0 4px;
+  height: 1px;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
+}
+.pdfjs .toolbarField {
+  padding: 3px 6px;
+  margin: 4px 0;
+  border: 1px solid transparent;
+  border-radius: 2px;
+  background-color: rgba(255, 255, 255, 0.09);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.35);
+  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  color: #f2f2f2;
+  font-size: 12px;
+  line-height: 14px;
+  outline-style: none;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
+}
+.pdfjs .toolbarField[type=checkbox] {
+  display: inline-block;
+  margin: 8px 0;
+}
+.pdfjs .toolbarField.pageNumber {
+  -moz-appearance: textfield;
+  min-width: 16px;
+  text-align: right;
+  width: 40px;
+}
+.pdfjs .toolbarField.pageNumber.visiblePageIsLoading {
+  background-image: url('../images/pdf.js-viewer/loading-small.png');
+  background-repeat: no-repeat;
+  background-position: 1px;
+}
+.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button,
+.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button {
+  -webkit-appearance: none;
+  margin: 0;
+}
+.pdfjs .toolbarField:hover {
+  background-color: rgba(255, 255, 255, 0.11);
+  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45);
+}
+.pdfjs .toolbarField:focus {
+  background-color: rgba(255, 255, 255, 0.15);
+  border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9);
+}
+.pdfjs .toolbarLabel {
+  min-width: 16px;
+  padding: 3px 6px 3px 2px;
+  margin: 4px 2px 4px 0;
+  border: 1px solid transparent;
+  border-radius: 2px;
+  color: #d9d9d9;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: left;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  cursor: default;
+}
+.pdfjs #thumbnailView {
+  position: absolute;
+  width: 120px;
+  top: 0;
+  bottom: 0;
+  padding: 10px 40px 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+}
+.pdfjs .thumbnail {
+  float: left;
+  margin-bottom: 5px;
+}
+.pdfjs #thumbnailView > a:last-of-type > .thumbnail {
+  margin-bottom: 10px;
+}
+.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) {
+  margin-bottom: 9px;
+}
+.pdfjs .thumbnail:not([data-loaded]) {
+  border: 1px dashed rgba(255, 255, 255, 0.5);
+  margin: -1px -1px 4px;
+}
+.pdfjs .thumbnailImage {
+  border: 1px solid transparent;
+  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
+  opacity: .8;
+  z-index: 99;
+  background-color: #fff;
+  background-clip: content-box;
+}
+.pdfjs .thumbnailSelectionRing {
+  border-radius: 2px;
+  padding: 7px;
+}
+.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
+.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
+  opacity: 0.9;
+}
+.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing,
+.pdfjs .thumbnail:hover > .thumbnailSelectionRing {
+  background-color: rgba(255, 255, 255, 0.15);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: rgba(255, 255, 255, 0.9);
+}
+.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
+  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
+  opacity: 1;
+}
+.pdfjs .thumbnail.selected > .thumbnailSelectionRing {
+  background-color: rgba(255, 255, 255, 0.3);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: #ffffff;
+}
+.pdfjs #outlineView,
+.pdfjs #attachmentsView {
+  position: absolute;
+  width: 192px;
+  top: 0;
+  bottom: 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+}
+.pdfjs #outlineView {
+  padding: 4px 4px 0;
+}
+.pdfjs #attachmentsView {
+  padding: 3px 4px 0;
+}
+html[dir='ltr'] .pdfjs .outlineItem > .outlineItems {
+  margin-left: 20px;
+}
+html[dir='rtl'] .pdfjs .outlineItem > .outlineItems {
+  margin-right: 20px;
+}
+.pdfjs .outlineItem > a,
+.pdfjs .attachmentsItem > button {
+  text-decoration: none;
+  display: inline-block;
+  min-width: 95%;
+  height: auto;
+  margin-bottom: 1px;
+  border-radius: 2px;
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 13px;
+  line-height: 15px;
+  -moz-user-select: none;
+  white-space: normal;
+}
+.pdfjs .attachmentsItem > button {
+  border: 0 none;
+  background: none;
+  cursor: pointer;
+  width: 100%;
+}
+html[dir='ltr'] .pdfjs .outlineItem > a {
+  padding: 2px 0 5px 10px;
+}
+html[dir='ltr'] .pdfjs .attachmentsItem > button {
+  padding: 2px 0 3px 7px;
+  text-align: left;
+}
+html[dir='rtl'] .pdfjs .outlineItem > a {
+  padding: 2px 10px 5px 0;
+}
+html[dir='rtl'] .pdfjs .attachmentsItem > button {
+  padding: 2px 7px 3px 0;
+  text-align: right;
+}
+.pdfjs .outlineItem > a:hover,
+.pdfjs .attachmentsItem > button:hover {
+  background-color: rgba(255, 255, 255, 0.02);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: rgba(255, 255, 255, 0.9);
+}
+.pdfjs .outlineItem.selected {
+  background-color: rgba(255, 255, 255, 0.08);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: #ffffff;
+}
+.pdfjs .noResults {
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.8);
+  font-style: italic;
+  cursor: default;
+}
+.pdfjs ::selection {
+  background: rgba(0, 0, 255, 0.3);
+}
+.pdfjs ::-moz-selection {
+  background: rgba(0, 0, 255, 0.3);
+}
+.pdfjs #errorWrapper {
+  background: none repeat scroll 0 0 #F55;
+  color: #fff;
+  left: 0;
+  position: absolute;
+  right: 0;
+  z-index: 1000;
+  padding: 3px;
+  font-size: 0.8em;
+}
+.pdfjs .loadingInProgress #errorWrapper {
+  top: 37px;
+}
+.pdfjs #errorMessageLeft {
+  float: left;
+}
+.pdfjs #errorMessageRight {
+  float: right;
+}
+.pdfjs #errorMoreInfo {
+  background-color: #FFF;
+  color: #000;
+  padding: 3px;
+  margin: 3px;
+  width: 98%;
+}
+.pdfjs .overlayButton {
+  width: auto;
+  margin: 3px 4px 2px!important;
+  padding: 2px 6px 3px;
+}
+.pdfjs #overlayContainer {
+  display: table;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.2);
+  z-index: 40000;
+}
+.pdfjs #overlayContainer > * {
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+}
+.pdfjs #overlayContainer > .container {
+  display: table-cell;
+  vertical-align: middle;
+  text-align: center;
+}
+.pdfjs #overlayContainer > .container > .dialog {
+  display: inline-block;
+  padding: 15px;
+  border-spacing: 4px;
+  color: #d9d9d9;
+  font-size: 12px;
+  line-height: 14px;
+  background-color: #474747;
+  background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95));
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
+  border: 1px solid rgba(0, 0, 0, 0.5);
+  border-radius: 4px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+}
+.pdfjs .dialog > .row {
+  display: table-row;
+}
+.pdfjs .dialog > .row > * {
+  display: table-cell;
+}
+.pdfjs .dialog .toolbarField {
+  margin: 5px 0;
+}
+.pdfjs .dialog .separator {
+  display: block;
+  margin: 4px 0;
+  height: 1px;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
+}
+.pdfjs .dialog .buttonRow {
+  text-align: center;
+  vertical-align: middle;
+}
+.pdfjs #passwordOverlay > .dialog {
+  text-align: center;
+}
+.pdfjs #passwordOverlay .toolbarField {
+  width: 200px;
+}
+.pdfjs #documentPropertiesOverlay > .dialog {
+  text-align: left;
+}
+.pdfjs #documentPropertiesOverlay .row > * {
+  min-width: 100px;
+}
+html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * {
+  text-align: left;
+}
+html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * {
+  text-align: right;
+}
+.pdfjs #documentPropertiesOverlay .row > span {
+  width: 125px;
+  word-wrap: break-word;
+}
+.pdfjs #documentPropertiesOverlay .row > p {
+  max-width: 225px;
+  word-wrap: break-word;
+}
+.pdfjs #documentPropertiesOverlay .buttonRow {
+  margin-top: 10px;
+}
+.pdfjs .clearBoth {
+  clear: both;
+}
+.pdfjs .fileInput {
+  background: #fff;
+  color: #000;
+  margin-top: 5px;
+  visibility: hidden;
+  position: fixed;
+  right: 0;
+  top: 0;
+}
+.pdfjs #PDFBug {
+  background: none repeat scroll 0 0 #fff;
+  border: 1px solid #666;
+  position: fixed;
+  top: 32px;
+  right: 0;
+  bottom: 0;
+  font-size: 10px;
+  padding: 0;
+  width: 300px;
+}
+.pdfjs #PDFBug .controls {
+  background: #EEE;
+  border-bottom: 1px solid #666;
+  padding: 3px;
+}
+.pdfjs #PDFBug .panels {
+  bottom: 0;
+  left: 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  position: absolute;
+  right: 0;
+  top: 27px;
+}
+.pdfjs #PDFBug button.active {
+  font-weight: 700;
+}
+.pdfjs .debuggerShowText {
+  background: none repeat scroll 0 0 #ff0;
+  color: blue;
+}
+.pdfjs .debuggerHideText:hover {
+  background: none repeat scroll 0 0 #ff0;
+}
+.pdfjs #PDFBug .stats {
+  font-family: courier;
+  font-size: 10px;
+  white-space: pre;
+}
+.pdfjs #PDFBug .stats .title {
+  font-weight: 700;
+}
+.pdfjs #PDFBug table {
+  font-size: 10px;
+}
+.pdfjs #viewer.textLayer-visible .textLayer > div,
+.pdfjs #viewer.textLayer-hover .textLayer > div:hover {
+  background-color: #fff;
+  color: #000;
+}
+.pdfjs #viewer.textLayer-shadow .textLayer > div {
+  background-color: rgba(255, 255, 255, 0.6);
+  color: #000;
+}
+.pdfjs .grab-to-pan-grab {
+  cursor: url('../images/pdf.js-viewer/grab.cur'), move !important;
+  cursor: -webkit-grab !important;
+  cursor: -moz-grab !important;
+  cursor: grab !important;
+}
+.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) {
+  cursor: inherit !important;
+}
+.pdfjs .grab-to-pan-grab:active,
+.pdfjs .grab-to-pan-grabbing {
+  cursor: url('../images/pdf.js-viewer/grabbing.cur'), move !important;
+  cursor: -webkit-grabbing !important;
+  cursor: -moz-grabbing !important;
+  cursor: grabbing!important;
+  position: fixed;
+  background: transparent;
+  display: block;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+  z-index: 50000;
+}
+@page {
+  margin: 0;
+}
+.pdfjs #printContainer {
+  display: none;
+}
+@media screen and (min-resolution: 2dppx) {
+  .pdfjs .toolbarButton::before {
+    -webkit-transform: scale(0.5);
+    transform: scale(0.5);
+    top: -5px;
+  }
+  .pdfjs .secondaryToolbarButton::before {
+    -webkit-transform: scale(0.5);
+    transform: scale(0.5);
+    top: -4px;
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton::before,
+  html[dir='rtl'] .pdfjs .toolbarButton::before {
+    left: -1px;
+  }
+  html[dir='ltr'] .pdfjs .secondaryToolbarButton::before {
+    left: -2px;
+  }
+  html[dir='rtl'] .pdfjs .secondaryToolbarButton::before {
+    left: 186px;
+  }
+  .pdfjs .toolbarField.pageNumber.visiblePageIsLoading,
+  .pdfjs #findInput[data-status="pending"] {
+    background-image: url('../images/pdf.js-viewer/loading-small@2x.png');
+    background-size: 16px 17px;
+  }
+  .pdfjs .dropdownToolbarButton {
+    background: url('../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png') no-repeat;
+    background-size: 7px 16px;
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png');
+  }
+  html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png');
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png');
+  }
+  html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png');
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
+    content: url('../images/pdf.js-viewer/findbarButton-previous@2x.png');
+  }
+  html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
+    content: url('../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png');
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
+    content: url('../images/pdf.js-viewer/findbarButton-next@2x.png');
+  }
+  html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
+    content: url('../images/pdf.js-viewer/findbarButton-next-rtl@2x.png');
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-pageUp@2x.png');
+  }
+  html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png');
+  }
+  html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-pageDown@2x.png');
+  }
+  html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png');
+  }
+  .pdfjs .toolbarButton.zoomIn::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png');
+  }
+  .pdfjs .toolbarButton.zoomOut::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png');
+  }
+  .pdfjs .toolbarButton.presentationMode::before,
+  .pdfjs .secondaryToolbarButton.presentationMode::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png');
+  }
+  .pdfjs .toolbarButton.print::before,
+  .pdfjs .secondaryToolbarButton.print::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-print@2x.png');
+  }
+  .pdfjs .toolbarButton.openFile::before,
+  .pdfjs .secondaryToolbarButton.openFile::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-openFile@2x.png');
+  }
+  .pdfjs .toolbarButton.download::before,
+  .pdfjs .secondaryToolbarButton.download::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-download@2x.png');
+  }
+  .pdfjs .toolbarButton.bookmark::before,
+  .pdfjs .secondaryToolbarButton.bookmark::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-bookmark@2x.png');
+  }
+  .pdfjs #viewThumbnail.toolbarButton::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png');
+  }
+  html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png');
+  }
+  html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png');
+  }
+  .pdfjs #viewAttachments.toolbarButton::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png');
+  }
+  .pdfjs #viewFind.toolbarButton::before {
+    content: url('../images/pdf.js-viewer/toolbarButton-search@2x.png');
+  }
+  .pdfjs .secondaryToolbarButton.firstPage::before {
+    content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png');
+  }
+  .pdfjs .secondaryToolbarButton.lastPage::before {
+    content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png');
+  }
+  .pdfjs .secondaryToolbarButton.rotateCcw::before {
+    content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png');
+  }
+  .pdfjs .secondaryToolbarButton.rotateCw::before {
+    content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png');
+  }
+  .pdfjs .secondaryToolbarButton.handTool::before {
+    content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png');
+  }
+  .pdfjs .secondaryToolbarButton.documentProperties::before {
+    content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png');
+  }
+}
+@media print {
+  body {
+    background: transparent none;
+  }
+  .pdfjs #sidebarContainer,
+  .pdfjs #secondaryToolbar,
+  .pdfjs .toolbar,
+  .pdfjs #loadingBox,
+  .pdfjs #errorWrapper,
+  .pdfjs .textLayer {
+    display: none;
+  }
+  .pdfjs #viewerContainer {
+    overflow: visible;
+  }
+  .pdfjs #mainContainer,
+  .pdfjs #viewerContainer,
+  .pdfjs .page,
+  .pdfjs .page canvas {
+    position: static;
+    padding: 0;
+    margin: 0;
+  }
+  .pdfjs .page {
+    float: left;
+    display: none;
+    border: none;
+    box-shadow: none;
+    background-clip: content-box;
+    background-color: #fff;
+  }
+  .pdfjs .page[data-loaded] {
+    display: block;
+  }
+  .pdfjs .fileInput {
+    display: none;
+  }
+  body[data-mozPrintCallback] .pdfjs #outerContainer {
+    display: none;
+  }
+  body[data-mozPrintCallback] .pdfjs #printContainer {
+    display: block;
+  }
+  .pdfjs #printContainer > div {
+    position: relative;
+    top: 0;
+    left: 0;
+    overflow: hidden;
+  }
+  .pdfjs #printContainer canvas {
+    display: block;
+  }
+}
+.pdfjs .visibleLargeView,
+.pdfjs .visibleMediumView,
+.pdfjs .visibleSmallView {
+  display: none;
+}
+@media all and (max-width: 960px) {
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
+    float: left;
+    left: 205px;
+  }
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
+    float: right;
+    right: 205px;
+  }
+}
+@media all and (max-width: 900px) {
+  .pdfjs .sidebarOpen .hiddenLargeView {
+    display: none;
+  }
+  .pdfjs .sidebarOpen .visibleLargeView {
+    display: inherit;
+  }
+}
+@media all and (max-width: 860px) {
+  .pdfjs .sidebarOpen .hiddenMediumView {
+    display: none;
+  }
+  .pdfjs .sidebarOpen .visibleMediumView {
+    display: inherit;
+  }
+}
+@media all and (max-width: 770px) {
+  .pdfjs #sidebarContainer {
+    top: 32px;
+    z-index: 100;
+  }
+  .pdfjs .loadingInProgress #sidebarContainer {
+    top: 37px;
+  }
+  .pdfjs #sidebarContent {
+    top: 32px;
+    background-color: rgba(0, 0, 0, 0.7);
+  }
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+    left: 0;
+  }
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+    right: 0;
+  }
+  html[dir='ltr'] .pdfjs .outerCenter {
+    float: left;
+    left: 205px;
+  }
+  html[dir='rtl'] .pdfjs .outerCenter {
+    float: right;
+    right: 205px;
+  }
+  .pdfjs #outerContainer .hiddenLargeView,
+  .pdfjs #outerContainer .hiddenMediumView {
+    display: inherit;
+  }
+  .pdfjs #outerContainer .visibleLargeView,
+  .pdfjs #outerContainer .visibleMediumView {
+    display: none;
+  }
+}
+@media all and (max-width: 700px) {
+  .pdfjs #outerContainer .hiddenLargeView {
+    display: none;
+  }
+  .pdfjs #outerContainer .visibleLargeView {
+    display: inherit;
+  }
+}
+@media all and (max-width: 660px) {
+  .pdfjs #outerContainer .hiddenMediumView {
+    display: none;
+  }
+  .pdfjs #outerContainer .visibleMediumView {
+    display: inherit;
+  }
+}
+@media all and (max-width: 600px) {
+  .pdfjs .hiddenSmallView {
+    display: none;
+  }
+  .pdfjs .visibleSmallView {
+    display: inherit;
+  }
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
+  html[dir='ltr'] .pdfjs .outerCenter {
+    left: 156px;
+  }
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
+  html[dir='rtl'] .pdfjs .outerCenter {
+    right: 156px;
+  }
+  .pdfjs .toolbarButtonSpacer {
+    width: 0;
+  }
+}
+@media all and (max-width: 510px) {
+  .pdfjs #scaleSelectContainer,
+  .pdfjs #pageNumberLabel {
+    display: none;
+  }
+}
+/* should be hidden differently */
+#fileInput.fileInput {
+  display: none;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-splitpane.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,80 @@
+$gray: gray;
+$blue: hsla(204, 70%, 53%, 0.7);
+$red: rgba(206, 0, 0, 0.7);
+$green: rgba(0, 160, 27, .7);
+$yellow: rgba(220, 200  , 0, .9);
+$violet: rgba(255, 31, 255, .7);
+
+.wvSplitpane {
+    height: 100%;
+    padding: 7px 2px 2px 2px;
+
+    // Anchor
+    position: relative;
+}
+.wvSplitpane__cell {
+    display: inline-block;
+    float: left;
+    height: 100%;
+    width: 100%;
+
+    // Anchor
+    position: relative;
+}
+.wvSplitpane__cellBorder,
+%wvSplitpane__cellBorder {
+    display: inline-block;
+    float: left;
+    height: calc(100% - 2px);
+    width: calc(100% - 2px);
+
+    border: 2px dashed transparent;
+
+    padding: 2px;
+    margin: 1px;
+}
+.wvSplitpane__cellBorder--selected {
+    @extend .wvSplitpane__cellBorder;
+
+    // Add border
+    border: 2px solid $blue;
+}
+
+// Color modifiers
+.wvSplitpane__cellBorder--blue {
+    @extend .wvSplitpane__cellBorder;
+    border-color: $blue;
+}
+
+.wvSplitpane__cellBorder--red {
+    @extend .wvSplitpane__cellBorder;
+    border-color: $red;
+}
+
+.wvSplitpane__cellBorder--green {
+    @extend .wvSplitpane__cellBorder;
+    border-color: $green;
+}
+
+.wvSplitpane__cellBorder--yellow {
+    @extend .wvSplitpane__cellBorder;
+    border-color: $yellow;
+}
+
+.wvSplitpane__cellBorder--violet {
+    @extend .wvSplitpane__cellBorder;
+    border-color: $violet;
+}
+
+// Make sure the pane keeps its size
+wv-pane-policy {
+    display: block;
+    width: 100%;
+    height: 100%;
+
+    > div[ng-transclude] {
+        display: block;
+        width: 100%;
+        height: 100%;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-timeline-controls.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,194 @@
+/* wv-timeline-controls directive */
+.wv-timeline-controls {
+    padding: 0.5em 0.5em 0.5em 0.5em;
+    line-height: 1em;
+    background-color: rgba(0, 0, 0, 0.66);
+
+    text-align: center;
+
+    transition: color 500ms, background-color 500ms;
+}
+
+.wv-timeline-controls:hover {
+    background-color: rgba(0, 0, 0, 0.9);
+}
+
+// Used to make sure buttons doesn't break the style
+.wv-timeline-controls-vertical-sizing {
+    display: inline-block; 
+    line-height: 1em;
+    font-size: 1em;
+}
+
+.wv-timeline-controls-vflip {
+    // flip only the icon
+    &:before, &:after{
+        transform: scaleX(-1);
+        display: inline-block;
+    }
+}
+
+.wv-timeline-controls-button {
+    display: inline-block;
+    height: 1em;
+    width: 1em;
+    line-height: 1em;
+    font-size: 1em;
+    margin: 0;
+
+    user-select: none;
+    cursor: pointer;
+}
+
+.wv-timeline-controls-input {
+    height: 1em;
+    width: 3em;
+    padding: 0;
+    padding-bottom: 1px;
+    box-sizing: content-box;
+
+    border: none;
+    border-bottom: 1px solid hsla(35, 100%, 75%, 0.24);
+    background-color: transparent;
+    
+    text-align: right;
+}
+
+// Display play button on the right side
+.wv-timeline-controls-play-button-wrapper {
+    float: right;
+}
+
+/* wv-play-button directive */
+.wv-play-button {
+    display: inline-block;
+    position: relative;
+    line-height: 1em;
+
+    // This is for the boxing box
+    height: 3em;
+    width: 6em;
+    padding-bottom: 1em;
+    padding-left: 0.25em;
+    padding-right: 0.25em;
+}
+
+.wv-play-button:hover .wv-play-button-config-position-handler {
+    visibility: visible;
+}
+
+// This is a 0x0 div to set the position
+.wv-play-button-config-position-handler {
+    visibility: hidden;
+    position: absolute;
+    bottom: 3em;
+    left: 1em;
+    right: 0.5em;
+    // z-index: 2;
+}
+
+// The layout of play configuration
+.wv-play-button-config {
+    position: absolute;
+    bottom: 0;
+    left: -6em;
+    width: 12em;
+    padding: 1em;
+    background-color: hsla(0,1,0, 0.5);
+}
+
+/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */
+
+.wv-play-button-config-framerate-wrapper {
+    display: inline-block;
+    margin: 0.25em 0 0.5em 0;
+}
+input[type="range"].wv-play-button-config-framerate {
+    /*removes default webkit styles*/
+    -webkit-appearance: none;
+    
+    /*fix for FF unable to apply focus style bug */
+    border: 1px solid white;
+    
+    /*required for proper track sizing in FF*/
+    width: 10em;
+}
+input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track {
+    width: 10em;
+    height: 5px;
+    background: #ddd;
+    border: none;
+    border-radius: 3px;
+}
+input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb {
+    -webkit-appearance: none;
+    border: none;
+    height: 16px;
+    width: 16px;
+    border-radius: 50%;
+    background: goldenrod;
+    margin-top: -4px;
+}
+input[type="range"].wv-play-button-config-framerate:focus {
+    outline: none;
+}
+input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track {
+    background: #ccc;
+}
+
+input[type="range"].wv-play-button-config-framerate::-moz-range-track {
+    width: 10em;
+    height: 5px;
+    background: #ddd;
+    border: none;
+    border-radius: 3px;
+}
+input[type="range"].wv-play-button-config-framerate::-moz-range-thumb {
+    border: none;
+    height: 16px;
+    width: 16px;
+    border-radius: 50%;
+    background: goldenrod;
+}
+
+/*hide the outline behind the border*/
+input[type="range"].wv-play-button-config-framerate:-moz-focusring{
+    outline: 1px solid white;
+    outline-offset: -1px;
+}
+
+input[type="range"].wv-play-button-config-framerate::-ms-track {
+    width: 10em;
+    height: 5px;
+    
+    /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
+    background: transparent;
+    
+    /*leave room for the larger thumb to overflow with a transparent border */
+    border-color: transparent;
+    border-width: 6px 0;
+
+    /*remove default tick marks*/
+    color: transparent;
+}
+input[type="range"].wv-play-button-config-framerate::-ms-fill-lower {
+    background: #777;
+    border-radius: 10px;
+}
+input[type="range"].wv-play-button-config-framerate::-ms-fill-upper {
+    background: #ddd;
+    border-radius: 10px;
+}
+input[type="range"].wv-play-button-config-framerate::-ms-thumb {
+    border: none;
+    height: 16px;
+    width: 16px;
+    border-radius: 50%;
+    background: goldenrod;
+}
+input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower {
+    background: #888;
+}
+input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper {
+    background: #ccc;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/Resources/Styles/wv-timeline.scss	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,34 @@
+.wv-timeline {
+    position: relative;
+    height: 2em; // save 2px to display the "upper part of the currently selected image on the timeline"
+    &.reduced{
+        height: 5px;
+        .wv-timeline-loading-bar-wrapper{
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+
+.wv-timeline-controls-wrapper {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 16em;
+    height: 100%; 
+    color: white;
+}
+
+.wv-timeline-loading-bar-wrapper {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: calc(100% - 16em);
+    height: calc(100% + 2px); 
+    
+    svg{
+        position:absolute;
+        left:0;
+        top:0;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebApplication/app.css	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,4752 @@
+.browsehappy {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0; }
+
+.wv-html, .wv-body {
+  height: 100%;
+  width: 100%;
+  margin: 0;
+  padding: 0;
+  overflow: hidden; }
+
+.wv-body {
+  background-color: black;
+  color: white;
+  position: relative;
+  overflow: hidden;
+  font-family: "Open Sans", Helvetica, Arial, sans-serif;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  font-size: 13px;
+  font-weight: 400;
+  line-height: 1.49;
+  font-size-adjust: 100%;
+  -moz-osx-font-smoothing: grayscale !important;
+  font-smoothing: antialiased !important;
+  -webkit-font-smoothing: antialiased !important; }
+
+.wvLoadingScreen {
+  width: 100%;
+  height: 100%;
+  background-color: black;
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center; }
+
+.wvLoadingSpinner {
+  margin: 100px auto 0;
+  width: 70px;
+  text-align: center; }
+
+.wvLoadingSpinner > div {
+  width: 18px;
+  height: 18px;
+  background-color: #FFF;
+  border-radius: 100%;
+  display: inline-block;
+  -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+  animation: sk-bouncedelay 1.4s infinite ease-in-out both; }
+
+.wvLoadingSpinner .bounce1 {
+  -webkit-animation-delay: -0.32s;
+  animation-delay: -0.32s; }
+
+.wvLoadingSpinner .bounce2 {
+  -webkit-animation-delay: -0.16s;
+  animation-delay: -0.16s; }
+
+@-webkit-keyframes sk-bouncedelay {
+  0%, 80%, 100% {
+    -webkit-transform: scale(0); }
+  40% {
+    -webkit-transform: scale(1); } }
+
+@keyframes sk-bouncedelay {
+  0%, 80%, 100% {
+    -webkit-transform: scale(0);
+    transform: scale(0); }
+  40% {
+    -webkit-transform: scale(1);
+    transform: scale(1); } }
+
+/* wvp-ui stuffs */
+wv-webviewer {
+  display: block;
+  height: 100%;
+  overflow: hidden; }
+
+.wvButton, .wvButton--rotate, .wvButton--vflip, .wvButton--underline, .fa.wvButton--underline, .wvButton--border, .wvButton--borderAndWhite {
+  outline: none;
+  background-color: transparent;
+  border: none;
+  border-radius: 0;
+  position: relative;
+  display: inline-block;
+  cursor: pointer;
+  font-variant: small-caps;
+  text-transform: lowercase;
+  text-align: center;
+  font-size: 1.3rem;
+  font-weight: 400;
+  color: #d9d9d9;
+  transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
+  margin: 0;
+  min-width: 3rem;
+  padding: 0 10px;
+  line-height: 3.6rem; }
+  .wvButton:hover, .wvButton--rotate:hover, .wvButton--vflip:hover, .wvButton--underline:hover, .wvButton--border:hover, .wvButton--borderAndWhite:hover {
+    text-decoration: none;
+    color: white; }
+  .wvButton.wvLargeButton, .wvLargeButton.wvButton--rotate, .wvLargeButton.wvButton--vflip, .wvLargeButton.wvButton--underline, .wvLargeButton.wvButton--border, .wvLargeButton.wvButton--borderAndWhite {
+    font-size: 2rem;
+    line-height: 6.2rem;
+    padding: 0 20px; }
+
+.wvButton--rotate:before, .wvButton--rotate:after {
+  transform: rotate(90deg);
+  display: inline-block; }
+
+.wvButton--vflip:before, .wvButton--vflip:after {
+  transform: scaleX(-1);
+  display: inline-block; }
+
+.wvButton--underline, .fa.wvButton--underline {
+  position: relative;
+  background-color: inherit;
+  text-decoration: none;
+  text-align: left;
+  font-size: 1.2rem;
+  width: 3.2rem;
+  vertical-align: middle;
+  color: white;
+  opacity: 0.75;
+  border: none;
+  border-bottom: 2px solid rgba(255, 255, 255, 0.1);
+  top: 0px; }
+  .wvButton--underline.wvLargeButton, .fa.wvButton--underline.wvLargeButton {
+    font-size: 2rem;
+    width: 6.4rem; }
+  .wvButton--underline *, .fa.wvButton--underline * {
+    pointer-events: none; }
+  .wvButton--underline:hover, .wvButton--underline:active, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:active, .fa.wvButton--underline:focus {
+    outline: 0; }
+  .wvButton--underline:hover, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:focus {
+    border-color: white;
+    opacity: 1; }
+    .wvButton--underline:hover .wvButton__bottomTriangle, .wvButton--underline:focus .wvButton__bottomTriangle, .fa.wvButton--underline:hover .wvButton__bottomTriangle, .fa.wvButton--underline:focus .wvButton__bottomTriangle {
+      border-left-color: white; }
+  .wvButton--underline.active, .fa.wvButton--underline.active {
+    opacity: 1;
+    border-color: #3498db; }
+  .wvButton--underline::before, .fa.wvButton--underline::before {
+    position: relative;
+    top: -1px; }
+  .wvButton--underline.fa, .fa.wvButton--underline.fa {
+    top: 0px;
+    font-weight: 800; }
+
+.wvButton__bottomTriangle {
+  transition: 0.3s border ease, 0.3s opacity ease;
+  display: block;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 0;
+  height: 0;
+  border-style: solid;
+  border-width: 10px 0 0 10px;
+  border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); }
+  .wvButton__bottomTriangle.active {
+    border-color: transparent transparent transparent #3498db !important; }
+    .wvButton__bottomTriangle.active.toggled {
+      border-left-color: #3498db !important; }
+
+.wvButton--border, .wvButton--borderAndWhite {
+  max-height: calc(2.8rem - 3px);
+  max-width: 100%;
+  overflow: hidden;
+  margin: 0.6rem;
+  margin-left: 0rem;
+  margin-right: 0rem;
+  line-height: 2rem;
+  padding-top: 0.1rem;
+  padding-bottom: 0.5rem;
+  font-size: 1.4rem;
+  border: 1px solid #454545;
+  font-family: Arial;
+  background-color: black; }
+  .wvButton--border + .wvButton--border, .wvButton--borderAndWhite + .wvButton--border, .wvButton--border + .wvButton--borderAndWhite, .wvButton--borderAndWhite + .wvButton--borderAndWhite {
+    margin-left: 0.7rem; }
+  .wvButton--border:hover, .wvButton--borderAndWhite:hover {
+    background-color: #1a1a1a; }
+  .wvButton--border > .glyphicon, .wvButton--borderAndWhite > .glyphicon {
+    position: relative;
+    display: inline-block;
+    top: 3px;
+    margin-right: 4px; }
+
+.wvButton--borderAndWhite {
+  color: #1a1a1a;
+  border: 1px solid #bababa;
+  background-color: white; }
+  .wvButton--borderAndWhite:hover {
+    color: #1a1a1a;
+    background-color: #e6e6e6; }
+
+.wvExitButton {
+  margin-left: 1rem;
+  margin-top: .25rem;
+  font-size: 1.25em;
+  color: white;
+  opacity: .66;
+  transition: .3s opacity ease;
+  background-color: inherit;
+  border: none;
+  text-decoration: none;
+  text-align: left;
+  padding: 0;
+  cursor: pointer;
+  font-family: inherit;
+  line-height: inherit; }
+  .wvExitButton:hover, .wvExitButton:focus {
+    opacity: 1;
+    outline: 0; }
+
+.wvExitButton__text {
+  position: relative;
+  top: -1px; }
+
+.wvStudyIsland--blue, .wvStudyIsland--red, .wvStudyIsland--green, .wvStudyIsland--yellow, .wvStudyIsland--violet {
+  margin: 1rem 1rem 1rem 1rem;
+  border: 0.3rem solid gray; }
+
+.wvStudyIsland__header--blue, .wvStudyIsland__header--red, .wvStudyIsland__header--green, .wvStudyIsland__header--yellow, .wvStudyIsland__header--violet {
+  background-color: gray;
+  padding: 0.5rem 0.5rem 0.8rem 0.5rem;
+  line-height: 1.35rem;
+  width: 100%; }
+
+.wvStudyIsland__actions {
+  float: right;
+  margin-top: -0.8rem;
+  margin-right: -0.8rem; }
+
+.wvStudyIsland__actions--oneCol {
+  float: none;
+  text-align: center; }
+
+.wvStudyIsland__main {
+  padding: 0.4rem;
+  color: white;
+  width: 100%; }
+
+.wvStudyIsland--blue {
+  border-color: rgba(51, 152, 219, 0.7); }
+
+.wvStudyIsland__header--blue {
+  background-color: rgba(51, 152, 219, 0.7); }
+
+.wvStudyIsland--red {
+  border-color: rgba(206, 0, 0, 0.7); }
+
+.wvStudyIsland__header--red {
+  background-color: rgba(206, 0, 0, 0.7); }
+
+.wvStudyIsland--green {
+  border-color: rgba(0, 160, 27, 0.7); }
+
+.wvStudyIsland__header--green {
+  background-color: rgba(0, 160, 27, 0.7); }
+
+.wvStudyIsland--yellow {
+  border-color: rgba(220, 200, 0, 0.9); }
+
+.wvStudyIsland__header--yellow {
+  background-color: rgba(220, 200, 0, 0.9); }
+
+.wvStudyIsland--violet {
+  border-color: rgba(255, 31, 255, 0.7); }
+
+.wvStudyIsland__header--violet {
+  background-color: rgba(255, 31, 255, 0.7); }
+
+/*
+ *  Source code taken from private Osimis' frontend toolbox 3.2.1.
+ */
+/**
+    _overlay.scss
+ */
+.overlay__transparent {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1; }
+
+/** _transition.scss **/
+.transition {
+  transition: 0.3s all ease; }
+
+.transition--long {
+  transition: 0.6s all ease; }
+
+/** _list.scss **/
+dd + dt {
+  clear: both; }
+
+.listDefinition {
+  width: 100%;
+  line-height: 1.3; }
+
+.listDefinition__term {
+  clear: both;
+  float: left;
+  text-align: right;
+  padding-right: 10px;
+  width: 50%; }
+
+.listDefinition__data {
+  text-align: left;
+  padding-left: 10px;
+  float: right;
+  width: 50%; }
+
+/** _animation.scss **/
+@keyframes blink__primary {
+  0% {
+    color: #3498db; }
+  100% {
+    color: #666666; } }
+
+.blink__primary {
+  animation: blink__primary 0.8s linear infinite; }
+
+[translate-cloak] {
+  transition: 0.3s all ease;
+  opacity: 1; }
+  [translate-cloak].translate-cloak {
+    opacity: 0; }
+
+/** _button.scss **/
+.button__unstyled, .button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close {
+  background-color: inherit;
+  border: none;
+  text-decoration: none;
+  text-align: left;
+  padding: 0;
+  cursor: pointer; }
+  .button__unstyled *, .button__base *, .button__state--active *, .button__state--inactive *, .button__iconed *, .button__switch--base *, .button__switch *, .button__switch--first *, .button__switch--last *, .button__lightgrey--hover *, .button__text-danger--hover *, .button__text-primary--hover *, .button__danger--hover *, .button__text *, .button__text--underlined *, .button__bordered *, .button__bordered--inverted *, .button__close * {
+    pointer-events: none; }
+  .button__unstyled:hover, .button__base:hover, .button__state--active:hover, .button__state--inactive:hover, .button__iconed:hover, .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__lightgrey--hover:hover, .button__text-danger--hover:hover, .button__text-primary--hover:hover, .button__danger--hover:hover, .button__text:hover, .button__text--underlined:hover, .button__bordered:hover, .button__bordered--inverted:hover, .button__close:hover, .button__unstyled:active, .button__base:active, .button__state--active:active, .button__state--inactive:active, .button__iconed:active, .button__switch--base:active, .button__switch:active, .button__switch--first:active, .button__switch--last:active, .button__lightgrey--hover:active, .button__text-danger--hover:active, .button__text-primary--hover:active, .button__danger--hover:active, .button__text:active, .button__text--underlined:active, .button__bordered:active, .button__bordered--inverted:active, .button__close:active, .button__unstyled:focus, .button__base:focus, .button__state--active:focus, .button__state--inactive:focus, .button__iconed:focus, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus, .button__lightgrey--hover:focus, .button__text-danger--hover:focus, .button__text-primary--hover:focus, .button__danger--hover:focus, .button__text:focus, .button__text--underlined:focus, .button__bordered:focus, .button__bordered--inverted:focus, .button__close:focus {
+    outline: 0; }
+
+.button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close {
+  transition: 0.3s all ease; }
+
+.button__state--active {
+  opacity: 1; }
+  .button__state--active:hover {
+    opacity: 0.9;
+    color: #3498db; }
+
+.button__state--inactive {
+  opacity: 0.333; }
+  .button__state--inactive:hover {
+    opacity: 0.4333;
+    color: #3498db; }
+
+.button__iconed {
+  opacity: 1; }
+  .button__iconed:hover, .button__iconed:focus, .button__iconed.active {
+    opacity: 0.75;
+    color: #3498db; }
+
+.button__switch--base, .button__switch, .button__switch--first, .button__switch--last {
+  padding: 5px 0px;
+  display: inline-block;
+  text-align: center;
+  border-top: 1px solid;
+  border-bottom: 1px solid;
+  border-color: #3498db;
+  background-color: white;
+  overflow: hidden; }
+  .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus {
+    background-color: #5faee3;
+    color: white; }
+  .button__switch--base.active, .active.button__switch, .active.button__switch--first, .active.button__switch--last {
+    background-color: #3498db;
+    color: white; }
+
+.button__switch--first {
+  border-left: 1px solid #3498db;
+  border-radius: 5px 0 0 5px; }
+
+.button__switch--last {
+  border-right: 1px solid #3498db;
+  border-radius: 0 5px 5px 0; }
+
+.button__lightgrey--hover:hover, .button__lightgrey--hover:focus, .button__lightgrey--hover.active {
+  background-color: #cccccc; }
+
+.button__text-danger--hover:hover, .button__text-danger--hover:focus, .button__text-danger--hover.active {
+  color: #E63F24; }
+
+.button__text-primary--hover:hover, .button__text-primary--hover:focus, .button__text-primary--hover.active {
+  color: #3498db; }
+
+.button__danger--hover:hover, .button__danger--hover:focus, .button__danger--hover.active {
+  background-color: #E63F24;
+  color: white; }
+
+.button__text {
+  opacity: 0.66; }
+  .button__text:hover, .button__text.active, .button__text:focus {
+    opacity: 1; }
+
+.button__text--underlined {
+  text-decoration: underline; }
+  .button__text--underlined:hover, .button__text--underlined.active, .button__text--underlined:focus {
+    text-decoration: none; }
+
+.button__bordered {
+  border-bottom: 2px solid #666666; }
+  .button__bordered:hover, .button__bordered:focus, .button__bordered.active {
+    border-color: #3498db; }
+
+.button__bordered--inverted {
+  border-bottom: 2px solid white; }
+
+.button__close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  opacity: 0.6;
+  z-index: 10; }
+  .button__close:hover, .button__close:focus {
+    opacity: 1; }
+
+/** _block.scss **/
+.block {
+  display: block !important; }
+
+/** _boxsizing.scss **/
+.boxsizing__borderbox {
+  box-sizing: border-box; }
+
+.boxsizing__contentbox {
+  box-sizing: content-box; }
+
+/** _scrollable.scss **/
+.scrollable {
+  overflow-y: auto; }
+
+.scrollable--x {
+  overflow-x: auto; }
+
+.no-scroll {
+  overflow: hidden; }
+
+/** _float.scss **/
+.float__right {
+  float: right; }
+
+.float__left {
+  float: left; }
+
+/** _fonts.scss **/
+.font__bold {
+  font-weight: 600; }
+
+.font__normal, .listDefinition__data {
+  font-weight: 400; }
+
+.font__light, .listDefinition__term {
+  font-weight: 200; }
+
+.fontColor__primary {
+  color: #3498db; }
+
+.fontColor__lightGrey {
+  color: #cccccc; }
+
+.fontColor__normal {
+  color: #666666; }
+
+.fontColor__darker {
+  color: #333333; }
+
+.fontColor__white {
+  color: white; }
+
+/** _forms.scss **/
+.textarea__unstyled {
+  border: none;
+  outline: none;
+  background-color: transparent;
+  color: inherit;
+  resize: none;
+  padding: 0;
+  margin: 0;
+  width: 100%; }
+
+/** _position.scss **/
+.position__relative {
+  position: relative; }
+
+/** _margin.scss **/
+.margin__auto {
+  margin: auto; }
+
+/** _helpers.scss **/
+/*************HELPERS**************/
+/**********************************/
+/*** identical width and height ***/
+.wh__0 {
+  width: 0px !important;
+  height: 0px !important; }
+
+.lh__0 {
+  line-height: 0px !important; }
+
+.wh__5 {
+  width: 5px !important;
+  height: 5px !important; }
+
+.lh__5 {
+  line-height: 5px !important; }
+
+.wh__8 {
+  width: 8px !important;
+  height: 8px !important; }
+
+.lh__8 {
+  line-height: 8px !important; }
+
+.wh__10 {
+  width: 10px !important;
+  height: 10px !important; }
+
+.lh__10 {
+  line-height: 10px !important; }
+
+.wh__11 {
+  width: 11px !important;
+  height: 11px !important; }
+
+.lh__11 {
+  line-height: 11px !important; }
+
+.wh__12 {
+  width: 12px !important;
+  height: 12px !important; }
+
+.lh__12 {
+  line-height: 12px !important; }
+
+.wh__13 {
+  width: 13px !important;
+  height: 13px !important; }
+
+.lh__13 {
+  line-height: 13px !important; }
+
+.wh__14 {
+  width: 14px !important;
+  height: 14px !important; }
+
+.lh__14 {
+  line-height: 14px !important; }
+
+.wh__15 {
+  width: 15px !important;
+  height: 15px !important; }
+
+.lh__15 {
+  line-height: 15px !important; }
+
+.wh__16 {
+  width: 16px !important;
+  height: 16px !important; }
+
+.lh__16 {
+  line-height: 16px !important; }
+
+.wh__17 {
+  width: 17px !important;
+  height: 17px !important; }
+
+.lh__17 {
+  line-height: 17px !important; }
+
+.wh__18 {
+  width: 18px !important;
+  height: 18px !important; }
+
+.lh__18 {
+  line-height: 18px !important; }
+
+.wh__19 {
+  width: 19px !important;
+  height: 19px !important; }
+
+.lh__19 {
+  line-height: 19px !important; }
+
+.wh__20 {
+  width: 20px !important;
+  height: 20px !important; }
+
+.lh__20 {
+  line-height: 20px !important; }
+
+.wh__21 {
+  width: 21px !important;
+  height: 21px !important; }
+
+.lh__21 {
+  line-height: 21px !important; }
+
+.wh__22 {
+  width: 22px !important;
+  height: 22px !important; }
+
+.lh__22 {
+  line-height: 22px !important; }
+
+.wh__23 {
+  width: 23px !important;
+  height: 23px !important; }
+
+.lh__23 {
+  line-height: 23px !important; }
+
+.wh__24 {
+  width: 24px !important;
+  height: 24px !important; }
+
+.lh__24 {
+  line-height: 24px !important; }
+
+.wh__25 {
+  width: 25px !important;
+  height: 25px !important; }
+
+.lh__25 {
+  line-height: 25px !important; }
+
+.wh__26 {
+  width: 26px !important;
+  height: 26px !important; }
+
+.lh__26 {
+  line-height: 26px !important; }
+
+.wh__27 {
+  width: 27px !important;
+  height: 27px !important; }
+
+.lh__27 {
+  line-height: 27px !important; }
+
+.wh__28 {
+  width: 28px !important;
+  height: 28px !important; }
+
+.lh__28 {
+  line-height: 28px !important; }
+
+.wh__29 {
+  width: 29px !important;
+  height: 29px !important; }
+
+.lh__29 {
+  line-height: 29px !important; }
+
+.wh__30 {
+  width: 30px !important;
+  height: 30px !important; }
+
+.lh__30 {
+  line-height: 30px !important; }
+
+.wh__31 {
+  width: 31px !important;
+  height: 31px !important; }
+
+.lh__31 {
+  line-height: 31px !important; }
+
+.wh__32 {
+  width: 32px !important;
+  height: 32px !important; }
+
+.lh__32 {
+  line-height: 32px !important; }
+
+.wh__33 {
+  width: 33px !important;
+  height: 33px !important; }
+
+.lh__33 {
+  line-height: 33px !important; }
+
+.wh__34 {
+  width: 34px !important;
+  height: 34px !important; }
+
+.lh__34 {
+  line-height: 34px !important; }
+
+.wh__35 {
+  width: 35px !important;
+  height: 35px !important; }
+
+.lh__35 {
+  line-height: 35px !important; }
+
+.wh__36 {
+  width: 36px !important;
+  height: 36px !important; }
+
+.lh__36 {
+  line-height: 36px !important; }
+
+.wh__37 {
+  width: 37px !important;
+  height: 37px !important; }
+
+.lh__37 {
+  line-height: 37px !important; }
+
+.wh__38 {
+  width: 38px !important;
+  height: 38px !important; }
+
+.lh__38 {
+  line-height: 38px !important; }
+
+.wh__39 {
+  width: 39px !important;
+  height: 39px !important; }
+
+.lh__39 {
+  line-height: 39px !important; }
+
+.wh__40 {
+  width: 40px !important;
+  height: 40px !important; }
+
+.lh__40 {
+  line-height: 40px !important; }
+
+.wh__41 {
+  width: 41px !important;
+  height: 41px !important; }
+
+.lh__41 {
+  line-height: 41px !important; }
+
+.wh__42 {
+  width: 42px !important;
+  height: 42px !important; }
+
+.lh__42 {
+  line-height: 42px !important; }
+
+.wh__43 {
+  width: 43px !important;
+  height: 43px !important; }
+
+.lh__43 {
+  line-height: 43px !important; }
+
+.wh__44 {
+  width: 44px !important;
+  height: 44px !important; }
+
+.lh__44 {
+  line-height: 44px !important; }
+
+.wh__45 {
+  width: 45px !important;
+  height: 45px !important; }
+
+.lh__45 {
+  line-height: 45px !important; }
+
+.wh__46 {
+  width: 46px !important;
+  height: 46px !important; }
+
+.lh__46 {
+  line-height: 46px !important; }
+
+.wh__47 {
+  width: 47px !important;
+  height: 47px !important; }
+
+.lh__47 {
+  line-height: 47px !important; }
+
+.wh__48 {
+  width: 48px !important;
+  height: 48px !important; }
+
+.lh__48 {
+  line-height: 48px !important; }
+
+.wh__49 {
+  width: 49px !important;
+  height: 49px !important; }
+
+.lh__49 {
+  line-height: 49px !important; }
+
+.wh__50 {
+  width: 50px !important;
+  height: 50px !important; }
+
+.lh__50 {
+  line-height: 50px !important; }
+
+.wh__55 {
+  width: 55px !important;
+  height: 55px !important; }
+
+.lh__55 {
+  line-height: 55px !important; }
+
+.wh__60 {
+  width: 60px !important;
+  height: 60px !important; }
+
+.lh__60 {
+  line-height: 60px !important; }
+
+.wh__64 {
+  width: 64px !important;
+  height: 64px !important; }
+
+.lh__64 {
+  line-height: 64px !important; }
+
+.wh__65 {
+  width: 65px !important;
+  height: 65px !important; }
+
+.lh__65 {
+  line-height: 65px !important; }
+
+.wh__70 {
+  width: 70px !important;
+  height: 70px !important; }
+
+.lh__70 {
+  line-height: 70px !important; }
+
+.wh__72 {
+  width: 72px !important;
+  height: 72px !important; }
+
+.lh__72 {
+  line-height: 72px !important; }
+
+.wh__75 {
+  width: 75px !important;
+  height: 75px !important; }
+
+.lh__75 {
+  line-height: 75px !important; }
+
+.wh__80 {
+  width: 80px !important;
+  height: 80px !important; }
+
+.lh__80 {
+  line-height: 80px !important; }
+
+.wh__85 {
+  width: 85px !important;
+  height: 85px !important; }
+
+.lh__85 {
+  line-height: 85px !important; }
+
+.wh__90 {
+  width: 90px !important;
+  height: 90px !important; }
+
+.lh__90 {
+  line-height: 90px !important; }
+
+.wh__95 {
+  width: 95px !important;
+  height: 95px !important; }
+
+.lh__95 {
+  line-height: 95px !important; }
+
+.wh__96 {
+  width: 96px !important;
+  height: 96px !important; }
+
+.lh__96 {
+  line-height: 96px !important; }
+
+.wh__100 {
+  width: 100px !important;
+  height: 100px !important; }
+
+.lh__100 {
+  line-height: 100px !important; }
+
+.wh__110 {
+  width: 110px !important;
+  height: 110px !important; }
+
+.lh__110 {
+  line-height: 110px !important; }
+
+.wh__120 {
+  width: 120px !important;
+  height: 120px !important; }
+
+.lh__120 {
+  line-height: 120px !important; }
+
+.wh__130 {
+  width: 130px !important;
+  height: 130px !important; }
+
+.lh__130 {
+  line-height: 130px !important; }
+
+.wh__140 {
+  width: 140px !important;
+  height: 140px !important; }
+
+.lh__140 {
+  line-height: 140px !important; }
+
+.wh__150 {
+  width: 150px !important;
+  height: 150px !important; }
+
+.lh__150 {
+  line-height: 150px !important; }
+
+.wh__160 {
+  width: 160px !important;
+  height: 160px !important; }
+
+.lh__160 {
+  line-height: 160px !important; }
+
+.wh__170 {
+  width: 170px !important;
+  height: 170px !important; }
+
+.lh__170 {
+  line-height: 170px !important; }
+
+.wh__180 {
+  width: 180px !important;
+  height: 180px !important; }
+
+.lh__180 {
+  line-height: 180px !important; }
+
+.wh__190 {
+  width: 190px !important;
+  height: 190px !important; }
+
+.lh__190 {
+  line-height: 190px !important; }
+
+.wh__200 {
+  width: 200px !important;
+  height: 200px !important; }
+
+.lh__200 {
+  line-height: 200px !important; }
+
+.wh__210 {
+  width: 210px !important;
+  height: 210px !important; }
+
+.lh__210 {
+  line-height: 210px !important; }
+
+.wh__220 {
+  width: 220px !important;
+  height: 220px !important; }
+
+.lh__220 {
+  line-height: 220px !important; }
+
+.wh__230 {
+  width: 230px !important;
+  height: 230px !important; }
+
+.lh__230 {
+  line-height: 230px !important; }
+
+.wh__240 {
+  width: 240px !important;
+  height: 240px !important; }
+
+.lh__240 {
+  line-height: 240px !important; }
+
+.wh__250 {
+  width: 250px !important;
+  height: 250px !important; }
+
+.lh__250 {
+  line-height: 250px !important; }
+
+.wh__260 {
+  width: 260px !important;
+  height: 260px !important; }
+
+.lh__260 {
+  line-height: 260px !important; }
+
+.wh__270 {
+  width: 270px !important;
+  height: 270px !important; }
+
+.lh__270 {
+  line-height: 270px !important; }
+
+.wh__280 {
+  width: 280px !important;
+  height: 280px !important; }
+
+.lh__280 {
+  line-height: 280px !important; }
+
+.wh__290 {
+  width: 290px !important;
+  height: 290px !important; }
+
+.lh__290 {
+  line-height: 290px !important; }
+
+.wh__300 {
+  width: 300px !important;
+  height: 300px !important; }
+
+.lh__300 {
+  line-height: 300px !important; }
+
+.wh__310 {
+  width: 310px !important;
+  height: 310px !important; }
+
+.lh__310 {
+  line-height: 310px !important; }
+
+.wh__320 {
+  width: 320px !important;
+  height: 320px !important; }
+
+.lh__320 {
+  line-height: 320px !important; }
+
+.wh__330 {
+  width: 330px !important;
+  height: 330px !important; }
+
+.lh__330 {
+  line-height: 330px !important; }
+
+.wh__340 {
+  width: 340px !important;
+  height: 340px !important; }
+
+.lh__340 {
+  line-height: 340px !important; }
+
+.wh__350 {
+  width: 350px !important;
+  height: 350px !important; }
+
+.lh__350 {
+  line-height: 350px !important; }
+
+.wh__360 {
+  width: 360px !important;
+  height: 360px !important; }
+
+.lh__360 {
+  line-height: 360px !important; }
+
+.wh__370 {
+  width: 370px !important;
+  height: 370px !important; }
+
+.lh__370 {
+  line-height: 370px !important; }
+
+.wh__380 {
+  width: 380px !important;
+  height: 380px !important; }
+
+.lh__380 {
+  line-height: 380px !important; }
+
+.wh__390 {
+  width: 390px !important;
+  height: 390px !important; }
+
+.lh__390 {
+  line-height: 390px !important; }
+
+.wh__400 {
+  width: 400px !important;
+  height: 400px !important; }
+
+.lh__400 {
+  line-height: 400px !important; }
+
+.wh__410 {
+  width: 410px !important;
+  height: 410px !important; }
+
+.lh__410 {
+  line-height: 410px !important; }
+
+.wh__420 {
+  width: 420px !important;
+  height: 420px !important; }
+
+.lh__420 {
+  line-height: 420px !important; }
+
+.wh__430 {
+  width: 430px !important;
+  height: 430px !important; }
+
+.lh__430 {
+  line-height: 430px !important; }
+
+.wh__440 {
+  width: 440px !important;
+  height: 440px !important; }
+
+.lh__440 {
+  line-height: 440px !important; }
+
+.wh__450 {
+  width: 450px !important;
+  height: 450px !important; }
+
+.lh__450 {
+  line-height: 450px !important; }
+
+.wh__460 {
+  width: 460px !important;
+  height: 460px !important; }
+
+.lh__460 {
+  line-height: 460px !important; }
+
+.wh__470 {
+  width: 470px !important;
+  height: 470px !important; }
+
+.lh__470 {
+  line-height: 470px !important; }
+
+.wh__480 {
+  width: 480px !important;
+  height: 480px !important; }
+
+.lh__480 {
+  line-height: 480px !important; }
+
+.wh__490 {
+  width: 490px !important;
+  height: 490px !important; }
+
+.lh__490 {
+  line-height: 490px !important; }
+
+.wh__500 {
+  width: 500px !important;
+  height: 500px !important; }
+
+.lh__500 {
+  line-height: 500px !important; }
+
+.lh__1 {
+  line-height: 1 !important; }
+
+.no-wrap {
+  white-space: nowrap; }
+
+.ov-h {
+  overflow: hidden; }
+
+.va-m {
+  vertical-align: middle; }
+
+.bg-inherit {
+  background-color: inherit; }
+
+.bg-black {
+  background-color: black; }
+
+.v-center:before {
+  content: '';
+  display: inline-block;
+  height: 100%;
+  vertical-align: middle;
+  margin-top: -0.25em;
+  /* Adjusts for spacing */ }
+
+.fluid-height {
+  height: 100%; }
+
+.visibility__hidden {
+  visibility: hidden; }
+
+.pointerEvents__none {
+  pointer-events: none; }
+
+/* Padding Helpers */
+.pn {
+  padding: 0 !important; }
+
+.p1 {
+  padding: 1px !important; }
+
+.p2 {
+  padding: 2px !important; }
+
+.p3 {
+  padding: 3px !important; }
+
+.p4 {
+  padding: 4px !important; }
+
+.p5 {
+  padding: 5px !important; }
+
+.p6 {
+  padding: 6px !important; }
+
+.p7 {
+  padding: 7px !important; }
+
+.p8 {
+  padding: 8px !important; }
+
+.p10 {
+  padding: 10px !important; }
+
+.p12 {
+  padding: 12px !important; }
+
+.p15 {
+  padding: 15px !important; }
+
+.p20 {
+  padding: 20px !important; }
+
+.p25 {
+  padding: 25px !important; }
+
+.p30 {
+  padding: 30px !important; }
+
+.p35 {
+  padding: 35px !important; }
+
+.p40 {
+  padding: 40px !important; }
+
+.p50 {
+  padding: 50px !important; }
+
+.ptn {
+  padding-top: 0 !important; }
+
+.pt5 {
+  padding-top: 5px !important; }
+
+.pt10 {
+  padding-top: 10px !important; }
+
+.pt15 {
+  padding-top: 15px !important; }
+
+.pt20 {
+  padding-top: 20px !important; }
+
+.pt25 {
+  padding-top: 25px !important; }
+
+.pt30 {
+  padding-top: 30px !important; }
+
+.pt35 {
+  padding-top: 35px !important; }
+
+.pt40 {
+  padding-top: 40px !important; }
+
+.pt50 {
+  padding-top: 50px !important; }
+
+.prn {
+  padding-right: 0 !important; }
+
+.pr5 {
+  padding-right: 5px !important; }
+
+.pr10 {
+  padding-right: 10px !important; }
+
+.pr15 {
+  padding-right: 15px !important; }
+
+.pr20 {
+  padding-right: 20px !important; }
+
+.pr25 {
+  padding-right: 25px !important; }
+
+.pr30 {
+  padding-right: 30px !important; }
+
+.pr35 {
+  padding-right: 35px !important; }
+
+.pr40 {
+  padding-right: 40px !important; }
+
+.pr50 {
+  padding-right: 50px !important; }
+
+.pbn {
+  padding-bottom: 0 !important; }
+
+.pb5 {
+  padding-bottom: 5px !important; }
+
+.pb10 {
+  padding-bottom: 10px !important; }
+
+.pb15 {
+  padding-bottom: 15px !important; }
+
+.pb20 {
+  padding-bottom: 20px !important; }
+
+.pb25 {
+  padding-bottom: 25px !important; }
+
+.pb30 {
+  padding-bottom: 30px !important; }
+
+.pb35 {
+  padding-bottom: 35px !important; }
+
+.pb40 {
+  padding-bottom: 40px !important; }
+
+.pb50 {
+  padding-bottom: 50px !important; }
+
+.pln {
+  padding-left: 0 !important; }
+
+.pl5 {
+  padding-left: 5px !important; }
+
+.pl10 {
+  padding-left: 10px !important; }
+
+.pl15 {
+  padding-left: 15px !important; }
+
+.pl20 {
+  padding-left: 20px !important; }
+
+.pl25 {
+  padding-left: 25px !important; }
+
+.pl30 {
+  padding-left: 30px !important; }
+
+.pl35 {
+  padding-left: 35px !important; }
+
+.pl40 {
+  padding-left: 40px !important; }
+
+.pl50 {
+  padding-left: 50px !important; }
+
+/* Axis Padding (both top/bottom or left/right) */
+.pv5 {
+  padding-top: 5px !important;
+  padding-bottom: 5px !important; }
+
+.pv8 {
+  padding-top: 8px !important;
+  padding-bottom: 8px !important; }
+
+.pv10 {
+  padding-top: 10px !important;
+  padding-bottom: 10px !important; }
+
+.pv15 {
+  padding-top: 15px !important;
+  padding-bottom: 15px !important; }
+
+.pv20 {
+  padding-top: 20px !important;
+  padding-bottom: 20px !important; }
+
+.pv25 {
+  padding-top: 25px !important;
+  padding-bottom: 25px !important; }
+
+.pv30 {
+  padding-top: 30px !important;
+  padding-bottom: 30px !important; }
+
+.pv40 {
+  padding-top: 40px !important;
+  padding-bottom: 40px !important; }
+
+.pv50 {
+  padding-top: 50px !important;
+  padding-bottom: 50px !important; }
+
+.ph5 {
+  padding-left: 5px !important;
+  padding-right: 5px !important; }
+
+.ph8 {
+  padding-left: 8px !important;
+  padding-right: 8px !important; }
+
+.ph10 {
+  padding-left: 10px !important;
+  padding-right: 10px !important; }
+
+.ph15 {
+  padding-left: 15px !important;
+  padding-right: 15px !important; }
+
+.ph20 {
+  padding-left: 20px !important;
+  padding-right: 20px !important; }
+
+.ph25 {
+  padding-left: 25px !important;
+  padding-right: 25px !important; }
+
+.ph30 {
+  padding-left: 30px !important;
+  padding-right: 30px !important; }
+
+.ph40 {
+  padding-left: 40px !important;
+  padding-right: 40px !important; }
+
+.ph50 {
+  padding-left: 50px !important;
+  padding-right: 50px !important; }
+
+/* margin center helper */
+.mauto {
+  margin-left: auto;
+  margin-right: auto; }
+
+.mn {
+  margin: 0 !important; }
+
+.m1 {
+  margin: 1px !important; }
+
+.m2 {
+  margin: 2px !important; }
+
+.m3 {
+  margin: 3px !important; }
+
+.m4 {
+  margin: 4px !important; }
+
+.m5 {
+  margin: 5px !important; }
+
+.m8 {
+  margin: 8px !important; }
+
+.m10 {
+  margin: 10px !important; }
+
+.m15 {
+  margin: 15px !important; }
+
+.m20 {
+  margin: 20px !important; }
+
+.m25 {
+  margin: 25px !important; }
+
+.m30 {
+  margin: 30px !important; }
+
+.m35 {
+  margin: 35px !important; }
+
+.m40 {
+  margin: 40px !important; }
+
+.m50 {
+  margin: 50px !important; }
+
+.mtn {
+  margin-top: 0 !important; }
+
+.mt5 {
+  margin-top: 5px !important; }
+
+.mt10 {
+  margin-top: 10px !important; }
+
+.mt15 {
+  margin-top: 15px !important; }
+
+.mt20 {
+  margin-top: 20px !important; }
+
+.mt25 {
+  margin-top: 25px !important; }
+
+.mt30 {
+  margin-top: 30px !important; }
+
+.mt35 {
+  margin-top: 35px !important; }
+
+.mt40 {
+  margin-top: 40px !important; }
+
+.mt50 {
+  margin-top: 50px !important; }
+
+.mt70 {
+  margin-top: 70px !important; }
+
+.mrn {
+  margin-right: 0 !important; }
+
+.mr5 {
+  margin-right: 5px !important; }
+
+.mr10 {
+  margin-right: 10px !important; }
+
+.mr15 {
+  margin-right: 15px !important; }
+
+.mr20 {
+  margin-right: 20px !important; }
+
+.mr25 {
+  margin-right: 25px !important; }
+
+.mr30 {
+  margin-right: 30px !important; }
+
+.mr35 {
+  margin-right: 35px !important; }
+
+.mr40 {
+  margin-right: 40px !important; }
+
+.mr50 {
+  margin-right: 50px !important; }
+
+.mbn {
+  margin-bottom: 0 !important; }
+
+.mb5 {
+  margin-bottom: 5px !important; }
+
+.mb10 {
+  margin-bottom: 10px !important; }
+
+.mb15 {
+  margin-bottom: 15px !important; }
+
+.mb20 {
+  margin-bottom: 20px !important; }
+
+.mb25 {
+  margin-bottom: 25px !important; }
+
+.mb30 {
+  margin-bottom: 30px !important; }
+
+.mb35 {
+  margin-bottom: 35px !important; }
+
+.mb40 {
+  margin-bottom: 40px !important; }
+
+.mb50 {
+  margin-bottom: 50px !important; }
+
+.mb70 {
+  margin-bottom: 70px !important; }
+
+.mln {
+  margin-left: 0 !important; }
+
+.ml5 {
+  margin-left: 5px !important; }
+
+.ml10 {
+  margin-left: 10px !important; }
+
+.ml15 {
+  margin-left: 15px !important; }
+
+.ml20 {
+  margin-left: 20px !important; }
+
+.ml25 {
+  margin-left: 25px !important; }
+
+.ml30 {
+  margin-left: 30px !important; }
+
+.ml35 {
+  margin-left: 35px !important; }
+
+.ml40 {
+  margin-left: 40px !important; }
+
+.ml50 {
+  margin-left: 50px !important; }
+
+/* Axis Margins (both top/bottom or left/right) */
+.mv5 {
+  margin-top: 5px !important;
+  margin-bottom: 5px !important; }
+
+.mv10 {
+  margin-top: 10px !important;
+  margin-bottom: 10px !important; }
+
+.mv15 {
+  margin-top: 15px !important;
+  margin-bottom: 15px !important; }
+
+.mv20 {
+  margin-top: 20px !important;
+  margin-bottom: 20px !important; }
+
+.mv25 {
+  margin-top: 25px !important;
+  margin-bottom: 25px !important; }
+
+.mv30 {
+  margin-top: 30px !important;
+  margin-bottom: 30px !important; }
+
+.mv40 {
+  margin-top: 40px !important;
+  margin-bottom: 40px !important; }
+
+.mv50 {
+  margin-top: 50px !important;
+  margin-bottom: 50px !important; }
+
+.mv70 {
+  margin-top: 70px !important;
+  margin-bottom: 70px !important; }
+
+.mh5 {
+  margin-left: 5px !important;
+  margin-right: 5px !important; }
+
+.mh10 {
+  margin-left: 10px !important;
+  margin-right: 10px !important; }
+
+.mh15 {
+  margin-left: 15px !important;
+  margin-right: 15px !important; }
+
+.mh20 {
+  margin-left: 20px !important;
+  margin-right: 20px !important; }
+
+.mh25 {
+  margin-left: 25px !important;
+  margin-right: 25px !important; }
+
+.mh30 {
+  margin-left: 30px !important;
+  margin-right: 30px !important; }
+
+.mh40 {
+  margin-left: 40px !important;
+  margin-right: 40px !important; }
+
+.mh50 {
+  margin-left: 50px !important;
+  margin-right: 50px !important; }
+
+.mh70 {
+  margin-left: 70px !important;
+  margin-right: 70px !important; }
+
+/* Negative Margin Helpers */
+.mtn5 {
+  margin-top: -5px !important; }
+
+.mtn10 {
+  margin-top: -10px !important; }
+
+.mtn15 {
+  margin-top: -15px !important; }
+
+.mtn20 {
+  margin-top: -20px !important; }
+
+.mtn30 {
+  margin-top: -30px !important; }
+
+.mrn5 {
+  margin-right: -5px !important; }
+
+.mrn10 {
+  margin-right: -10px !important; }
+
+.mrn15 {
+  margin-right: -15px !important; }
+
+.mrn20 {
+  margin-right: -20px !important; }
+
+.mrn30 {
+  margin-right: -30px !important; }
+
+.mbn5 {
+  margin-bottom: -5px !important; }
+
+.mbn10 {
+  margin-bottom: -10px !important; }
+
+.mbn15 {
+  margin-bottom: -15px !important; }
+
+.mbn20 {
+  margin-bottom: -20px !important; }
+
+.mbn30 {
+  margin-bottom: -30px !important; }
+
+.mln5 {
+  margin-left: -5px !important; }
+
+.mln10 {
+  margin-left: -10px !important; }
+
+.mln15 {
+  margin-left: -15px !important; }
+
+.mln20 {
+  margin-left: -20px !important; }
+
+.mln30 {
+  margin-left: -30px !important; }
+
+/* Vertical Negative Margin "mv" + "n" + "x" */
+.mvn5 {
+  margin-top: -5px !important;
+  margin-bottom: -5px !important; }
+
+.mvn10 {
+  margin-top: -10px !important;
+  margin-bottom: -10px !important; }
+
+.mvn15 {
+  margin-top: -15px !important;
+  margin-bottom: -15px !important; }
+
+.mvn20 {
+  margin-top: -20px !important;
+  margin-bottom: -20px !important; }
+
+.mvn30 {
+  margin-top: -30px !important;
+  margin-bottom: -30px !important; }
+
+/* Horizontal Negative Margin "mh" + "n" + "x" */
+.mhn5 {
+  margin-left: -5px !important;
+  margin-right: -5px !important; }
+
+.mhn10 {
+  margin-left: -10px !important;
+  margin-right: -10px !important; }
+
+.mhn15 {
+  margin-left: -15px !important;
+  margin-right: -15px !important; }
+
+.mhn20 {
+  margin-left: -20px !important;
+  margin-right: -20px !important; }
+
+.mhn30 {
+  margin-left: -30px !important;
+  margin-right: -30px !important; }
+
+/* Vertical Align Helpers */
+.va-t {
+  vertical-align: top !important; }
+
+.va-m {
+  vertical-align: middle !important; }
+
+.va-b {
+  vertical-align: bottom !important; }
+
+.va-s {
+  vertical-align: super !important; }
+
+/* Text Helpers */
+.text-left {
+  text-align: left !important; }
+
+.text-right {
+  text-align: right !important; }
+
+.text-center {
+  text-align: center !important; }
+
+.text-justify {
+  text-align: justify !important; }
+
+.text-nowrap {
+  white-space: nowrap !important; }
+
+/* Inline Block Helper */
+.ib,
+.inline-object {
+  display: inline-block !important; }
+
+.clear {
+  clear: both; }
+
+.wvWarning {
+  position: relative;
+  width: 320px;
+  min-height: 130px;
+  z-index: 999;
+  left: calc(50% - 160px);
+  border: #000 solid 1px;
+  -webkit-border-radius: 7px;
+  -moz-border-radius: 7px;
+  border-radius: 7px;
+  color: #FF5722;
+  box-shadow: 0px 3px 23px #ff980078;
+  -webkit-animation-name: example;
+  /* Safari 4.0 - 8.0 */
+  -webkit-animation-duration: 3s;
+  /* Safari 4.0 - 8.0 */
+  -webkit-animation-fill-mode: both;
+  /* Safari 4.0 - 8.0 */
+  animation-name: example;
+  animation-duration: 2s;
+  animation-fill-mode: both;
+  animation-timing-function: ease-out; }
+
+@-webkit-keyframes example {
+  from {
+    top: 0vh;
+    opacity: 0;
+    background: #868686; }
+  to {
+    top: 10vh;
+    opacity: 1;
+    background: #ffffff; } }
+
+@keyframes example {
+  from {
+    top: 0vh;
+    opacity: 0;
+    background: #868686; }
+  to {
+    top: 10vh;
+    opacity: 1;
+    background: #ffffff; } }
+
+.wvWarning-content {
+  position: relative;
+  width: 190px;
+  min-height: 88px;
+  max-height: 80vh;
+  margin: auto; }
+
+.wvWarning-icon {
+  font-size: 32px; }
+
+.wvWarning-text {
+  position: relative; }
+
+.wvWarning-button {
+  background-color: #f1ededcc;
+  color: #607D8B;
+  width: 50px;
+  font-weight: 600;
+  margin-top: 2px;
+  margin-right: 30px; }
+
+.wvScreenToSmallWarning {
+  position: fixed;
+  display: block;
+  top: 0;
+  left: 0;
+  background-color: white;
+  color: #333;
+  width: 100%;
+  height: 100%;
+  z-index: 1000; }
+
+.wvScreenToSmallWarning-content {
+  padding: 10px;
+  text-align: center; }
+
+/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
+@media only screen and (min-width: 550px) and (min-height: 280px) {
+  .wvScreenToSmallWarning {
+    display: none; } }
+
+/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
+@media only screen and (min-width: 280px) and (min-height: 550px) {
+  .wvScreenToSmallWarning {
+    display: none; } }
+
+wv-notice {
+  display: block;
+  height: 100%;
+  width: 100%; }
+
+.wvNotice {
+  padding: 0.5rem 0.25rem;
+  height: 100%; }
+
+.wvNotice__text {
+  position: relative;
+  top: 50%;
+  transform: translateY(-50%);
+  text-align: center;
+  margin-left: 1rem;
+  font-weight: 400;
+  color: #b3b3b3;
+  float: left;
+  width: calc(100% - 7rem); }
+
+.wvNotice__closeButton {
+  float: right;
+  margin-right: 0.5em;
+  position: relative;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 3.5rem;
+  height: 2.5rem;
+  text-align: center;
+  font-size: 1em;
+  font-weight: 100;
+  line-height: 2.2rem;
+  cursor: pointer;
+  border: 1px solid #454545; }
+
+/* layout: left section */
+.wvLayoutLeft {
+  position: absolute;
+  z-index: 2;
+  background-color: black;
+  width: 32rem;
+  left: 0; }
+  .wvLayoutLeft.wvLayoutLeft--toppadding {
+    top: 42px; }
+  .wvLayoutLeft:not(.wvLayoutLeft--toppadding) {
+    top: 0; }
+  @media screen and (max-device-width: 374px) {
+    .wvLayoutLeft.wvLayoutLeft--bottompadding {
+      bottom: 7rem; } }
+  @media screen and (min-device-width: 375px) {
+    .wvLayoutLeft.wvLayoutLeft--bottompadding {
+      bottom: 5rem; } }
+  .wvLayoutLeft:not(.wvLayoutLeft--bottompadding) {
+    bottom: 0; }
+  .wvLayoutLeft.wvLayoutLeft--closed {
+    transform: translateX(-32rem); }
+    .wvLayoutLeft.wvLayoutLeft--closed.wvLayoutLeft--small {
+      transform: translateX(-12rem); }
+  .wvLayoutLeft.wvLayoutLeft--small {
+    width: 12rem; }
+    .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentTop, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentMiddle, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentBottom {
+      width: 100%; }
+
+.wvLayoutLeft__content {
+  border-right: 1px solid #AAA;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow-y: auto;
+  height: 100%; }
+
+.wvLayoutLeft__contentTop {
+  padding: 0rem 1rem 0rem 1rem;
+  width: 31.9rem; }
+  .wvLayoutLeft__contentTop:after {
+    content: "";
+    display: block;
+    height: 0;
+    width: 0;
+    clear: both; }
+
+.wvLayoutLeft__contentMiddle {
+  flex: 1 0 auto;
+  width: 31.9rem; }
+
+.wvLayoutLeft__contentBottom {
+  width: 31.9rem; }
+
+.wvLayout__leftBottom.wvLayout__leftBottom--enabled {
+  border-top: 1px solid rgba(255, 255, 255, 0.2);
+  margin-top: 1rem;
+  padding: 1rem; }
+
+.wvLayoutLeft__actions, .wvLayoutLeft__actions--outside {
+  display: block;
+  position: absolute;
+  right: 1px;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 25px; }
+
+.wvLayoutLeft__actions--outside {
+  right: -25px; }
+
+.wvLayoutLeft__action {
+  background-color: #3498db;
+  opacity: 0.5;
+  color: white;
+  transition: none; }
+  .wvLayoutLeft__action:hover, .wvLayoutLeft__action:focus {
+    opacity: 1; }
+
+/* layout: right section */
+.wvLayout__right {
+  display: block;
+  position: absolute;
+  z-index: 2;
+  background-color: black;
+  width: 85px;
+  right: 0; }
+  .wvLayout__right.wvLayout__right--toppadding {
+    top: 42px; }
+  .wvLayout__right:not(.wvLayout__right--toppadding) {
+    top: 0; }
+  @media screen and (max-device-width: 374px) {
+    .wvLayout__right.wvLayout__right--bottompadding {
+      bottom: 7rem; } }
+  @media screen and (min-device-width: 375px) {
+    .wvLayout__right.wvLayout__right--bottompadding {
+      bottom: 5rem; } }
+  .wvLayout__right:not(.wvLayout__right--bottompadding) {
+    bottom: 0; }
+  .wvLayout__right.wvLayout__right--closed {
+    transform: translateX(85px); }
+  .wvLayout__right > wv-layout-right,
+  .wvLayout__right > wv-layout-right > .wvViewer__asideRight {
+    display: block;
+    height: 100%;
+    width: 100%; }
+
+.wvAsideRight__content {
+  height: 100%;
+  float: left;
+  border-left: 1px solid #AAA;
+  padding: 0 5px;
+  width: 32rem; }
+
+.wvAsideRight__actions, .wvAsideRight__actions--outside {
+  display: block;
+  position: absolute;
+  left: 1px;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 25px;
+  z-index: 3; }
+
+.wvAsideRight__actions--outside {
+  left: -25px; }
+
+.wvAsideRight__action {
+  background-color: #3498db;
+  opacity: 0.5;
+  color: white;
+  transition: none; }
+  .wvAsideRight__action:hover, .wvAsideRight__action:focus {
+    opacity: 1; }
+
+.wvAsideRight__fixOpenFullyTooltip + .tooltip {
+  left: -6.633em !important;
+  top: 1px !important; }
+
+/* layout: bottom section */
+.wvLayout__bottom {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background-color: #1a1a1a; }
+  @media screen and (max-device-width: 374px) {
+    .wvLayout__bottom {
+      height: 7rem; } }
+  @media screen and (min-device-width: 375px) {
+    .wvLayout__bottom {
+      height: 5rem; } }
+
+/* layout: main section */
+.wvLayout__main {
+  position: absolute;
+  text-align: center;
+  right: 0;
+  left: 0; }
+  .wvLayout__main .wvLayout__splitpane--toolbarAtTop {
+    display: block;
+    height: calc(100% - 42px);
+    width: 100%;
+    position: relative;
+    top: 42px; }
+  .wvLayout__main .wvLayout__splitpane--toolbarAtRight {
+    display: block;
+    height: 100%;
+    width: calc(100% - 42px); }
+  .wvLayout__main .wvLayout__splitpane--bigToolbarAtTop {
+    display: block;
+    height: calc(100% - 68px);
+    width: 100%;
+    position: relative;
+    top: 68px; }
+  .wvLayout__main .wvLayout__splitpane--bigToolbarAtRight {
+    display: block;
+    height: 100%;
+    width: calc(100% - 68px); }
+  .wvLayout__main.wvLayout__main--toppadding {
+    top: 42px; }
+  .wvLayout__main:not(.wvLayout__main--toppadding) {
+    top: 0; }
+  .wvLayout__main.wvLayout__main--bottompadding {
+    bottom: 440px; }
+    @media screen and (max-device-width: 374px) {
+      .wvLayout__main.wvLayout__main--bottompadding {
+        bottom: 7rem; } }
+    @media screen and (min-device-width: 375px) {
+      .wvLayout__main.wvLayout__main--bottompadding {
+        bottom: 5rem; } }
+  .wvLayout__main:not(.wvLayout__main--bottompadding) {
+    bottom: 0; }
+  .wvLayout__main.wvLayout__main--leftpadding {
+    left: 32rem; }
+  .wvLayout__main {
+    left: 0px; }
+  .wvLayout__main.wvLayout__main--smallleftpadding {
+    left: 12rem; }
+  .wvLayout__main.wvLayout__main--rightpadding {
+    right: 85px; }
+  .wvLayout__main:not(.wvLayout__main--rightpadding) {
+    right: 0px; }
+
+/* global */
+.popover {
+  color: black; }
+
+.wvViewer__editor--full {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 10;
+  opacity: 0;
+  transform: translateX(100%);
+  width: 100%;
+  height: 100%;
+  background-color: white;
+  color: #666666; }
+  .wvViewer__editor--full.opened {
+    opacity: 1;
+    transform: translateX(0); }
+
+.wvViewer__topBar {
+  width: 100%;
+  overflow-y: auto;
+  white-space: nowrap;
+  max-width: 100%; }
+
+.wvViewer__buttonGroup {
+  display: inline-block; }
+
+.wvViewer__buttonGroup--asideWidth {
+  width: 32rem;
+  padding-right: 1rem; }
+
+.wvViewer__buttonGroup--contentWidth {
+  width: calc(100% - 32rem);
+  padding-left: 1rem;
+  max-height: 4.2rem; }
+
+.wvViewer__iframe {
+  position: absolute;
+  left: 0;
+  top: 0; }
+
+/* bottom bar */
+.wvViewer__bottomBar, .wvViewer__bottomBar--expanded, .wvViewer__bottomBar--minimized {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  background-color: #111111; }
+
+.wvViewer__bottomBar--expanded {
+  height: 80px;
+  color: white; }
+  .wvViewer__bottomBar--expanded .wvViewer__timeline {
+    width: calc(100% - 80px); }
+  .wvViewer__bottomBar--expanded .wvTimeline__hotspots {
+    bottom: -40px; }
+
+.wvViewer__bottomBar--minimized {
+  color: white;
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+  padding-left: 2.5rem; }
+  .wvViewer__bottomBar--minimized .wvTimeline__hotspot {
+    top: -40px;
+    opacity: 0;
+    visibility: hidden;
+    z-index: -1; }
+  .wvViewer__bottomBar--minimized:hover .wvTimeline__hotspot {
+    opacity: 1;
+    visibility: visible;
+    z-index: 5;
+    transition-delay: 0s; }
+
+.wvViewer__timeline {
+  height: 24px;
+  line-height: 24px;
+  vertical-align: middle;
+  width: 100%; }
+
+.wvViewer__trademark {
+  display: inline-block;
+  float: right;
+  width: 80px;
+  height: 80px;
+  float: right;
+  line-height: 80px;
+  vertical-align: middle;
+  text-align: center; }
+
+.wvTimeline__input {
+  border-radius: 3px;
+  margin-top: 2px;
+  border: 1px solid #e7e7e7; }
+  .wvTimeline__input:focus {
+    outline: none; }
+
+.wvTimeline__actions {
+  display: inline-block;
+  border-right: 1px solid #e7e7e7; }
+
+.wvSerieslist {
+  margin: 0;
+  padding: 0;
+  list-style: none; }
+
+.wvSerieslist__seriesItem--selectable {
+  cursor: pointer !important; }
+  .wvSerieslist__seriesItem--selectable:hover {
+    color: white; }
+
+.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  font-size: 3.25rem;
+  line-height: 6.5rem;
+  text-align: center; }
+
+.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa {
+  color: #c3c3c3; }
+  .wvSerieslist__placeholderIcon--strikeout::after, .wvSerieslist__placeholderIcon--strikeout.fa::after {
+    position: absolute;
+    left: 0;
+    top: 50%;
+    right: 0;
+    transform: rotate(-45deg) scaleX(0.9);
+    border-top: 5px solid;
+    border-color: inherit;
+    content: ""; }
+
+.wvSerieslist__picture {
+  display: inline-block;
+  font-size: 14px;
+  width: 6.5rem;
+  height: 6.5rem;
+  position: relative;
+  z-index: -1; }
+
+.wvSerieslist__badge, .wvSerieslist__badge--blue, .wvSerieslist__badge--red, .wvSerieslist__badge--green, .wvSerieslist__badge--yellow, .wvSerieslist__badge--violet {
+  position: absolute;
+  bottom: 5px;
+  right: 5px;
+  font-size: 10px;
+  line-height: 15px;
+  width: 15px;
+  height: 15px;
+  border-radius: 100%;
+  background-color: gray;
+  vertical-align: middle;
+  text-align: center;
+  font-weight: bold; }
+
+.wvSerieslist__information {
+  font-size: 14px;
+  float: right;
+  padding-left: 1rem;
+  width: calc(100% - 6.5rem);
+  height: 6.5rem; }
+
+.wvSerieslist__label {
+  white-space: nowrap;
+  width: calc(100% - 10px);
+  overflow: hidden;
+  height: 3.25rem;
+  line-height: 3.25rem;
+  vertical-align: middle; }
+
+.wvSerieslist__timeline {
+  height: 3.25rem;
+  line-height: 3.25rem;
+  vertical-align: middle; }
+
+.wvSerieslist__seriesItem {
+  position: relative;
+  padding-left: 0;
+  list-style: none;
+  font-size: 0;
+  border-right: 0.2rem solid transparent;
+  border-left: 0.2rem solid transparent;
+  border-top: 0.2rem solid transparent;
+  border-bottom: 0.2rem solid transparent;
+  border-corner-shape: notch;
+  line-height: 0px;
+  margin: 0.1rem; }
+  .wvSerieslist__seriesItem.active {
+    border-color: rgba(255, 255, 255, 0.6);
+    border-style: solid; }
+  .wvSerieslist__seriesItem.highlighted {
+    border-color: white;
+    border-style: solid; }
+  .wvSerieslist__seriesItem:hover, .wvSerieslist__seriesItem:focus, .wvSerieslist__seriesItem.focused {
+    border-style: dashed;
+    border-color: rgba(255, 255, 255, 0.8); }
+
+.wvSerieslist__seriesItem--list {
+  display: block; }
+
+.wvSerieslist__seriesItem--grid {
+  display: inline-block; }
+
+.wvSerieslist__seriesItem--oneCol {
+  text-align: center; }
+
+.wvSerieslist__seriesItem--activated,
+.wvSerieslist__videoItem--activated,
+.wvSerieslist__pdfItem--activated {
+  border: 0.2rem solid #3398db !important; }
+
+.wvSerieslist__badge--blue {
+  background-color: rgba(51, 152, 219, 0.7); }
+
+.wvSerieslist__badge--red {
+  background-color: rgba(206, 0, 0, 0.7); }
+
+.wvSerieslist__badge--green {
+  background-color: rgba(0, 160, 27, 0.7); }
+
+.wvSerieslist__badge--yellow {
+  background-color: rgba(220, 200, 0, 0.9); }
+
+.wvSerieslist__badge--violet {
+  background-color: rgba(255, 31, 255, 0.7); }
+
+.wvToolbar {
+  position: absolute; }
+
+.wvToolbar--top {
+  top: 0;
+  height: 42px;
+  right: 0;
+  text-align: right;
+  white-space: nowrap;
+  max-width: 100%; }
+
+.wvToolbar--right {
+  right: 0;
+  width: 42px;
+  height: 100%;
+  z-index: 2; }
+  .wvToolbar--right.wvToolbar--big {
+    width: 68px; }
+
+/* Splitpane Grid Configuration */
+.wvToolbar__splitpaneConfigPopover {
+  font-size: 0; }
+
+.wvToolbar__splitpaneConfigNotice {
+  font-size: 1.25rem;
+  font-style: italic;
+  text-align: center;
+  color: #333; }
+
+input[type="radio"].wvToolbar__splitpaneConfigButtonInput {
+  position: absolute;
+  width: 0;
+  height: 0;
+  left: 0;
+  top: 0;
+  bottom: 2px;
+  right: 0;
+  opacity: 0; }
+
+/* Windowing Preset */
+.wvToolbar__windowingPresetConfigNotice {
+  font-size: 1.25rem;
+  font-style: italic;
+  text-align: center;
+  color: #333; }
+
+.wvToolbar__windowingPresetList {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  font-size: 1.5rem; }
+
+.wvToolbar__windowingPresetListItem {
+  outline: none;
+  background-color: transparent;
+  border: none;
+  position: relative;
+  display: inline-block;
+  cursor: pointer;
+  font-variant: small-caps;
+  text-transform: lowercase;
+  text-align: center;
+  font-size: 1.3rem;
+  font-weight: 400;
+  line-height: 2.2rem;
+  color: #d9d9d9;
+  transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
+  margin: 0;
+  min-width: 3rem;
+  padding: 0 10px;
+  line-height: 3.6rem;
+  max-height: 2.8rem;
+  max-width: 100%;
+  overflow: hidden;
+  margin: 0.6rem;
+  margin-left: 0rem;
+  margin-right: 0rem;
+  line-height: 2rem;
+  padding-top: 0.1rem;
+  padding-bottom: 0.5rem;
+  font-size: 1.4rem;
+  border: 1px solid #454545;
+  font-family: Arial;
+  background-color: black;
+  color: #1a1a1a;
+  border: 1px solid #bababa;
+  background-color: white;
+  width: 100%;
+  margin: 0;
+  margin-left: 0 !important;
+  border-top: none;
+  border-bottom: none; }
+  .wvToolbar__windowingPresetListItem:hover {
+    text-decoration: none;
+    color: white; }
+  .wvToolbar__windowingPresetListItem + .wvToolbar__windowingPresetListItem {
+    margin-left: 0.7rem; }
+  .wvToolbar__windowingPresetListItem:hover {
+    background-color: #1a1a1a; }
+  .wvToolbar__windowingPresetListItem > .glyphicon {
+    position: relative;
+    display: inline-block;
+    top: 3px;
+    margin-right: 4px; }
+  .wvToolbar__windowingPresetListItem:hover {
+    color: #1a1a1a;
+    background-color: #e6e6e6; }
+
+.wvVideo {
+  position: absolute;
+  top: 50%;
+  left: 0;
+  width: 100%;
+  height: auto;
+  transform: translateY(-50%); }
+
+.wvStudyInformationBreadcrumb__patient {
+  display: inline-block;
+  background-color: rgba(255, 255, 255, 0.15);
+  padding: 0.2rem 1rem 0.3rem 1rem;
+  text-align: center;
+  font-size: 1em;
+  margin: 0.6rem;
+  font-weight: 400;
+  line-height: 2.3rem;
+  margin-right: 0; }
+
+.wvStudyInformationBreadcrumb__study {
+  display: inline-block;
+  background-color: rgba(255, 255, 255, 0.15);
+  padding: 0.2rem 1rem 0.3rem 1rem;
+  text-align: center;
+  font-size: 1em;
+  margin: 0.6rem;
+  font-weight: 400;
+  line-height: 2.3rem; }
+
+.wvSelectionActionlist {
+  display: block;
+  text-align: center; }
+
+/* wvb-ui stuffs */
+.wv-overlay {
+  color: orange; }
+
+.wv-overlay-icon {
+  width: 64px; }
+
+.wvOverlay__studyBadge, .wvOverlay__studyBadge--blue, .wvOverlay__studyBadge--red, .wvOverlay__studyBadge--green, .wvOverlay__studyBadge--yellow, .wvOverlay__studyBadge--violet {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 1.5rem;
+  height: 1.5rem;
+  background-color: gray;
+  z-index: 1; }
+
+.wv-overlay-topleft {
+  position: absolute;
+  top: 0rem;
+  left: 0rem;
+  text-align: left; }
+
+.wv-overlay-topright {
+  position: absolute;
+  top: 0rem;
+  right: 0rem;
+  text-align: right; }
+
+.wv-overlay-bottomright {
+  position: absolute;
+  bottom: 2em;
+  right: 0rem;
+  text-align: right; }
+
+.wv-overlay-bottomleft {
+  position: absolute;
+  bottom: 2em;
+  left: 0rem;
+  text-align: left; }
+
+.wv-overlay-timeline-wrapper {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1; }
+
+.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft {
+  padding: 2rem;
+  transition: color 500ms, background-color 500ms;
+  background-color: rgba(0, 0, 0, 0.66); }
+
+.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover {
+  background-color: rgba(0, 0, 0, 0.9); }
+
+.wvPaneOverlay {
+  position: absolute;
+  top: 50%;
+  width: 100%;
+  transform: translateY(-50%);
+  font-weight: 100;
+  text-align: center;
+  color: white;
+  font-size: 2rem; }
+
+.wv-overlay-scrollbar-loaded {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  height: 5px;
+  background-color: red;
+  will-change: right;
+  transform-origin: 0% 50%; }
+
+.wv-overlay-scrollbar-loading {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  height: 5px;
+  background-color: #660000;
+  will-change: right;
+  transform-origin: 0% 50%; }
+
+.wv-overlay-scrollbar-text {
+  position: absolute;
+  bottom: calc(1em + 5px);
+  left: 5px;
+  height: 1em;
+  color: red;
+  font-size: 0.8em;
+  font-family: helvetica; }
+
+.wvOverlay__studyBadge--blue {
+  background-color: rgba(51, 152, 219, 0.7); }
+
+.wvOverlay__studyBadge--red {
+  background-color: rgba(206, 0, 0, 0.7); }
+
+.wvOverlay__studyBadge--green {
+  background-color: rgba(0, 160, 27, 0.7); }
+
+.wvOverlay__studyBadge--yellow {
+  background-color: rgba(220, 200, 0, 0.9); }
+
+.wvOverlay__studyBadge--violet {
+  background-color: rgba(255, 31, 255, 0.7); }
+
+wv-pdf-viewer {
+  display: block;
+  width: 100%;
+  height: 100%; }
+
+#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton {
+  background-color: inherit;
+  color: white;
+  border: none;
+  padding: 2px;
+  margin-left: 4px;
+  margin-right: 2px; }
+  #toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton:hover {
+    color: black; }
+
+.fa.fa-window-close.wv-pdf-viewer-closebuttonicon {
+  font-size: 2rem;
+  line-height: 28px; }
+
+.pdfjs .textLayer {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+  opacity: 0.2; }
+
+.pdfjs .textLayer > div {
+  color: transparent;
+  position: absolute;
+  white-space: pre;
+  cursor: text;
+  -webkit-transform-origin: 0 0;
+  -moz-transform-origin: 0 0;
+  -o-transform-origin: 0 0;
+  -ms-transform-origin: 0 0;
+  transform-origin: 0 0; }
+
+.pdfjs .textLayer .highlight {
+  margin: -1px;
+  padding: 1px;
+  background-color: #b400aa;
+  border-radius: 4px; }
+
+.pdfjs .textLayer .highlight.begin {
+  border-radius: 4px 0 0 4px; }
+
+.pdfjs .textLayer .highlight.end {
+  border-radius: 0 4px 4px 0; }
+
+.pdfjs .textLayer .highlight.middle {
+  border-radius: 0; }
+
+.pdfjs .textLayer .highlight.selected {
+  background-color: #006400; }
+
+.pdfjs .textLayer ::selection {
+  background: #00f; }
+
+.pdfjs .textLayer ::-moz-selection {
+  background: #00f; }
+
+.pdfjs .pdfViewer .canvasWrapper {
+  overflow: hidden; }
+
+.pdfjs .pdfViewer .page {
+  direction: ltr;
+  width: 816px;
+  height: 1056px;
+  margin: 1px auto -8px;
+  position: relative;
+  overflow: visible;
+  border: 9px solid transparent;
+  background-clip: content-box;
+  border-image: url("../images/pdf.js-viewer/shadow.png") 9 9 repeat;
+  background-color: #fff; }
+
+body {
+  height: 100%; }
+
+.pdfjs .pdfViewer.removePageBorders .page {
+  margin: 0 auto 10px;
+  border: none; }
+
+.pdfjs .pdfViewer .page canvas {
+  margin: 0;
+  display: block; }
+
+.pdfjs .pdfViewer .page .loadingIcon {
+  position: absolute;
+  display: block;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: url("../images/pdf.js-viewer/loading-icon.gif") center no-repeat; }
+
+.pdfjs .pdfViewer .page .annotLink > a:hover {
+  opacity: .2;
+  background: #ff0;
+  box-shadow: 0 2px 10px #ff0; }
+
+.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0; }
+
+.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0; }
+
+.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
+  margin-bottom: 100% !important;
+  border: 0; }
+
+.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0; }
+
+.pdfjs .pdfViewer .page .annotText > img {
+  position: absolute;
+  cursor: pointer; }
+
+.pdfjs .pdfViewer .page .annotTextContentWrapper {
+  position: absolute;
+  width: 20em; }
+
+.pdfjs .pdfViewer .page .annotTextContent {
+  z-index: 200;
+  float: left;
+  max-width: 20em;
+  background-color: #FF9;
+  box-shadow: 0 2px 5px #333;
+  border-radius: 2px;
+  padding: .6em;
+  cursor: pointer; }
+
+.pdfjs .pdfViewer .page .annotTextContent > h1 {
+  font-size: 1em;
+  border-bottom: 1px solid #000;
+  padding-bottom: 0.2em; }
+
+.pdfjs .pdfViewer .page .annotTextContent > p {
+  padding-top: 0.2em; }
+
+.pdfjs .pdfViewer .page .annotLink > a {
+  position: absolute;
+  font-size: 1em;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%; }
+
+.pdfjs .pdfViewer .page .annotLink > a {
+  background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA LAAAAAABAAEAAAIBRAA7") 0 0 repeat; }
+
+.pdfjs * {
+  padding: 0;
+  margin: 0; }
+
+html {
+  height: 100%;
+  font-size: 10px; }
+
+.pdfjs input,
+.pdfjs button,
+.pdfjs select {
+  font: message-box;
+  outline: none; }
+
+.pdfjs .hidden {
+  display: none !important; }
+
+.pdfjs [hidden] {
+  display: none !important; }
+
+.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen {
+  top: 0;
+  border-top: 2px solid transparent;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -webkit-user-select: none; }
+
+.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen {
+  top: 0;
+  border-top: 2px solid transparent;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -moz-user-select: none; }
+
+.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen {
+  top: 0 !important;
+  border-top: 2px solid transparent;
+  width: 100%;
+  height: 100%;
+  overflow: hidden !important;
+  cursor: none;
+  -ms-user-select: none; }
+
+.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop {
+  background-color: #000; }
+
+.pdfjs #viewerContainer.pdfPresentationMode:fullscreen {
+  top: 0;
+  border-top: 2px solid transparent;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none; }
+
+.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) {
+  display: none; }
+
+.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) {
+  display: none; }
+
+.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) {
+  display: none !important; }
+
+.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) {
+  display: none; }
+
+.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div {
+  cursor: none; }
+
+.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div {
+  cursor: none; }
+
+.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div {
+  cursor: none; }
+
+.pdfjs .pdfPresentationMode:fullscreen .textLayer > div {
+  cursor: none; }
+
+.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *,
+.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
+  cursor: default; }
+
+.pdfjs .outerCenter {
+  pointer-events: none;
+  position: relative; }
+
+html[dir='ltr'] .pdfjs .outerCenter {
+  float: right;
+  right: 50%; }
+
+html[dir='rtl'] .pdfjs .outerCenter {
+  float: left;
+  left: 50%; }
+
+.pdfjs .innerCenter {
+  pointer-events: auto;
+  position: relative; }
+
+html[dir='ltr'] .pdfjs .innerCenter {
+  float: right;
+  right: -50%; }
+
+html[dir='rtl'] .pdfjs .innerCenter {
+  float: left;
+  left: -50%; }
+
+.pdfjs #outerContainer {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  background-color: #404040;
+  background-image: url("../images/pdf.js-viewer/texture.png"); }
+
+.pdfjs #sidebarContainer {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  width: 200px;
+  visibility: hidden;
+  -webkit-transition-duration: 200ms;
+  -webkit-transition-timing-function: ease;
+  transition-duration: 200ms;
+  transition-timing-function: ease; }
+
+html[dir='ltr'] .pdfjs #sidebarContainer {
+  -webkit-transition-property: left;
+  transition-property: left;
+  left: -200px; }
+
+html[dir='rtl'] .pdfjs #sidebarContainer {
+  -webkit-transition-property: right;
+  transition-property: right;
+  right: -200px; }
+
+.pdfjs #outerContainer.sidebarMoving > #sidebarContainer,
+.pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
+  visibility: visible; }
+
+html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
+  left: 0; }
+
+html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
+  right: 0; }
+
+.pdfjs #mainContainer {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  min-width: 320px;
+  -webkit-transition-duration: 200ms;
+  -webkit-transition-timing-function: ease;
+  transition-duration: 200ms;
+  transition-timing-function: ease; }
+
+html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+  -webkit-transition-property: left;
+  transition-property: left;
+  left: 200px; }
+
+html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+  -webkit-transition-property: right;
+  transition-property: right;
+  right: 200px; }
+
+.pdfjs #sidebarContent {
+  top: 32px;
+  bottom: 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  position: absolute;
+  width: 200px;
+  background-color: rgba(0, 0, 0, 0.1); }
+
+html[dir='ltr'] .pdfjs #sidebarContent {
+  left: 0;
+  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25); }
+
+html[dir='rtl'] .pdfjs #sidebarContent {
+  right: 0;
+  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25); }
+
+.pdfjs #viewerContainer {
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  position: absolute;
+  top: 32px;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  outline: none; }
+
+html[dir='ltr'] .pdfjs #viewerContainer {
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05); }
+
+html[dir='rtl'] .pdfjs #viewerContainer {
+  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05); }
+
+.pdfjs .toolbar {
+  position: relative;
+  left: 0;
+  right: 0;
+  cursor: default; }
+
+.pdfjs #toolbarContainer {
+  width: 100%; }
+
+.pdfjs #toolbarSidebar {
+  width: 200px;
+  height: 32px;
+  background-color: #424242;
+  background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95)); }
+
+html[dir='ltr'] .pdfjs #toolbarSidebar {
+  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); }
+
+html[dir='rtl'] .pdfjs #toolbarSidebar {
+  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); }
+
+.pdfjs #toolbarContainer,
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  position: relative;
+  height: 32px;
+  background-color: #474747;
+  background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); }
+
+html[dir='ltr'] .pdfjs #toolbarContainer,
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); }
+
+html[dir='rtl'] .pdfjs #toolbarContainer,
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); }
+
+.pdfjs #toolbarViewer {
+  height: 32px; }
+
+.pdfjs #loadingBar {
+  position: relative;
+  width: 100%;
+  height: 4px;
+  background-color: #333;
+  border-bottom: 1px solid #333; }
+
+.pdfjs #loadingBar .progress {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 0;
+  height: 100%;
+  background-color: #ddd;
+  overflow: hidden;
+  -webkit-transition: width 200ms;
+  transition: width 200ms; }
+
+@-webkit-keyframes progressIndeterminate {
+  0% {
+    left: 0; }
+  50% {
+    left: 100%; }
+  100% {
+    left: 100%; } }
+
+@keyframes progressIndeterminate {
+  0% {
+    left: 0; }
+  50% {
+    left: 100%; }
+  100% {
+    left: 100%; } }
+
+.pdfjs #loadingBar .progress.indeterminate {
+  background-color: #999;
+  -webkit-transition: none;
+  transition: none; }
+
+.pdfjs #loadingBar .indeterminate .glimmer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 50px;
+  background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  -webkit-animation: progressIndeterminate 2s linear infinite;
+  animation: progressIndeterminate 2s linear infinite; }
+
+.pdfjs .findbar,
+.pdfjs .secondaryToolbar {
+  top: 32px;
+  position: absolute;
+  z-index: 10000;
+  height: 32px;
+  min-width: 16px;
+  padding: 0 6px;
+  margin: 4px 2px;
+  color: #d9d9d9;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: left;
+  cursor: default; }
+
+html[dir='ltr'] .pdfjs .findbar {
+  left: 68px; }
+
+html[dir='rtl'] .pdfjs .findbar {
+  right: 68px; }
+
+.pdfjs .findbar label {
+  -webkit-user-select: none;
+  -moz-user-select: none; }
+
+.pdfjs #findInput[data-status="pending"] {
+  background-image: url("../images/pdf.js-viewer/loading-small.png");
+  background-repeat: no-repeat;
+  background-position: right; }
+
+html[dir='rtl'] .pdfjs #findInput[data-status="pending"] {
+  background-position: left; }
+
+.pdfjs .secondaryToolbar {
+  padding: 6px;
+  height: auto;
+  z-index: 30000; }
+
+html[dir='ltr'] .pdfjs .secondaryToolbar {
+  right: 4px; }
+
+html[dir='rtl'] .pdfjs .secondaryToolbar {
+  left: 4px; }
+
+.pdfjs #secondaryToolbarButtonContainer {
+  max-width: 200px;
+  max-height: 400px;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+  margin-bottom: -4px; }
+
+.pdfjs .doorHanger,
+.pdfjs .doorHangerRight {
+  border: 1px solid rgba(0, 0, 0, 0.5);
+  border-radius: 2px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); }
+
+.pdfjs .doorHanger:after,
+.pdfjs .doorHanger:before,
+.pdfjs .doorHangerRight:after,
+.pdfjs .doorHangerRight:before {
+  bottom: 100%;
+  border: solid transparent;
+  content: " ";
+  height: 0;
+  width: 0;
+  position: absolute;
+  pointer-events: none; }
+
+.pdfjs .doorHanger:after,
+.pdfjs .doorHangerRight:after {
+  border-bottom-color: rgba(82, 82, 82, 0.99);
+  border-width: 8px; }
+
+.pdfjs .doorHanger:before,
+.pdfjs .doorHangerRight:before {
+  border-bottom-color: rgba(0, 0, 0, 0.5);
+  border-width: 9px; }
+
+html[dir='ltr'] .pdfjs .doorHanger:after,
+html[dir='rtl'] .pdfjs .doorHangerRight:after {
+  left: 13px;
+  margin-left: -8px; }
+
+html[dir='ltr'] .pdfjs .doorHanger:before,
+html[dir='rtl'] .pdfjs .doorHangerRight:before {
+  left: 13px;
+  margin-left: -9px; }
+
+html[dir='rtl'] .pdfjs .doorHanger:after,
+html[dir='ltr'] .pdfjs .doorHangerRight:after {
+  right: 13px;
+  margin-right: -8px; }
+
+html[dir='rtl'] .pdfjs .doorHanger:before,
+html[dir='ltr'] .pdfjs .doorHangerRight:before {
+  right: 13px;
+  margin-right: -9px; }
+
+.pdfjs #findMsg {
+  font-style: italic;
+  color: #A6B7D0; }
+
+.pdfjs #findInput.notFound {
+  background-color: #f66; }
+
+html[dir='ltr'] .pdfjs #toolbarViewerLeft {
+  margin-left: -1px; }
+
+html[dir='rtl'] .pdfjs #toolbarViewerRight {
+  margin-right: -1px; }
+
+html[dir='ltr'] .pdfjs #toolbarViewerLeft,
+html[dir='rtl'] .pdfjs #toolbarViewerRight {
+  position: absolute;
+  top: 0;
+  left: 0; }
+
+html[dir='ltr'] .pdfjs #toolbarViewerRight,
+html[dir='rtl'] .pdfjs #toolbarViewerLeft {
+  position: absolute;
+  top: 0;
+  right: 0; }
+
+html[dir='ltr'] .pdfjs #toolbarViewerLeft > *,
+html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *,
+html[dir='ltr'] .pdfjs #toolbarViewerRight > *,
+html[dir='ltr'] .pdfjs .findbar > * {
+  position: relative;
+  float: left; }
+
+html[dir='rtl'] .pdfjs #toolbarViewerLeft > *,
+html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *,
+html[dir='rtl'] .pdfjs #toolbarViewerRight > *,
+html[dir='rtl'] .pdfjs .findbar > * {
+  position: relative;
+  float: right; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButton {
+  margin: 3px 2px 4px 0;
+  display: inline-block; }
+
+html[dir='rtl'] .pdfjs .splitToolbarButton {
+  margin: 3px 0 4px 2px;
+  display: inline-block; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton {
+  border-radius: 0;
+  float: left; }
+
+html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton {
+  border-radius: 0;
+  float: right; }
+
+.pdfjs .toolbarButton,
+.pdfjs .secondaryToolbarButton,
+.pdfjs .overlayButton {
+  border: 0 none;
+  background: none;
+  width: 32px;
+  height: 25px; }
+
+.pdfjs .toolbarButton > span {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  overflow: hidden; }
+
+.pdfjs .toolbarButton[disabled],
+.pdfjs .secondaryToolbarButton[disabled],
+.pdfjs .overlayButton[disabled] {
+  opacity: 0.5; }
+
+.pdfjs .toolbarButton.group {
+  margin-right: 0; }
+
+.pdfjs .splitToolbarButton.toggled .toolbarButton {
+  margin: 0; }
+
+.pdfjs .splitToolbarButton:hover > .toolbarButton,
+.pdfjs .splitToolbarButton:focus > .toolbarButton,
+.pdfjs .splitToolbarButton.toggled > .toolbarButton,
+.pdfjs .toolbarButton.textButton {
+  background-color: rgba(0, 0, 0, 0.12);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.35);
+  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 150ms;
+  -webkit-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease; }
+
+.pdfjs .splitToolbarButton > .toolbarButton:hover,
+.pdfjs .splitToolbarButton > .toolbarButton:focus,
+.pdfjs .dropdownToolbarButton:hover,
+.pdfjs .overlayButton:hover,
+.pdfjs .toolbarButton.textButton:hover,
+.pdfjs .toolbarButton.textButton:focus {
+  background-color: rgba(0, 0, 0, 0.2);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05);
+  z-index: 199; }
+
+.pdfjs .splitToolbarButton > .toolbarButton {
+  position: relative; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child {
+  position: relative;
+  margin: 0;
+  margin-right: -1px;
+  border-top-left-radius: 2px;
+  border-bottom-left-radius: 2px;
+  border-right-color: transparent; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child {
+  position: relative;
+  margin: 0;
+  margin-left: -1px;
+  border-top-right-radius: 2px;
+  border-bottom-right-radius: 2px;
+  border-left-color: transparent; }
+
+.pdfjs .splitToolbarButtonSeparator {
+  padding: 8px 0;
+  width: 1px;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 99;
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
+  display: inline-block;
+  margin: 5px 0; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator {
+  float: left; }
+
+html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator {
+  float: right; }
+
+.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator,
+.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator {
+  padding: 12px 0;
+  margin: 1px 0;
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03);
+  -webkit-transition-property: padding;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: ease;
+  transition-property: padding;
+  transition-duration: 10ms;
+  transition-timing-function: ease; }
+
+.pdfjs .toolbarButton,
+.pdfjs .dropdownToolbarButton,
+.pdfjs .secondaryToolbarButton,
+.pdfjs .overlayButton {
+  min-width: 16px;
+  padding: 2px 6px 0;
+  border: 1px solid transparent;
+  border-radius: 2px;
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 12px;
+  line-height: 14px;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  cursor: default;
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 150ms;
+  -webkit-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease; }
+
+html[dir='ltr'] .pdfjs .toolbarButton,
+html[dir='ltr'] .pdfjs .overlayButton,
+html[dir='ltr'] .pdfjs .dropdownToolbarButton {
+  margin: 3px 2px 4px 0; }
+
+html[dir='rtl'] .pdfjs .toolbarButton,
+html[dir='rtl'] .pdfjs .overlayButton,
+html[dir='rtl'] .pdfjs .dropdownToolbarButton {
+  margin: 3px 0 4px 2px; }
+
+.pdfjs .toolbarButton:hover,
+.pdfjs .toolbarButton:focus,
+.pdfjs .dropdownToolbarButton,
+.pdfjs .overlayButton,
+.pdfjs .secondaryToolbarButton:hover,
+.pdfjs .secondaryToolbarButton:focus {
+  background-color: rgba(0, 0, 0, 0.12);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.35);
+  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); }
+
+.pdfjs .toolbarButton:hover:active,
+.pdfjs .overlayButton:hover:active,
+.pdfjs .dropdownToolbarButton:hover:active,
+.pdfjs .secondaryToolbarButton:hover:active {
+  background-color: rgba(0, 0, 0, 0.2);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: linear;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 10ms;
+  transition-timing-function: linear; }
+
+.pdfjs .toolbarButton.toggled,
+.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled,
+.pdfjs .secondaryToolbarButton.toggled {
+  background-color: rgba(0, 0, 0, 0.3);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: linear;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 10ms;
+  transition-timing-function: linear; }
+
+.pdfjs .toolbarButton.toggled:hover:active,
+.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active,
+.pdfjs .secondaryToolbarButton.toggled:hover:active {
+  background-color: rgba(0, 0, 0, 0.4);
+  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05); }
+
+.pdfjs .dropdownToolbarButton {
+  width: 120px;
+  max-width: 120px;
+  padding: 0;
+  overflow: hidden;
+  background: url("../images/pdf.js-viewer/toolbarButton-menuArrows.png") no-repeat; }
+
+html[dir='ltr'] .pdfjs .dropdownToolbarButton {
+  background-position: 95%; }
+
+html[dir='rtl'] .pdfjs .dropdownToolbarButton {
+  background-position: 5%; }
+
+.pdfjs .dropdownToolbarButton > select {
+  min-width: 140px;
+  font-size: 12px;
+  color: #f2f2f2;
+  margin: 0;
+  padding: 3px 2px 2px;
+  border: none;
+  background: rgba(0, 0, 0, 0); }
+
+.pdfjs .dropdownToolbarButton > select > option {
+  background: #3d3d3d; }
+
+.pdfjs #customScaleOption {
+  display: none; }
+
+.pdfjs #pageWidthOption {
+  border-bottom: 1px rgba(255, 255, 255, 0.5) solid; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButton:first-child,
+html[dir='ltr'] .pdfjs .toolbarButton:first-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton:last-child,
+html[dir='rtl'] .pdfjs .toolbarButton:last-child {
+  margin-left: 4px; }
+
+html[dir='ltr'] .pdfjs .splitToolbarButton:last-child,
+html[dir='ltr'] .pdfjs .toolbarButton:last-child,
+html[dir='rtl'] .pdfjs .splitToolbarButton:first-child,
+html[dir='rtl'] .pdfjs .toolbarButton:first-child {
+  margin-right: 4px; }
+
+.pdfjs .toolbarButtonSpacer {
+  width: 30px;
+  display: inline-block;
+  height: 1px; }
+
+.pdfjs .toolbarButtonFlexibleSpacer {
+  -webkit-box-flex: 1;
+  -moz-box-flex: 1;
+  min-width: 30px; }
+
+html[dir='ltr'] .pdfjs #findPrevious {
+  margin-left: 3px; }
+
+html[dir='ltr'] .pdfjs #findNext {
+  margin-right: 3px; }
+
+html[dir='rtl'] .pdfjs #findPrevious {
+  margin-right: 3px; }
+
+html[dir='rtl'] .pdfjs #findNext {
+  margin-left: 3px; }
+
+.pdfjs .toolbarButton::before,
+.pdfjs .secondaryToolbarButton::before {
+  position: absolute;
+  display: inline-block;
+  top: 4px;
+  left: 7px; }
+
+html[dir="ltr"] .pdfjs .secondaryToolbarButton::before {
+  left: 4px; }
+
+html[dir="rtl"] .pdfjs .secondaryToolbarButton::before {
+  right: 4px; }
+
+html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle.png"); }
+
+html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png"); }
+
+html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png"); }
+
+html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png"); }
+
+html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
+  content: url("../images/pdf.js-viewer/findbarButton-previous.png"); }
+
+html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
+  content: url("../images/pdf.js-viewer/findbarButton-previous-rtl.png"); }
+
+html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
+  content: url("../images/pdf.js-viewer/findbarButton-next.png"); }
+
+html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
+  content: url("../images/pdf.js-viewer/findbarButton-next-rtl.png"); }
+
+html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-pageUp.png"); }
+
+html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png"); }
+
+html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-pageDown.png"); }
+
+html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png"); }
+
+.pdfjs .toolbarButton.zoomOut::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-zoomOut.png"); }
+
+.pdfjs .toolbarButton.zoomIn::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-zoomIn.png"); }
+
+.pdfjs .toolbarButton.presentationMode::before,
+.pdfjs .secondaryToolbarButton.presentationMode::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-presentationMode.png"); }
+
+.pdfjs .toolbarButton.print::before,
+.pdfjs .secondaryToolbarButton.print::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-print.png"); }
+
+.pdfjs .toolbarButton.openFile::before,
+.pdfjs .secondaryToolbarButton.openFile::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-openFile.png"); }
+
+.pdfjs .toolbarButton.download::before,
+.pdfjs .secondaryToolbarButton.download::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-download.png"); }
+
+.pdfjs .toolbarButton.bookmark,
+.pdfjs .secondaryToolbarButton.bookmark {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  outline: none;
+  padding-top: 4px;
+  text-decoration: none; }
+
+.pdfjs .secondaryToolbarButton.bookmark {
+  padding-top: 5px; }
+
+.pdfjs .bookmark[href='#'] {
+  opacity: .5;
+  pointer-events: none; }
+
+.pdfjs .toolbarButton.bookmark::before,
+.pdfjs .secondaryToolbarButton.bookmark::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-bookmark.png"); }
+
+.pdfjs #viewThumbnail.toolbarButton::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail.png"); }
+
+html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-viewOutline.png"); }
+
+html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png"); }
+
+.pdfjs #viewAttachments.toolbarButton::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments.png"); }
+
+.pdfjs #viewFind.toolbarButton::before {
+  content: url("../images/pdf.js-viewer/toolbarButton-search.png"); }
+
+.pdfjs .secondaryToolbarButton {
+  position: relative;
+  margin: 0 0 4px;
+  padding: 3px 0 1px;
+  height: auto;
+  min-height: 25px;
+  width: auto;
+  min-width: 100%;
+  white-space: normal; }
+
+html[dir="ltr"] .pdfjs .secondaryToolbarButton {
+  padding-left: 24px;
+  text-align: left; }
+
+html[dir="rtl"] .pdfjs .secondaryToolbarButton {
+  padding-right: 24px;
+  text-align: right; }
+
+html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark {
+  padding-left: 27px; }
+
+html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark {
+  padding-right: 27px; }
+
+html[dir="ltr"] .pdfjs .secondaryToolbarButton > span {
+  padding-right: 4px; }
+
+html[dir="rtl"] .pdfjs .secondaryToolbarButton > span {
+  padding-left: 4px; }
+
+.pdfjs .secondaryToolbarButton.firstPage::before {
+  content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png"); }
+
+.pdfjs .secondaryToolbarButton.lastPage::before {
+  content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png"); }
+
+.pdfjs .secondaryToolbarButton.rotateCcw::before {
+  content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png"); }
+
+.pdfjs .secondaryToolbarButton.rotateCw::before {
+  content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png"); }
+
+.pdfjs .secondaryToolbarButton.handTool::before {
+  content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool.png"); }
+
+.pdfjs .secondaryToolbarButton.documentProperties::before {
+  content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png"); }
+
+.pdfjs .verticalToolbarSeparator {
+  display: block;
+  padding: 8px 0;
+  margin: 8px 4px;
+  width: 1px;
+  background-color: rgba(0, 0, 0, 0.5);
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }
+
+html[dir='ltr'] .pdfjs .verticalToolbarSeparator {
+  margin-left: 2px; }
+
+html[dir='rtl'] .pdfjs .verticalToolbarSeparator {
+  margin-right: 2px; }
+
+.pdfjs .horizontalToolbarSeparator {
+  display: block;
+  margin: 0 0 4px;
+  height: 1px;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }
+
+.pdfjs .toolbarField {
+  padding: 3px 6px;
+  margin: 4px 0;
+  border: 1px solid transparent;
+  border-radius: 2px;
+  background-color: rgba(255, 255, 255, 0.09);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.35);
+  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
+  color: #f2f2f2;
+  font-size: 12px;
+  line-height: 14px;
+  outline-style: none;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease; }
+
+.pdfjs .toolbarField[type=checkbox] {
+  display: inline-block;
+  margin: 8px 0; }
+
+.pdfjs .toolbarField.pageNumber {
+  -moz-appearance: textfield;
+  min-width: 16px;
+  text-align: right;
+  width: 40px; }
+
+.pdfjs .toolbarField.pageNumber.visiblePageIsLoading {
+  background-image: url("../images/pdf.js-viewer/loading-small.png");
+  background-repeat: no-repeat;
+  background-position: 1px; }
+
+.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button,
+.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button {
+  -webkit-appearance: none;
+  margin: 0; }
+
+.pdfjs .toolbarField:hover {
+  background-color: rgba(255, 255, 255, 0.11);
+  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45); }
+
+.pdfjs .toolbarField:focus {
+  background-color: rgba(255, 255, 255, 0.15);
+  border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9); }
+
+.pdfjs .toolbarLabel {
+  min-width: 16px;
+  padding: 3px 6px 3px 2px;
+  margin: 4px 2px 4px 0;
+  border: 1px solid transparent;
+  border-radius: 2px;
+  color: #d9d9d9;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: left;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  cursor: default; }
+
+.pdfjs #thumbnailView {
+  position: absolute;
+  width: 120px;
+  top: 0;
+  bottom: 0;
+  padding: 10px 40px 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch; }
+
+.pdfjs .thumbnail {
+  float: left;
+  margin-bottom: 5px; }
+
+.pdfjs #thumbnailView > a:last-of-type > .thumbnail {
+  margin-bottom: 10px; }
+
+.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) {
+  margin-bottom: 9px; }
+
+.pdfjs .thumbnail:not([data-loaded]) {
+  border: 1px dashed rgba(255, 255, 255, 0.5);
+  margin: -1px -1px 4px; }
+
+.pdfjs .thumbnailImage {
+  border: 1px solid transparent;
+  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
+  opacity: .8;
+  z-index: 99;
+  background-color: #fff;
+  background-clip: content-box; }
+
+.pdfjs .thumbnailSelectionRing {
+  border-radius: 2px;
+  padding: 7px; }
+
+.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
+.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
+  opacity: 0.9; }
+
+.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing,
+.pdfjs .thumbnail:hover > .thumbnailSelectionRing {
+  background-color: rgba(255, 255, 255, 0.15);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: rgba(255, 255, 255, 0.9); }
+
+.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
+  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
+  opacity: 1; }
+
+.pdfjs .thumbnail.selected > .thumbnailSelectionRing {
+  background-color: rgba(255, 255, 255, 0.3);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: #ffffff; }
+
+.pdfjs #outlineView,
+.pdfjs #attachmentsView {
+  position: absolute;
+  width: 192px;
+  top: 0;
+  bottom: 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  -webkit-user-select: none;
+  -moz-user-select: none; }
+
+.pdfjs #outlineView {
+  padding: 4px 4px 0; }
+
+.pdfjs #attachmentsView {
+  padding: 3px 4px 0; }
+
+html[dir='ltr'] .pdfjs .outlineItem > .outlineItems {
+  margin-left: 20px; }
+
+html[dir='rtl'] .pdfjs .outlineItem > .outlineItems {
+  margin-right: 20px; }
+
+.pdfjs .outlineItem > a,
+.pdfjs .attachmentsItem > button {
+  text-decoration: none;
+  display: inline-block;
+  min-width: 95%;
+  height: auto;
+  margin-bottom: 1px;
+  border-radius: 2px;
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 13px;
+  line-height: 15px;
+  -moz-user-select: none;
+  white-space: normal; }
+
+.pdfjs .attachmentsItem > button {
+  border: 0 none;
+  background: none;
+  cursor: pointer;
+  width: 100%; }
+
+html[dir='ltr'] .pdfjs .outlineItem > a {
+  padding: 2px 0 5px 10px; }
+
+html[dir='ltr'] .pdfjs .attachmentsItem > button {
+  padding: 2px 0 3px 7px;
+  text-align: left; }
+
+html[dir='rtl'] .pdfjs .outlineItem > a {
+  padding: 2px 10px 5px 0; }
+
+html[dir='rtl'] .pdfjs .attachmentsItem > button {
+  padding: 2px 7px 3px 0;
+  text-align: right; }
+
+.pdfjs .outlineItem > a:hover,
+.pdfjs .attachmentsItem > button:hover {
+  background-color: rgba(255, 255, 255, 0.02);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: rgba(255, 255, 255, 0.9); }
+
+.pdfjs .outlineItem.selected {
+  background-color: rgba(255, 255, 255, 0.08);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
+  background-clip: padding-box;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
+  color: #ffffff; }
+
+.pdfjs .noResults {
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.8);
+  font-style: italic;
+  cursor: default; }
+
+.pdfjs ::selection {
+  background: rgba(0, 0, 255, 0.3); }
+
+.pdfjs ::-moz-selection {
+  background: rgba(0, 0, 255, 0.3); }
+
+.pdfjs #errorWrapper {
+  background: none repeat scroll 0 0 #F55;
+  color: #fff;
+  left: 0;
+  position: absolute;
+  right: 0;
+  z-index: 1000;
+  padding: 3px;
+  font-size: 0.8em; }
+
+.pdfjs .loadingInProgress #errorWrapper {
+  top: 37px; }
+
+.pdfjs #errorMessageLeft {
+  float: left; }
+
+.pdfjs #errorMessageRight {
+  float: right; }
+
+.pdfjs #errorMoreInfo {
+  background-color: #FFF;
+  color: #000;
+  padding: 3px;
+  margin: 3px;
+  width: 98%; }
+
+.pdfjs .overlayButton {
+  width: auto;
+  margin: 3px 4px 2px !important;
+  padding: 2px 6px 3px; }
+
+.pdfjs #overlayContainer {
+  display: table;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.2);
+  z-index: 40000; }
+
+.pdfjs #overlayContainer > * {
+  overflow: auto;
+  -webkit-overflow-scrolling: touch; }
+
+.pdfjs #overlayContainer > .container {
+  display: table-cell;
+  vertical-align: middle;
+  text-align: center; }
+
+.pdfjs #overlayContainer > .container > .dialog {
+  display: inline-block;
+  padding: 15px;
+  border-spacing: 4px;
+  color: #d9d9d9;
+  font-size: 12px;
+  line-height: 14px;
+  background-color: #474747;
+  background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95));
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
+  border: 1px solid rgba(0, 0, 0, 0.5);
+  border-radius: 4px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); }
+
+.pdfjs .dialog > .row {
+  display: table-row; }
+
+.pdfjs .dialog > .row > * {
+  display: table-cell; }
+
+.pdfjs .dialog .toolbarField {
+  margin: 5px 0; }
+
+.pdfjs .dialog .separator {
+  display: block;
+  margin: 4px 0;
+  height: 1px;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }
+
+.pdfjs .dialog .buttonRow {
+  text-align: center;
+  vertical-align: middle; }
+
+.pdfjs #passwordOverlay > .dialog {
+  text-align: center; }
+
+.pdfjs #passwordOverlay .toolbarField {
+  width: 200px; }
+
+.pdfjs #documentPropertiesOverlay > .dialog {
+  text-align: left; }
+
+.pdfjs #documentPropertiesOverlay .row > * {
+  min-width: 100px; }
+
+html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * {
+  text-align: left; }
+
+html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * {
+  text-align: right; }
+
+.pdfjs #documentPropertiesOverlay .row > span {
+  width: 125px;
+  word-wrap: break-word; }
+
+.pdfjs #documentPropertiesOverlay .row > p {
+  max-width: 225px;
+  word-wrap: break-word; }
+
+.pdfjs #documentPropertiesOverlay .buttonRow {
+  margin-top: 10px; }
+
+.pdfjs .clearBoth {
+  clear: both; }
+
+.pdfjs .fileInput {
+  background: #fff;
+  color: #000;
+  margin-top: 5px;
+  visibility: hidden;
+  position: fixed;
+  right: 0;
+  top: 0; }
+
+.pdfjs #PDFBug {
+  background: none repeat scroll 0 0 #fff;
+  border: 1px solid #666;
+  position: fixed;
+  top: 32px;
+  right: 0;
+  bottom: 0;
+  font-size: 10px;
+  padding: 0;
+  width: 300px; }
+
+.pdfjs #PDFBug .controls {
+  background: #EEE;
+  border-bottom: 1px solid #666;
+  padding: 3px; }
+
+.pdfjs #PDFBug .panels {
+  bottom: 0;
+  left: 0;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  position: absolute;
+  right: 0;
+  top: 27px; }
+
+.pdfjs #PDFBug button.active {
+  font-weight: 700; }
+
+.pdfjs .debuggerShowText {
+  background: none repeat scroll 0 0 #ff0;
+  color: blue; }
+
+.pdfjs .debuggerHideText:hover {
+  background: none repeat scroll 0 0 #ff0; }
+
+.pdfjs #PDFBug .stats {
+  font-family: courier;
+  font-size: 10px;
+  white-space: pre; }
+
+.pdfjs #PDFBug .stats .title {
+  font-weight: 700; }
+
+.pdfjs #PDFBug table {
+  font-size: 10px; }
+
+.pdfjs #viewer.textLayer-visible .textLayer > div,
+.pdfjs #viewer.textLayer-hover .textLayer > div:hover {
+  background-color: #fff;
+  color: #000; }
+
+.pdfjs #viewer.textLayer-shadow .textLayer > div {
+  background-color: rgba(255, 255, 255, 0.6);
+  color: #000; }
+
+.pdfjs .grab-to-pan-grab {
+  cursor: url("../images/pdf.js-viewer/grab.cur"), move !important;
+  cursor: -webkit-grab !important;
+  cursor: -moz-grab !important;
+  cursor: grab !important; }
+
+.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) {
+  cursor: inherit !important; }
+
+.pdfjs .grab-to-pan-grab:active,
+.pdfjs .grab-to-pan-grabbing {
+  cursor: url("../images/pdf.js-viewer/grabbing.cur"), move !important;
+  cursor: -webkit-grabbing !important;
+  cursor: -moz-grabbing !important;
+  cursor: grabbing !important;
+  position: fixed;
+  background: transparent;
+  display: block;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+  z-index: 50000; }
+
+@page {
+  margin: 0; }
+
+.pdfjs #printContainer {
+  display: none; }
+
+@media screen and (min-resolution: 2dppx) {
+  .pdfjs .toolbarButton::before {
+    -webkit-transform: scale(0.5);
+    transform: scale(0.5);
+    top: -5px; }
+  .pdfjs .secondaryToolbarButton::before {
+    -webkit-transform: scale(0.5);
+    transform: scale(0.5);
+    top: -4px; }
+  html[dir='ltr'] .pdfjs .toolbarButton::before,
+  html[dir='rtl'] .pdfjs .toolbarButton::before {
+    left: -1px; }
+  html[dir="ltr"] .pdfjs .secondaryToolbarButton::before {
+    left: -2px; }
+  html[dir="rtl"] .pdfjs .secondaryToolbarButton::before {
+    left: 186px; }
+  .pdfjs .toolbarField.pageNumber.visiblePageIsLoading,
+  .pdfjs #findInput[data-status="pending"] {
+    background-image: url("../images/pdf.js-viewer/loading-small@2x.png");
+    background-size: 16px 17px; }
+  .pdfjs .dropdownToolbarButton {
+    background: url("../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png") no-repeat;
+    background-size: 7px 16px; }
+  html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png"); }
+  html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png"); }
+  html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png"); }
+  html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png"); }
+  html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
+    content: url("../images/pdf.js-viewer/findbarButton-previous@2x.png"); }
+  html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
+    content: url("../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png"); }
+  html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
+    content: url("../images/pdf.js-viewer/findbarButton-next@2x.png"); }
+  html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
+    content: url("../images/pdf.js-viewer/findbarButton-next-rtl@2x.png"); }
+  html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-pageUp@2x.png"); }
+  html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png"); }
+  html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-pageDown@2x.png"); }
+  html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png"); }
+  .pdfjs .toolbarButton.zoomIn::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png"); }
+  .pdfjs .toolbarButton.zoomOut::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png"); }
+  .pdfjs .toolbarButton.presentationMode::before,
+  .pdfjs .secondaryToolbarButton.presentationMode::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png"); }
+  .pdfjs .toolbarButton.print::before,
+  .pdfjs .secondaryToolbarButton.print::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-print@2x.png"); }
+  .pdfjs .toolbarButton.openFile::before,
+  .pdfjs .secondaryToolbarButton.openFile::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-openFile@2x.png"); }
+  .pdfjs .toolbarButton.download::before,
+  .pdfjs .secondaryToolbarButton.download::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-download@2x.png"); }
+  .pdfjs .toolbarButton.bookmark::before,
+  .pdfjs .secondaryToolbarButton.bookmark::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-bookmark@2x.png"); }
+  .pdfjs #viewThumbnail.toolbarButton::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png"); }
+  html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png"); }
+  html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png"); }
+  .pdfjs #viewAttachments.toolbarButton::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png"); }
+  .pdfjs #viewFind.toolbarButton::before {
+    content: url("../images/pdf.js-viewer/toolbarButton-search@2x.png"); }
+  .pdfjs .secondaryToolbarButton.firstPage::before {
+    content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png"); }
+  .pdfjs .secondaryToolbarButton.lastPage::before {
+    content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png"); }
+  .pdfjs .secondaryToolbarButton.rotateCcw::before {
+    content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png"); }
+  .pdfjs .secondaryToolbarButton.rotateCw::before {
+    content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png"); }
+  .pdfjs .secondaryToolbarButton.handTool::before {
+    content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png"); }
+  .pdfjs .secondaryToolbarButton.documentProperties::before {
+    content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png"); } }
+
+@media print {
+  body {
+    background: transparent none; }
+  .pdfjs #sidebarContainer,
+  .pdfjs #secondaryToolbar,
+  .pdfjs .toolbar,
+  .pdfjs #loadingBox,
+  .pdfjs #errorWrapper,
+  .pdfjs .textLayer {
+    display: none; }
+  .pdfjs #viewerContainer {
+    overflow: visible; }
+  .pdfjs #mainContainer,
+  .pdfjs #viewerContainer,
+  .pdfjs .page,
+  .pdfjs .page canvas {
+    position: static;
+    padding: 0;
+    margin: 0; }
+  .pdfjs .page {
+    float: left;
+    display: none;
+    border: none;
+    box-shadow: none;
+    background-clip: content-box;
+    background-color: #fff; }
+  .pdfjs .page[data-loaded] {
+    display: block; }
+  .pdfjs .fileInput {
+    display: none; }
+  body[data-mozPrintCallback] .pdfjs #outerContainer {
+    display: none; }
+  body[data-mozPrintCallback] .pdfjs #printContainer {
+    display: block; }
+  .pdfjs #printContainer > div {
+    position: relative;
+    top: 0;
+    left: 0;
+    overflow: hidden; }
+  .pdfjs #printContainer canvas {
+    display: block; } }
+
+.pdfjs .visibleLargeView,
+.pdfjs .visibleMediumView,
+.pdfjs .visibleSmallView {
+  display: none; }
+
+@media all and (max-width: 960px) {
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
+    float: left;
+    left: 205px; }
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
+    float: right;
+    right: 205px; } }
+
+@media all and (max-width: 900px) {
+  .pdfjs .sidebarOpen .hiddenLargeView {
+    display: none; }
+  .pdfjs .sidebarOpen .visibleLargeView {
+    display: inherit; } }
+
+@media all and (max-width: 860px) {
+  .pdfjs .sidebarOpen .hiddenMediumView {
+    display: none; }
+  .pdfjs .sidebarOpen .visibleMediumView {
+    display: inherit; } }
+
+@media all and (max-width: 770px) {
+  .pdfjs #sidebarContainer {
+    top: 32px;
+    z-index: 100; }
+  .pdfjs .loadingInProgress #sidebarContainer {
+    top: 37px; }
+  .pdfjs #sidebarContent {
+    top: 32px;
+    background-color: rgba(0, 0, 0, 0.7); }
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+    left: 0; }
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
+    right: 0; }
+  html[dir='ltr'] .pdfjs .outerCenter {
+    float: left;
+    left: 205px; }
+  html[dir='rtl'] .pdfjs .outerCenter {
+    float: right;
+    right: 205px; }
+  .pdfjs #outerContainer .hiddenLargeView,
+  .pdfjs #outerContainer .hiddenMediumView {
+    display: inherit; }
+  .pdfjs #outerContainer .visibleLargeView,
+  .pdfjs #outerContainer .visibleMediumView {
+    display: none; } }
+
+@media all and (max-width: 700px) {
+  .pdfjs #outerContainer .hiddenLargeView {
+    display: none; }
+  .pdfjs #outerContainer .visibleLargeView {
+    display: inherit; } }
+
+@media all and (max-width: 660px) {
+  .pdfjs #outerContainer .hiddenMediumView {
+    display: none; }
+  .pdfjs #outerContainer .visibleMediumView {
+    display: inherit; } }
+
+@media all and (max-width: 600px) {
+  .pdfjs .hiddenSmallView {
+    display: none; }
+  .pdfjs .visibleSmallView {
+    display: inherit; }
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
+  html[dir='ltr'] .pdfjs .outerCenter {
+    left: 156px; }
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
+  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
+  html[dir='rtl'] .pdfjs .outerCenter {
+    right: 156px; }
+  .pdfjs .toolbarButtonSpacer {
+    width: 0; } }
+
+@media all and (max-width: 510px) {
+  .pdfjs #scaleSelectContainer,
+  .pdfjs #pageNumberLabel {
+    display: none; } }
+
+/* should be hidden differently */
+#fileInput.fileInput {
+  display: none; }
+
+.wvSplitpane {
+  height: 100%;
+  padding: 7px 2px 2px 2px;
+  position: relative; }
+
+.wvSplitpane__cell {
+  display: inline-block;
+  float: left;
+  height: 100%;
+  width: 100%;
+  position: relative; }
+
+.wvSplitpane__cellBorder, .wvSplitpane__cellBorder--selected, .wvSplitpane__cellBorder--blue, .wvSplitpane__cellBorder--red, .wvSplitpane__cellBorder--green, .wvSplitpane__cellBorder--yellow, .wvSplitpane__cellBorder--violet {
+  display: inline-block;
+  float: left;
+  height: calc(100% - 2px);
+  width: calc(100% - 2px);
+  border: 2px dashed transparent;
+  padding: 2px;
+  margin: 1px; }
+
+.wvSplitpane__cellBorder--selected {
+  border: 2px solid rgba(51, 152, 219, 0.7); }
+
+.wvSplitpane__cellBorder--blue {
+  border-color: rgba(51, 152, 219, 0.7); }
+
+.wvSplitpane__cellBorder--red {
+  border-color: rgba(206, 0, 0, 0.7); }
+
+.wvSplitpane__cellBorder--green {
+  border-color: rgba(0, 160, 27, 0.7); }
+
+.wvSplitpane__cellBorder--yellow {
+  border-color: rgba(220, 200, 0, 0.9); }
+
+.wvSplitpane__cellBorder--violet {
+  border-color: rgba(255, 31, 255, 0.7); }
+
+wv-pane-policy {
+  display: block;
+  width: 100%;
+  height: 100%; }
+  wv-pane-policy > div[ng-transclude] {
+    display: block;
+    width: 100%;
+    height: 100%; }
+
+.wv-timeline {
+  position: relative;
+  height: 2em; }
+  .wv-timeline.reduced {
+    height: 5px; }
+    .wv-timeline.reduced .wv-timeline-loading-bar-wrapper {
+      width: 100%;
+      height: 100%; }
+
+.wv-timeline-controls-wrapper {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 16em;
+  height: 100%;
+  color: white; }
+
+.wv-timeline-loading-bar-wrapper {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  width: calc(100% - 16em);
+  height: calc(100% + 2px); }
+  .wv-timeline-loading-bar-wrapper svg {
+    position: absolute;
+    left: 0;
+    top: 0; }
+
+/* wv-timeline-controls directive */
+.wv-timeline-controls {
+  padding: 0.5em 0.5em 0.5em 0.5em;
+  line-height: 1em;
+  background-color: rgba(0, 0, 0, 0.66);
+  text-align: center;
+  transition: color 500ms, background-color 500ms; }
+
+.wv-timeline-controls:hover {
+  background-color: rgba(0, 0, 0, 0.9); }
+
+.wv-timeline-controls-vertical-sizing {
+  display: inline-block;
+  line-height: 1em;
+  font-size: 1em; }
+
+.wv-timeline-controls-vflip:before, .wv-timeline-controls-vflip:after {
+  transform: scaleX(-1);
+  display: inline-block; }
+
+.wv-timeline-controls-button {
+  display: inline-block;
+  height: 1em;
+  width: 1em;
+  line-height: 1em;
+  font-size: 1em;
+  margin: 0;
+  user-select: none;
+  cursor: pointer; }
+
+.wv-timeline-controls-input {
+  height: 1em;
+  width: 3em;
+  padding: 0;
+  padding-bottom: 1px;
+  box-sizing: content-box;
+  border: none;
+  border-bottom: 1px solid rgba(255, 202, 128, 0.24);
+  background-color: transparent;
+  text-align: right; }
+
+.wv-timeline-controls-play-button-wrapper {
+  float: right; }
+
+/* wv-play-button directive */
+.wv-play-button {
+  display: inline-block;
+  position: relative;
+  line-height: 1em;
+  height: 3em;
+  width: 6em;
+  padding-bottom: 1em;
+  padding-left: 0.25em;
+  padding-right: 0.25em; }
+
+.wv-play-button:hover .wv-play-button-config-position-handler {
+  visibility: visible; }
+
+.wv-play-button-config-position-handler {
+  visibility: hidden;
+  position: absolute;
+  bottom: 3em;
+  left: 1em;
+  right: 0.5em; }
+
+.wv-play-button-config {
+  position: absolute;
+  bottom: 0;
+  left: -6em;
+  width: 12em;
+  padding: 1em;
+  background-color: rgba(0, 0, 0, 0.5); }
+
+/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */
+.wv-play-button-config-framerate-wrapper {
+  display: inline-block;
+  margin: 0.25em 0 0.5em 0; }
+
+input[type="range"].wv-play-button-config-framerate {
+  /*removes default webkit styles*/
+  -webkit-appearance: none;
+  /*fix for FF unable to apply focus style bug */
+  border: 1px solid white;
+  /*required for proper track sizing in FF*/
+  width: 10em; }
+
+input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track {
+  width: 10em;
+  height: 5px;
+  background: #ddd;
+  border: none;
+  border-radius: 3px; }
+
+input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb {
+  -webkit-appearance: none;
+  border: none;
+  height: 16px;
+  width: 16px;
+  border-radius: 50%;
+  background: goldenrod;
+  margin-top: -4px; }
+
+input[type="range"].wv-play-button-config-framerate:focus {
+  outline: none; }
+
+input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track {
+  background: #ccc; }
+
+input[type="range"].wv-play-button-config-framerate::-moz-range-track {
+  width: 10em;
+  height: 5px;
+  background: #ddd;
+  border: none;
+  border-radius: 3px; }
+
+input[type="range"].wv-play-button-config-framerate::-moz-range-thumb {
+  border: none;
+  height: 16px;
+  width: 16px;
+  border-radius: 50%;
+  background: goldenrod; }
+
+/*hide the outline behind the border*/
+input[type="range"].wv-play-button-config-framerate:-moz-focusring {
+  outline: 1px solid white;
+  outline-offset: -1px; }
+
+input[type="range"].wv-play-button-config-framerate::-ms-track {
+  width: 10em;
+  height: 5px;
+  /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
+  background: transparent;
+  /*leave room for the larger thumb to overflow with a transparent border */
+  border-color: transparent;
+  border-width: 6px 0;
+  /*remove default tick marks*/
+  color: transparent; }
+
+input[type="range"].wv-play-button-config-framerate::-ms-fill-lower {
+  background: #777;
+  border-radius: 10px; }
+
+input[type="range"].wv-play-button-config-framerate::-ms-fill-upper {
+  background: #ddd;
+  border-radius: 10px; }
+
+input[type="range"].wv-play-button-config-framerate::-ms-thumb {
+  border: none;
+  height: 16px;
+  width: 16px;
+  border-radius: 50%;
+  background: goldenrod; }
+
+input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower {
+  background: #888; }
+
+input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper {
+  background: #ccc; }
+
+.wv-loadingbar-image-bar {
+  cursor: pointer; }
+
+.wv-loadingbar-not-loaded {
+  fill: rgba(255, 255, 255, 0.1); }
+
+.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality {
+  transition: none; }
+
+.wv-loadingbar-not-loaded:hover {
+  fill: rgba(255, 255, 255, 0.2); }
+
+.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality {
+  fill: rgba(0, 255, 0, 0.7); }
+
+.wv-loadingbar-LOSSLESS-quality:hover,
+.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active,
+.wv-loadingbar-PIXELDATA-quality:hover,
+.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active {
+  fill: lime; }
+
+.wv-loadingbar-LOW-quality {
+  fill: rgba(255, 0, 0, 0.7); }
+
+.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active {
+  fill: red; }
+
+.wv-loadingbar-MEDIUM-quality {
+  fill: rgba(255, 95, 0, 0.7); }
+
+.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active {
+  fill: #ff5f00; }
+
+.disclaimer {
+  color: #E63F24;
+  background-color: #303030;
+  padding: 5px;
+  text-align: center;
+  font-weight: bold; }
+
+.tbGroup {
+  position: relative; }
+
+.tbGroup__buttons--base, .tbGroup__buttons--bottom, .tbGroup__buttons--left {
+  z-index: 5;
+  background-color: black;
+  position: absolute; }
+
+.tbGroup__buttons--bottom {
+  right: 0;
+  display: block; }
+
+.tbGroup__buttons--left {
+  right: 100%;
+  top: 0;
+  display: block; }
+
+.tbGroup__icon {
+  display: block;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 0;
+  height: 0;
+  border-style: solid;
+  border-width: 10px 0 0 10px;
+  border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); }
+  .tbGroup__icon.active {
+    border-color: transparent transparent transparent #3498db; }
+
+wv-viewport {
+  display: inline-block;
+  width: 100%;
+  height: 100%; }
+  wv-viewport > div {
+    position: relative;
+    width: 100%;
+    height: 100%; }
+  wv-viewport > div > .wv-cornerstone-enabled-image {
+    width: 100%;
+    height: 100%;
+    text-align: center; }
+
+.wv-draggable-clone {
+  width: 150px;
+  height: 150px;
+  background-color: rgba(255, 255, 255, 0.25); }
+
+@media print {
+  .wvPrintExclude {
+    display: none; }
+  .wvPrintFullPage {
+    width: 100% !important;
+    height: 100% !important;
+    position: absolute !important;
+    top: 0 !important;
+    left: 0 !important;
+    display: block !important; }
+  .wvLayout__main {
+    top: 0 !important;
+    right: 0 !important;
+    left: 0 !important;
+    bottom: 0 !important; }
+  .wvPrintViewer {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center; }
+  .wvPrintViewer canvas {
+    max-width: 100% !important;
+    max-height: 100% !important;
+    margin: auto; }
+  .wv-overlay-topleft, .wv-overlay-topleft *, .wv-overlay-topright, .wv-overlay-topright *, .wv-overlay-bottomright, .wv-overlay-bottomright *, .wv-overlay-bottomleft, .wv-overlay-bottomleft * {
+    background-color: black !important;
+    -webkit-print-color-adjust: exact !important;
+    color-adjust: exact !important;
+    color: orange !important; }
+  .tooltip {
+    display: none !important; }
+  body {
+    margin: 0;
+    padding: 0;
+    position: relative;
+    width: 8.5in;
+    height: 11in; }
+    body, body * {
+      background-color: black !important;
+      -webkit-print-color-adjust: exact !important; } }
+
+.closePrintButton {
+  display: none; }
+
+body.print .wvPrintExclude {
+  display: none; }
+
+body.print .wvPrintFullPage {
+  width: 100% !important;
+  height: 100% !important;
+  position: absolute !important;
+  top: 0 !important;
+  left: 0 !important;
+  display: block !important; }
+
+body.print .wvLayout__main {
+  top: 0 !important;
+  right: 0 !important;
+  left: 0 !important;
+  bottom: 0 !important; }
+
+body.print .wvPrintViewer {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center; }
+
+body.print .wvPrintViewer canvas {
+  max-width: 100% !important;
+  max-height: 100% !important;
+  margin: auto; }
+
+body.print .wv-overlay-topleft, body.print .wv-overlay-topleft *, body.print .wv-overlay-topright, body.print .wv-overlay-topright *, body.print .wv-overlay-bottomright, body.print .wv-overlay-bottomright *, body.print .wv-overlay-bottomleft, body.print .wv-overlay-bottomleft * {
+  background-color: black !important;
+  -webkit-print-color-adjust: exact !important;
+  color-adjust: exact !important;
+  color: orange !important; }
+
+body.print .tooltip {
+  display: none !important; }
+
+body.print body {
+  margin: 0;
+  padding: 0;
+  position: relative;
+  width: 8.5in;
+  height: 11in; }
+  body.print body, body.print body * {
+    background-color: black !important;
+    -webkit-print-color-adjust: exact !important; }
+
+@media screen {
+  body.print .closePrintButton {
+    display: block;
+    position: fixed;
+    top: 0;
+    right: 0;
+    padding: 10px;
+    font-size: 24px;
+    background-color: black;
+    color: white;
+    border: none; } }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebApplication/app.js	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,614 @@
+/**
+ * Stone of Orthanc
+ * 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/>.
+ **/
+
+
+var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ];
+var SERIES_INSTANCE_UID = '0020,000e';
+var STUDY_INSTANCE_UID = '0020,000d';
+var STUDY_DESCRIPTION = '0008,1030';
+var STUDY_DATE = '0008,0020';
+
+
+function getParameterFromUrl(key) {
+  var url = window.location.search.substring(1);
+  var args = url.split('&');
+  for (var i = 0; i < args.length; i++) {
+    var arg = args[i].split('=');
+    if (arg[0] == key) {
+      return arg[1];
+    }
+  }
+}
+
+
+Vue.component('viewport', {
+  props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex',
+           'quality', 'framesCount', 'currentFrame', 'showInfo' ],
+  template: '#viewport-template',
+  data: function () {
+    return {
+      stone: stone,  // To access global object "stone" from "index.html"
+      status: 'waiting'
+    }
+  },
+  watch: { 
+    series: function(newVal, oldVal) {
+      this.status = 'loading';
+
+      var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID];
+      var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID];
+      stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid);
+      
+      if ((newVal.type == stone.ThumbnailType.IMAGE ||
+           newVal.type == stone.ThumbnailType.NO_PREVIEW) &&
+          newVal.complete) {
+        this.status = 'ready';
+
+        var that = this;
+        Vue.nextTick(function() {
+          stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid);
+        });
+      }
+      else if (newVal.type == stone.ThumbnailType.PDF ||
+               newVal.type == stone.ThumbnailType.VIDEO) {
+        // TODO
+      }
+    }
+  },
+  methods: {
+    SeriesDragAccept: function(event) {
+      event.preventDefault();
+    },
+    SeriesDragDrop: function(event) {
+      event.preventDefault();
+      this.$emit('updated-series', event.dataTransfer.getData('seriesIndex'));
+    },
+    MakeActive: function() {
+      this.$emit('selected-viewport');
+    },
+    DecrementFrame: function() {
+      stone.DecrementFrame(this.canvasId);
+    },
+    IncrementFrame: function() {
+      stone.IncrementFrame(this.canvasId);
+    }
+  }
+})
+
+
+var app = new Vue({
+  el: '#wv',
+  data: function() {
+    return {
+      stone: stone,  // To access global object "stone" from "index.html"
+      ready: false,
+      leftMode: 'grid',   // Can be 'small', 'grid' or 'full'
+      leftVisible: true,
+      showWarning: false,
+      viewportLayoutButtonsVisible: false,
+      activeViewport: 0,
+      showInfo: true,
+      showReferenceLines: true,
+      
+      viewport1Width: '100%',
+      viewport1Height: '100%',
+      viewport1Left: '0%',
+      viewport1Top: '0%',
+      viewport1Visible: true,
+      viewport1Series: {},
+      viewport1Quality: '',
+      viewport1FramesCount: 0,
+      viewport1CurrentFrame: 0,
+      
+      viewport2Width: '100%',
+      viewport2Height: '100%',
+      viewport2Left: '0%',
+      viewport2Top: '0%',
+      viewport2Visible: false,
+      viewport2Series: {},
+      viewport2Quality: '',
+      viewport2FramesCount: 0,
+      viewport2CurrentFrame: 0,
+
+      viewport3Width: '100%',
+      viewport3Height: '100%',
+      viewport3Left: '0%',
+      viewport3Top: '0%',
+      viewport3Visible: false,
+      viewport3Series: {},
+      viewport3Quality: '',
+      viewport3FramesCount: 0,
+      viewport3CurrentFrame: 0,
+
+      viewport4Width: '100%',
+      viewport4Height: '100%',
+      viewport4Left: '0%',
+      viewport4Top: '0%',
+      viewport4Visible: false,
+      viewport4Series: {},
+      viewport4Quality: '',
+      viewport4FramesCount: 0,
+      viewport4CurrentFrame: 0,
+
+      series: [],
+      studies: [],
+      seriesIndex: {}  // Maps "SeriesInstanceUID" to "index in this.series"
+    }
+  },
+  computed: {
+    getSelectedStudies() {
+      var s = '';
+      for (var i = 0; i < this.studies.length; i++) {
+        if (this.studies[i].selected) {
+          if (s.length > 0)
+            s += ', ';
+          s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' +
+                this.studies[i].tags[STUDY_DATE] + ']');
+        }
+      }
+      if (s.length == 0) 
+        return '...';
+      else
+        return s;
+    }
+  },
+  watch: { 
+    leftVisible: function(newVal, oldVal) {
+      this.FitContent();
+    },
+    showReferenceLines: function(newVal, oldVal) {
+      stone.ShowReferenceLines(newVal ? 1 : 0);
+    }
+  },
+  methods: {
+    FitContent() {
+      // This function can be used even if WebAssembly is not initialized yet
+      if (typeof stone._AllViewportsUpdateSize !== 'undefined') {
+        this.$nextTick(function () {
+          stone.AllViewportsUpdateSize(true /* fit content */);
+        });
+      }
+    },
+    
+    GetActiveSeries() {
+      var s = [];
+
+      if ('tags' in this.viewport1Series)
+        s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]);
+
+      if ('tags' in this.viewport2Series)
+        s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]);
+
+      if ('tags' in this.viewport3Series)
+        s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]);
+
+      if ('tags' in this.viewport4Series)
+        s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]);
+
+      return s;
+    },
+
+    GetActiveCanvas() {
+      if (this.activeViewport == 1) {
+        return 'canvas1';
+      }
+      else if (this.activeViewport == 2) {
+        return 'canvas2';
+      }
+      else if (this.activeViewport == 3) {
+        return 'canvas3';
+      }
+      else if (this.activeViewport == 4) {
+        return 'canvas4';
+      }
+      else {
+        return 'canvas1';
+      }
+    },
+
+    SetResources: function(sourceStudies, sourceSeries) {     
+      var indexStudies = {};
+
+      var studies = [];
+      var posColor = 0;
+
+      for (var i = 0; i < sourceStudies.length; i++) {
+        var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID];
+        if (studyInstanceUid !== undefined) {
+          if (studyInstanceUid in indexStudies) {
+            console.error('Twice the same study: ' + studyInstanceUid);
+          } else {
+            indexStudies[studyInstanceUid] = studies.length;
+            
+            studies.push({
+              'studyInstanceUid' : studyInstanceUid,
+              'series' : [ ],
+              'color' : COLORS[posColor],
+              'selected' : true,
+              'tags' : sourceStudies[i]
+            });
+
+            posColor = (posColor + 1) % COLORS.length;
+          }
+        }
+      }
+
+      var series = [];
+      var seriesIndex = {};
+
+      for (var i = 0; i < sourceSeries.length; i++) {
+        var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID];
+        var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID];
+        if (studyInstanceUid !== undefined &&
+            seriesInstanceUid !== undefined) {
+          if (studyInstanceUid in indexStudies) {
+            seriesIndex[seriesInstanceUid] = series.length;
+            var study = studies[indexStudies[studyInstanceUid]];
+            study.series.push(i);
+            series.push({
+              //'length' : 4,
+              'complete' : false,
+              'type' : stone.ThumbnailType.LOADING,
+              'color': study.color,
+              'tags': sourceSeries[i]
+            });
+          }
+        }
+      }
+      
+      this.studies = studies;
+      this.series = series;
+      this.seriesIndex = seriesIndex;
+      this.ready = true;
+    },
+    
+    SeriesDragStart: function(event, seriesIndex) {
+      event.dataTransfer.setData('seriesIndex', seriesIndex);
+    },
+    
+    SetViewportSeries: function(viewportIndex, seriesIndex) {
+      var series = this.series[seriesIndex];
+      
+      if (viewportIndex == 1) {
+        this.viewport1Series = series;
+      }
+      else if (viewportIndex == 2) {
+        this.viewport2Series = series;
+      }
+      else if (viewportIndex == 3) {
+        this.viewport3Series = series;
+      }
+      else if (viewportIndex == 4) {
+        this.viewport4Series = series;
+      }
+    },
+    
+    ClickSeries: function(seriesIndex) {
+      this.SetViewportSeries(this.activeViewport, seriesIndex);
+    },
+    
+    HideViewport: function(index) {
+      if (index == 1) {
+        this.viewport1Visible = false;
+      }
+      else if (index == 2) {
+        this.viewport2Visible = false;
+      }
+      else if (index == 3) {
+        this.viewport3Visible = false;
+      }
+      else if (index == 4) {
+        this.viewport4Visible = false;
+      }
+    },
+    
+    ShowViewport: function(index, left, top, width, height) {
+      if (index == 1) {
+        this.viewport1Visible = true;
+        this.viewport1Left = left;
+        this.viewport1Top = top;
+        this.viewport1Width = width;
+        this.viewport1Height = height;
+      }
+      else if (index == 2) {
+        this.viewport2Visible = true;
+        this.viewport2Left = left;
+        this.viewport2Top = top;
+        this.viewport2Width = width;
+        this.viewport2Height = height;
+      }
+      else if (index == 3) {
+        this.viewport3Visible = true;
+        this.viewport3Left = left;
+        this.viewport3Top = top;
+        this.viewport3Width = width;
+        this.viewport3Height = height;
+      }
+      else if (index == 4) {
+        this.viewport4Visible = true;
+        this.viewport4Left = left;
+        this.viewport4Top = top;
+        this.viewport4Width = width;
+        this.viewport4Height = height;
+      }
+    },
+    
+    SetViewportLayout: function(layout) {
+      this.viewportLayoutButtonsVisible = false;
+      if (layout == '1x1') {
+        this.ShowViewport(1, '0%', '0%', '100%', '100%');
+        this.HideViewport(2);
+        this.HideViewport(3);
+        this.HideViewport(4);
+      }
+      else if (layout == '2x2') {
+        this.ShowViewport(1, '0%', '0%', '50%', '50%');
+        this.ShowViewport(2, '50%', '0%', '50%', '50%');
+        this.ShowViewport(3, '0%', '50%', '50%', '50%');
+        this.ShowViewport(4, '50%', '50%', '50%', '50%');
+      }
+      else if (layout == '2x1') {
+        this.ShowViewport(1, '0%', '0%', '50%', '100%');
+        this.ShowViewport(2, '50%', '0%', '50%', '100%');
+        this.HideViewport(3);
+        this.HideViewport(4);
+      }
+      else if (layout == '1x2') {
+        this.ShowViewport(1, '0%', '0%', '100%', '50%');
+        this.ShowViewport(2, '0%', '50%', '100%', '50%');
+        this.HideViewport(3);
+        this.HideViewport(4);
+      }
+
+      this.FitContent();
+    },
+
+    UpdateSeriesThumbnail: function(seriesInstanceUid) {
+      if (seriesInstanceUid in this.seriesIndex) {
+        var index = this.seriesIndex[seriesInstanceUid];
+        var series = this.series[index];
+
+        var type = stone.LoadSeriesThumbnail(seriesInstanceUid);
+        series.type = type;
+
+        if (type == stone.ThumbnailType.IMAGE) {
+          series.thumbnail = stone.GetStringBuffer();
+        }
+
+        // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
+        this.$set(this.series, index, series);
+      }
+    },
+
+    UpdateIsSeriesComplete: function(seriesInstanceUid) {
+      if (seriesInstanceUid in this.seriesIndex) {
+        var index = this.seriesIndex[seriesInstanceUid];
+        var series = this.series[index];
+
+        series.complete = stone.IsSeriesComplete(seriesInstanceUid);
+
+        // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
+        this.$set(this.series, index, series);
+
+        if ('tags' in this.viewport1Series &&
+            this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
+          this.$set(this.viewport1Series, series);
+        }
+
+        if ('tags' in this.viewport2Series &&
+            this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
+          this.$set(this.viewport2Series, series);
+        }
+
+        if ('tags' in this.viewport3Series &&
+            this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
+          this.$set(this.viewport3Series, series);
+        }
+
+        if ('tags' in this.viewport4Series &&
+            this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
+          this.$set(this.viewport4Series, series);
+        }
+      }
+    },
+
+    SetWindowing(center, width) {
+      var canvas = this.GetActiveCanvas();
+      if (canvas != '') {
+        stone.SetWindowing(canvas, center, width);
+      }
+    },
+
+    SetDefaultWindowing() {
+      var canvas = this.GetActiveCanvas();
+      if (canvas != '') {
+        stone.SetDefaultWindowing(canvas);
+      }
+    },
+
+    InvertContrast() {
+      var canvas = this.GetActiveCanvas();
+      if (canvas != '') {
+        stone.InvertContrast(canvas);
+      }
+    }
+  },
+  
+  mounted: function() {
+    this.SetViewportLayout('1x1');
+  }
+});
+
+
+
+window.addEventListener('StoneInitialized', function() {
+  stone.Setup(Module);
+  stone.SetOrthancRoot('..', true);
+  console.warn('Native Stone properly intialized');
+
+  var study = getParameterFromUrl('study');
+  var series = getParameterFromUrl('series');
+
+  if (study === undefined) {
+    alert('No study was provided in the URL!');
+  } else {
+    if (series === undefined) {
+      console.warn('Loading study: ' + study);
+      stone.FetchStudy(study);
+    } else {
+      console.warn('Loading series: ' + series + ' from study ' + study);
+      stone.FetchSeries(study, series);
+      app.leftMode = 'full';
+    }
+  }
+});
+
+
+window.addEventListener('ResourcesLoaded', function() {
+  console.log('resources loaded');
+
+  var studies = [];
+  for (var i = 0; i < stone.GetStudiesCount(); i++) {
+    stone.LoadStudyTags(i);
+    studies.push(JSON.parse(stone.GetStringBuffer()));
+  }
+
+  var series = [];
+  for (var i = 0; i < stone.GetSeriesCount(); i++) {
+    stone.LoadSeriesTags(i);
+    series.push(JSON.parse(stone.GetStringBuffer()));
+  }
+
+  app.SetResources(studies, series);
+
+  for (var i = 0; i < app.series.length; i++) {
+    var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID];
+    app.UpdateSeriesThumbnail(seriesInstanceUid);
+    app.UpdateIsSeriesComplete(seriesInstanceUid);
+  }
+});
+
+
+window.addEventListener('ThumbnailLoaded', function(args) {
+  //var studyInstanceUid = args.detail.studyInstanceUid;
+  var seriesInstanceUid = args.detail.seriesInstanceUid;
+  app.UpdateSeriesThumbnail(seriesInstanceUid);
+});
+
+
+window.addEventListener('MetadataLoaded', function(args) {
+  //var studyInstanceUid = args.detail.studyInstanceUid;
+  var seriesInstanceUid = args.detail.seriesInstanceUid;
+  app.UpdateIsSeriesComplete(seriesInstanceUid);
+});
+
+
+window.addEventListener('FrameUpdated', function(args) {
+  var canvasId = args.detail.canvasId;
+  var framesCount = args.detail.framesCount;
+  var currentFrame = (args.detail.currentFrame + 1);
+  var quality = args.detail.quality;
+  
+  if (canvasId == 'canvas1') {
+    app.viewport1CurrentFrame = currentFrame;
+    app.viewport1FramesCount = framesCount;
+    app.viewport1Quality = quality;
+  }
+  else if (canvasId == 'canvas2') {
+    app.viewport2CurrentFrame = currentFrame;
+    app.viewport2FramesCount = framesCount;
+    app.viewport2Quality = quality;
+  }
+  else if (canvasId == 'canvas3') {
+    app.viewport3CurrentFrame = currentFrame;
+    app.viewport3FramesCount = framesCount;
+    app.viewport3Quality = quality;
+  }
+  else if (canvasId == 'canvas4') {
+    app.viewport4CurrentFrame = currentFrame;
+    app.viewport4FramesCount = framesCount;
+    app.viewport4Quality = quality;
+  }
+});
+
+
+window.addEventListener('StoneException', function() {
+  console.error('Exception catched in Stone');
+});
+
+
+
+
+
+
+$(document).ready(function() {
+  // Enable support for tooltips in Bootstrap
+  //$('[data-toggle="tooltip"]').tooltip();
+
+  //app.showWarning = true;
+
+
+  $('#windowing-popover').popover({
+    container: 'body',
+    content: $('#windowing-content').html(),
+    template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
+    placement: 'auto',
+    html: true,
+    sanitize: false,
+    trigger: 'focus'   // Close on click
+  });
+  
+  
+  var wasmSource = 'StoneWebViewer.js';
+  
+  // Option 1: Loading script using plain HTML
+  
+  /*
+    var script = document.createElement('script');
+    script.src = wasmSource;
+    script.type = 'text/javascript';
+    document.body.appendChild(script);
+  */
+
+  // Option 2: Loading script using AJAX (gives the opportunity to
+  // report explicit errors)
+
+  axios.get(wasmSource)
+    .then(function (response) {
+      var script = document.createElement('script');
+      script.innerHTML = response.data;
+      script.type = 'text/javascript';
+      document.body.appendChild(script);
+    })
+    .catch(function (error) {
+      alert('Cannot load the WebAssembly framework');
+    });
+});
+
+
+// "Prevent Bootstrap dropdown from closing on clicks" for the list of
+// studies: https://stackoverflow.com/questions/26639346
+$('.dropdown-menu').click(function(e) {
+  e.stopPropagation();
+});
+
+
+// Disable the selection of text using the mouse
+document.onselectstart = new Function ('return false');
Binary file Applications/StoneWebViewer/WebApplication/img/grid1x1.png has changed
Binary file Applications/StoneWebViewer/WebApplication/img/grid1x2.png has changed
Binary file Applications/StoneWebViewer/WebApplication/img/grid2x1.png has changed
Binary file Applications/StoneWebViewer/WebApplication/img/grid2x2.png has changed
Binary file Applications/StoneWebViewer/WebApplication/img/loading.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebApplication/index.html	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,488 @@
+<!doctype html>
+<html class="wv-html">
+  <head>
+    <title>Stone Web Viewer</title>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css">
+    <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
+    <link rel="stylesheet" href="app.css">
+
+    <!-- https://stackoverflow.com/a/16863182/881731 -->
+    <style>
+      .tooltip {
+      position: fixed;
+      }
+    </style>
+
+    <!-- Fix if Bootstrap CSS is not used -->
+    <!--style>
+        *,
+        *::before,
+        *::after {
+        box-sizing: border-box;
+        }
+        </style-->
+  </head>
+  <body class="wv-body">
+    <div id="wv">
+      <div class="wvLoadingScreen" v-show="!ready">
+        <span class="wvLoadingSpinner">
+          <div class="bounce1"></div>
+          <div class="bounce2"></div>
+          <div class="bounce3"></div>
+        </span>
+      </div>
+
+      <div class="fluid-height fluid-width" v-show="ready">
+
+        <div class="wvWarning wvPrintExclude" v-show="showWarning">
+          <div class="wvWarning-content clearfix">
+            <span class="wvWarning-text">
+              <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2>
+              <p class="mn mb10" style="color:#000">
+                You browser is not supported. You might expect
+                inconsistent behaviours and must not use the viewer to
+                produce a diagnostic.
+              </p>
+            </span> 
+          </div>
+          <div class="text-right mb10 mr10">
+            <button class="btn btn-primary" @click="showWarning=false">OK</button>
+          </div>
+        </div>
+
+
+        <div class="wvLayoutLeft wvLayoutLeft--closed" v-show="!leftVisible">
+          <div class="wvLayoutLeft__actions--outside" style="z-index:10">
+            <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center"
+                    @click="leftVisible = true">
+              <i class="fa fa-angle-double-right"></i>
+            </button>
+          </div>
+        </div>
+
+
+        <div class="wvLayoutLeft" v-show="leftVisible"
+             v-bind:class="{ 'wvLayoutLeft--small': leftMode == 'small' }" 
+             >
+          <div class="wvLayoutLeft__actions" style="z-index:10">
+            <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center"
+                    @click="leftVisible = false">
+              <i class="fa fa-angle-double-left"></i>
+            </button>
+          </div>
+          <div class="wvLayoutLeft__content">
+            <div class="wvLayoutLeft__contentTop">
+              <div class="float__left dropdown" style="max-width: calc(100% - 4.5rem); height:4.5rem !important" v-show="leftMode != 'small'">
+                <button type="button" class="wvButton--border" data-toggle="dropdown">
+                  {{ getSelectedStudies }}
+                  <span class="caret"></span>
+                </button>
+                <ul class="dropdown-menu checkbox-menu allow-focus">
+                  <li v-for="study in studies"
+                      v-bind:class="{ active: study.selected }" 
+                      @click="study.selected = !study.selected">
+                    <a>
+                      {{ study.tags['0008,1030'] }}
+                      <span v-if="study.selected">&nbsp;<i class="fa fa-check"></i></span>
+                    </a> 
+                  </li>
+                </ul>
+              </div>
+
+              <div class="float__right wvButton" v-if="leftMode == 'grid'" @click="leftMode = 'full'">
+                <i class="fa fa-th-list"></i>
+              </div>
+              <div class="float__right wvButton" v-if="leftMode == 'full'" @click="leftMode = 'small'">
+                <i class="fa fa-ellipsis-v"></i>
+              </div>
+              <div class="float__right wvButton" v-if="leftMode == 'small'" @click="leftMode = 'grid'">
+                <i class="fa fa-th"></i>
+              </div>
+
+              <p class="clear disclaimer mbn">For patients, teachers and researchers.</p>
+            </div>        
+            <div class="wvLayoutLeft__contentMiddle">
+
+              <div v-for="study in studies">
+                <div v-if="study.selected">
+                  <div v-bind:class="'wvStudyIsland--' + study.color">
+                    <div v-bind:class="'wvStudyIsland__header--' + study.color">
+                      <!-- Actions -->
+                      <div class="wvStudyIsland__actions"
+                           v-bind:class="{ 'wvStudyIsland__actions--oneCol': leftMode == 'small' }">
+                        <a class="wvButton">
+                          <!-- download --> 
+                          <i class="fa fa-download"></i>
+                        </a>
+                      </div>
+                      
+                      <!-- Title -->
+                      {{ study.tags['0008,1030'] }}
+                      <br/>
+                      <small>{{ study.tags['0008,0020'] }}</small>
+                    </div>
+
+                    <div class="wvStudyIsland__main">
+                      <ul class="wvSerieslist">
+                        <li class="wvSerieslist__seriesItem"
+                            v-bind:class="{ highlighted : GetActiveSeries().includes(series[seriesIndex].tags['0020,000e']), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }"
+                            v-on:dragstart="SeriesDragStart($event, seriesIndex)"
+                            v-on:click="ClickSeries(seriesIndex)"
+                            v-for="seriesIndex in study.series">
+                          <div class="wvSerieslist__picture" style="z-index:0"
+                               draggable="true"
+                               v-if="series[seriesIndex].type != stone.ThumbnailType.UNKNOWN"
+                               >
+                            <div v-if="series[seriesIndex].type == stone.ThumbnailType.LOADING">
+                              <img src="img/loading.gif"
+                                   style="vertical-align:baseline"
+                                   width="65px" height="65px"
+                                   />
+                            </div>
+
+                            <i v-if="series[seriesIndex].type == stone.ThumbnailType.PDF"
+                               class="wvSerieslist__placeholderIcon fa fa-file-text"></i>
+
+                            <i v-if="series[seriesIndex].type == stone.ThumbnailType.VIDEO"
+                               class="wvSerieslist__placeholderIcon fa fa-video-camera"></i>
+
+                            
+                            <div v-if="[stone.ThumbnailType.IMAGE, stone.ThumbnailType.NO_PREVIEW].includes(series[seriesIndex].type)"
+                                 class="wvSerieslist__placeholderIcon"
+                                 v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']">
+                              <i v-if="series[seriesIndex].type == stone.ThumbnailType.NO_PREVIEW"
+                                 class="fa fa-eye-slash"></i>
+
+                              <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE"
+                                   v-bind:src="series[seriesIndex].thumbnail"
+                                   style="vertical-align:baseline"
+                                   width="65px" height="65px"
+                                   v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']"
+                                   />
+                              
+                              <div v-bind:class="'wvSerieslist__badge--' + study.color"
+                                   v-if="'length' in series[seriesIndex]">{{ series[seriesIndex].length }}</div>
+                            </div>
+                          </div>
+
+                          <div v-if="leftMode == 'full'" class="wvSerieslist__information"
+                               draggable="true"
+                               v-on:dragstart="SeriesDragStart($event, seriesIndex)"
+                               v-on:click="ClickSeries(seriesIndex)">
+                            <p class="wvSerieslist__label">
+                              [{{ series[seriesIndex].tags['0008,0060'] }}]
+                              {{ series[seriesIndex].tags['0008,103e'] }}
+                            </p>
+                          </div>
+                        </li>
+                      </ul>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+            </div>        
+            <div class="wvLayoutLeft__contentBottom">
+            </div>        
+          </div>
+        </div>
+        <div class="wvLayout__main"
+             v-bind:class="{ 'wvLayout__main--smallleftpadding': leftVisible && leftMode == 'small', 'wvLayout__main--leftpadding': leftVisible && leftMode != 'small' }" 
+             >
+
+          <div class="wvToolbar wvToolbar--top">
+            <div class="ng-scope inline-object">
+              <div class="tbGroup">
+                <div class="tbGroup__toggl">
+                  <button class="wvButton"
+                          v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible }"
+                          @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible">
+                    <i class="fa fa-th"></i>
+                  </button>
+                </div>
+                
+                <div class="tbGroup__buttons--bottom" v-show="viewportLayoutButtonsVisible">
+                  <div class="inline-object">
+                    <button class="wvButton" @click="SetViewportLayout('1x1')">
+                      <img src="img/grid1x1.png" style="width:1em;height:1em" />
+                    </button>
+                  </div>
+                  <div class="inline-object">
+                    <button class="wvButton" @click="SetViewportLayout('2x1')">
+                      <img src="img/grid2x1.png" style="width:1em;height:1em" />
+                    </button>
+                  </div>
+                  <div class="inline-object">
+                    <button class="wvButton" @click="SetViewportLayout('1x2')">
+                      <img src="img/grid1x2.png" style="width:1em;height:1em" />
+                    </button>
+                  </div>
+                  <div class="inline-object">
+                    <button class="wvButton" @click="SetViewportLayout('2x2')">
+                      <img src="img/grid2x2.png" style="width:1em;height:1em" />
+                    </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <!--div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center active">
+                <i class="fa fa-hand-pointer-o"></i>
+              </button>
+            </div>
+
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center">
+                <i class="fa fa-search"></i>
+              </button>
+            </div>
+
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center">
+                <i class="fa fa-arrows"></i>
+              </button>
+            </div-->
+
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center"
+                      v-on:click="InvertContrast()">
+                <i class="fa fa-adjust"></i>
+              </button>
+            </div>
+            
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center" id="windowing-popover">
+                <i class="fa fa-sun-o"></i>
+              </button>
+            </div>
+
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center"
+                      v-bind:class="{ 'active' : showInfo }"
+                      v-on:click="showInfo = !showInfo">
+                <i class="fa fa-info-circle"></i>
+              </button>
+            </div>
+
+            <div class="ng-scope inline-object">
+              <button class="wvButton--underline text-center"
+                      v-bind:class="{ 'active' : showReferenceLines }"
+                      v-on:click="showReferenceLines = !showReferenceLines">
+                <i class="fa fa-bars"></i>
+              </button>
+            </div>
+          </div>
+
+          
+          <div class="wvLayout__splitpane--toolbarAtTop">
+            <div id="viewport" class="wvSplitpane">
+              <viewport v-on:updated-series="SetViewportSeries(1, $event)"
+                        v-on:selected-viewport="activeViewport=1"
+                        v-show="viewport1Visible"
+                        canvas-id="canvas1"
+                        v-bind:series="viewport1Series"
+                        v-bind:left="viewport1Left"
+                        v-bind:top="viewport1Top"
+                        v-bind:width="viewport1Width"
+                        v-bind:height="viewport1Height"
+                        v-bind:quality="viewport1Quality"
+                        v-bind:current-frame="viewport1CurrentFrame"
+                        v-bind:frames-count="viewport1FramesCount"
+                        v-bind:show-info="showInfo"
+                        v-bind:active="activeViewport==1"></viewport>
+              <viewport v-on:updated-series="SetViewportSeries(2, $event)"
+                        v-on:selected-viewport="activeViewport=2"
+                        v-show="viewport2Visible"
+                        canvas-id="canvas2"
+                        v-bind:series="viewport2Series"
+                        v-bind:left="viewport2Left"
+                        v-bind:top="viewport2Top"
+                        v-bind:width="viewport2Width"
+                        v-bind:height="viewport2Height"
+                        v-bind:quality="viewport2Quality"
+                        v-bind:current-frame="viewport2CurrentFrame"
+                        v-bind:frames-count="viewport2FramesCount"
+                        v-bind:show-info="showInfo"
+                        v-bind:active="activeViewport==2"></viewport>
+              <viewport v-on:updated-series="SetViewportSeries(3, $event)"
+                        v-on:selected-viewport="activeViewport=3"
+                        v-show="viewport3Visible"
+                        canvas-id="canvas3"
+                        v-bind:series="viewport3Series"
+                        v-bind:left="viewport3Left"
+                        v-bind:top="viewport3Top"
+                        v-bind:width="viewport3Width"
+                        v-bind:height="viewport3Height"
+                        v-bind:quality="viewport3Quality"
+                        v-bind:current-frame="viewport3CurrentFrame"
+                        v-bind:frames-count="viewport3FramesCount"
+                        v-bind:show-info="showInfo"
+                        v-bind:active="activeViewport==3"></viewport>
+              <viewport v-on:updated-series="SetViewportSeries(4, $event)"
+                        v-on:selected-viewport="activeViewport=4"
+                        v-show="viewport4Visible"
+                        canvas-id="canvas4"
+                        v-bind:series="viewport4Series"
+                        v-bind:left="viewport4Left"
+                        v-bind:top="viewport4Top"
+                        v-bind:width="viewport4Width"
+                        v-bind:height="viewport4Height"
+                        v-bind:quality="viewport4Quality"
+                        v-bind:current-frame="viewport4CurrentFrame"
+                        v-bind:frames-count="viewport4FramesCount"
+                        v-bind:show-info="showInfo"
+                        v-bind:active="activeViewport==4"></viewport>
+            </div>
+          </div>
+
+        </div>
+      </div>
+    </div>
+
+
+
+    <script type="text/x-template" id="windowing-content">
+      <p class="wvToolbar__windowingPresetConfigNotice">
+        Click on the button to toggle the windowing tool or apply a preset to the selected viewport.
+      </p>
+
+      <ul class="wvToolbar__windowingPresetList">
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetDefaultWindowing()">
+            Default
+          </a>
+        </li>
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetWindowing(-400, 1600)">
+            CT Lung <small>(L -400, W 1,600)</small>
+          </a>
+        </li>
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetWindowing(300, 1500)">
+            CT Abdomen <small>(L 300, W 1,500)</small>
+          </a>
+        </li>
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetWindowing(40, 80)">
+            CT Bone <small>(L 40, W 80)</small>
+          </a>
+        </li>
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetWindowing(40, 400)">
+            CT Brain <small>(L 40, W 400)</small>
+          </a>
+        </li>
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetWindowing(-400, 1600)">
+            CT Chest <small>(L -400, W 1,600)</small>
+          </a>
+        </li>
+        <li class="wvToolbar__windowingPresetListItem">
+          <a href="#" onclick="app.SetWindowing(300, 600)">
+            CT Angio <small>(L 300, W 600)</small>
+          </a>
+        </li>
+      </ul>
+    </script>
+    
+
+    <script type="text/x-template" id="viewport-template">
+      <div v-bind:style="{ padding:'2px', 
+                         position:'absolute', 
+                         left: left, 
+                         top: top,
+                         width: width, 
+                         height: height }">
+        <div v-bind:class="{ 'wvSplitpane__cellBorder--selected' : active, 
+                           'wvSplitpane__cellBorder' : series.color == '', 
+                           'wvSplitpane__cellBorder--blue' : series.color == 'blue', 
+                           'wvSplitpane__cellBorder--red' : series.color == 'red',
+                           'wvSplitpane__cellBorder--green' : series.color == 'green', 
+                           'wvSplitpane__cellBorder--yellow' : series.color == 'yellow', 
+                           'wvSplitpane__cellBorder--violet' : series.color == 'violet'
+                           }" 
+             v-on:dragover="SeriesDragAccept($event)"
+             v-on:drop="SeriesDragDrop($event)"
+             style="width:100%;height:100%">
+          <div class="wvSplitpane__cell"
+               v-on:click="MakeActive()">
+            <div v-show="status == 'ready'"
+                 style="position:absolute; left:0; top:0; width:100%; height:100%">
+              <!--div style="width: 100%; height: 100%; background-color: red"></div-->
+              <canvas v-bind:id="canvasId"
+                      style="position:absolute; left:0; top:0; width:100%; height:100%"
+                      oncontextmenu="return false"></canvas>
+
+              <div v-if="'tags' in series" v-show="showInfo">
+                <div class="wv-overlay">
+                  <div class="wv-overlay-topleft">
+                    {{ series.tags['0010,0010'] }}<br/>
+                    {{ series.tags['0010,0020'] }}
+                  </div>
+                  <div class="wv-overlay-topright">
+                    {{ series.tags['0008,1030'] }}<br/>
+                    {{ series.tags['0008,0020'] }}<br/>
+                    {{ series.tags['0020,0011'] }} | {{ series.tags['0008,103e'] }}
+                  </div>
+                  <div class="wv-overlay-bottomleft"
+                       v-show="framesCount != 0">
+                    <button class="btn btn-primary" @click="DecrementFrame()">
+                      <i class="fa fa-chevron-circle-left"></i>
+                    </button>
+                    &nbsp;&nbsp;{{ currentFrame }} / {{ framesCount }}&nbsp;&nbsp;
+                    <button class="btn btn-primary" @click="IncrementFrame()">
+                      <i class="fa fa-chevron-circle-right"></i>
+                    </button>
+                  </div>
+                  <div class="wv-overlay-bottomright">
+                    <div v-show="quality == stone.DisplayedFrameQuality.NONE"
+                         style="display:block;background-color:red;width:1em;height:1em" />
+                    <div v-show="quality == stone.DisplayedFrameQuality.LOW"
+                         style="display:block;background-color:orange;width:1em;height:1em" />
+                    <div v-show="quality == stone.DisplayedFrameQuality.HIGH"
+                         style="display:block;background-color:green;width:1em;height:1em" />
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div v-if="status == 'waiting'" class="wvPaneOverlay">
+              [ drop a series here ]
+            </div>
+                
+            <!--div v-if="status == 'video'" class="wvPaneOverlay">
+              <video class="wvVideo" autoplay="" loop="" controls="" preload="auto" type="video/mp4"
+                     src="http://viewer-pro.osimis.io/instances/e465dd27-83c96343-96848735-7035a133-1facf1a0/frames/0/raw">
+              </video>
+            </div-->
+                
+            <div v-if="status == 'loading'" class="wvPaneOverlay">
+              <span class="wvLoadingSpinner">
+                <div class="bounce1"></div>
+                <div class="bounce2"></div>
+                <div class="bounce3"></div>
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </script>
+
+
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
+    
+    <script src="stone.js"></script>
+    <script src="app.js"></script>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,129 @@
+cmake_minimum_required(VERSION 2.8.3)
+
+project(OrthancStone)
+
+# Configuration of the Emscripten compiler for WebAssembly target
+# ---------------------------------------------------------------
+set(USE_WASM ON CACHE BOOL "")
+
+set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
+
+set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
+endif()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
+add_definitions(
+  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
+)
+
+# Stone of Orthanc configuration
+# ---------------------------------------------------------------
+set(ALLOW_DOWNLOADS ON)
+
+include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
+
+SET(ENABLE_DCMTK ON)
+SET(ENABLE_DCMTK_NETWORKING OFF)
+SET(ENABLE_DCMTK_TRANSCODING OFF)
+SET(ENABLE_GOOGLE_TEST OFF)
+SET(ENABLE_LOCALE ON)  # Necessary for text rendering
+SET(ENABLE_WASM ON)
+SET(ORTHANC_SANDBOXED ON)
+
+# this will set up the build system for Stone of Orthanc and will
+# populate the ORTHANC_STONE_SOURCES CMake variable
+include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
+
+if (CMAKE_BUILD_TYPE MATCHES Debug)
+  # specific flags go here
+elseif (CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
+  # specific flags go here
+elseif (CMAKE_BUILD_TYPE MATCHES Release)
+  # specific flags go here
+else()
+  message(FATAL_ERROR "CMAKE_BUILD_TYPE must match either Debug, RelWithDebInfo or Release" )
+endif()
+
+################################################################################
+
+project(StoneWebViewer)
+
+
+# Create the wrapper to call C++ from JavaScript
+# ---------------------------------------------------------------
+
+set(LIBCLANG "libclang-4.0.so.1" CACHE PATH "Version of clang to generate the code model")
+set(STONE_WRAPPER ${CMAKE_CURRENT_BINARY_DIR}/stone.js)
+
+add_custom_command(
+  COMMAND
+  ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py --libclang=${LIBCLANG} ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp > ${STONE_WRAPPER}
+  DEPENDS
+  ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp
+  ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py
+  OUTPUT
+  ${STONE_WRAPPER}
+  )
+
+add_custom_target(StoneWrapper
+  DEPENDS
+  ${STONE_WRAPPER}
+  )  
+
+
+# Define the WASM module
+# ---------------------------------------------------------------
+
+add_executable(StoneWebViewer
+  ${ORTHANC_STONE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  StoneWebViewer.cpp
+  )
+
+# Make sure to have the wrapper generated
+add_dependencies(StoneWebViewer StoneWrapper)
+
+
+# Declare installation files for the module
+# ---------------------------------------------------------------
+
+install(
+  TARGETS StoneWebViewer
+  RUNTIME DESTINATION .
+  )
+
+
+# Declare installation files for the companion files (web scaffolding)
+# please note that ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.js
+# (the generated JS loader for the WASM module) is handled by the `install`
+# section above: it is considered to be the binary output of 
+# the linker.
+# ---------------------------------------------------------------
+install(
+  FILES
+  ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.wasm
+  ${CMAKE_SOURCE_DIR}/../WebApplication/app.css
+  ${CMAKE_SOURCE_DIR}/../WebApplication/app.js
+  ${CMAKE_SOURCE_DIR}/../WebApplication/index.html
+  ${STONE_WRAPPER}
+  DESTINATION .
+  )
+
+install(
+  FILES
+  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x1.png
+  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x2.png
+  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x1.png
+  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x2.png
+  ${CMAKE_SOURCE_DIR}/../WebApplication/img/loading.gif
+  DESTINATION img
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/NOTES.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,33 @@
+
+Building WebAssembly samples using Docker
+=========================================
+
+The script "./docker-build.sh" can be used to quickly build the
+WebAssembly samples on any GNU/Linux distribution equipped with
+Docker. This avoids newcomers to install Emscripten and learn the
+CMake options. Just type:
+
+$ ./docker-build.sh Release
+
+After successful build, the binaries will be installed in the
+following folder (i.e. in the folder "wasm-binaries" at the root of
+the source distribution):
+
+$ ls -l ../../wasm-binaries
+
+
+NB: The source code of the Docker build environment can be found at
+the following location:
+https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder
+
+
+Native compilation (without Docker)
+===================================
+
+Install Emscripten:
+https://emscripten.org/docs/getting_started/downloads.html
+
+Then, if the installation path is "~/Downloads/emsdk/":
+
+# source ~/Downloads/emsdk/emsdk_env.sh
+# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+
+# Stone of Orthanc
+# 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/>.
+
+
+# Ubuntu 20.04:
+# sudo apt-get install python-clang-6.0
+# ./ParseWebAssemblyExports.py --libclang=libclang-6.0.so.1 ./Test.cpp
+
+# Ubuntu 18.04:
+# sudo apt-get install python-clang-4.0
+# ./ParseWebAssemblyExports.py --libclang=libclang-4.0.so.1 ./Test.cpp
+
+# Ubuntu 14.04:
+# ./ParseWebAssemblyExports.py --libclang=libclang-3.6.so.1 ./Test.cpp
+
+
+import sys
+import clang.cindex
+import pystache
+import argparse
+
+##
+## Parse the command-line arguments
+##
+
+parser = argparse.ArgumentParser(description = 'Parse WebAssembly C++ source file, and create a basic JavaScript wrapper.')
+parser.add_argument('--libclang',
+                    default = '',
+                    help = 'manually provides the path to the libclang shared library')
+parser.add_argument('source', 
+                    help = 'Input C++ file')
+
+args = parser.parse_args()
+
+
+
+if len(args.libclang) != 0:
+    clang.cindex.Config.set_library_file(args.libclang)
+
+index = clang.cindex.Index.create()
+
+# PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of
+# undefined types, which prevents compilation of functions
+tu = index.parse(args.source,
+                 [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))' ],
+                 options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
+
+
+
+TEMPLATE = '''
+const Stone = function() {
+  {{#enumerations}}
+  this.{{name}} = {
+    {{#values}}
+    {{name}}: {{value}}{{separator}}
+    {{/values}}
+  };
+
+  {{/enumerations}}
+  {{#functions}}
+  this._{{name}} = undefined;
+  {{/functions}}
+};
+
+Stone.prototype.Setup = function(Module) {
+  {{#functions}}
+  this._{{name}} = Module.cwrap('{{name}}', {{{returnType}}}, [ {{#args}}{{{type}}}{{^last}}, {{/last}}{{/args}} ]);
+  {{/functions}}
+};
+
+{{#functions}}
+Stone.prototype.{{name}} = function({{#args}}{{name}}{{^last}}, {{/last}}{{/args}}) {
+  {{#hasReturn}}return {{/hasReturn}}this._{{name}}({{#args}}{{name}}{{^last}}, {{/last}}{{/args}});
+};
+
+{{/functions}}
+var stone = new Stone();
+'''
+
+
+
+
+# WARNING: Undefined types are mapped as "int"
+
+functions = []
+enumerations = []
+
+def ToUpperCase(source):
+    target = source[0]
+    for c in source[1:]:
+        if c.isupper():
+            target += '_'
+        target += c.upper()
+    return target
+    
+
+
+def IsExported(node):
+    for child in node.get_children():
+        if (child.kind == clang.cindex.CursorKind.ANNOTATE_ATTR and
+            child.displayname == 'WebAssembly'):
+            return True
+
+    return False
+
+
+def Explore(node):
+    if node.kind == clang.cindex.CursorKind.ENUM_DECL:
+        if IsExported(node):
+            name = node.spelling
+            values = []
+            for value in node.get_children():
+                if (value.spelling.startswith(name + '_')):
+                    s = value.spelling[len(name) + 1:]
+                    if len(values) > 0:
+                        values[-1]['separator'] = ','
+                    values.append({
+                        'name' : ToUpperCase(s),
+                        'value' : value.enum_value
+                    })
+                    
+            enumerations.append({
+                'name' : name,
+                'values' : values
+                })
+    
+    if node.kind == clang.cindex.CursorKind.FUNCTION_DECL:
+        if IsExported(node):
+            f = {
+                'name' : node.spelling,
+                'args' : [],
+            }
+
+            returnType = node.result_type.spelling
+            if returnType == 'void':
+                f['hasReturn'] = False
+                f['returnType'] = "null"
+            elif returnType == 'const char *':
+                f['hasReturn'] = True
+                f['returnType'] = "'string'"
+            elif returnType in [ 'int', 'unsigned int' ]:
+                f['hasReturn'] = True
+                f['returnType'] = "'int'"
+            else:
+                raise Exception('Unknown return type in function "%s()": %s' % (node.spelling, returnType))
+
+            for child in node.get_children():
+                if child.kind == clang.cindex.CursorKind.PARM_DECL:
+                    arg = {
+                        'name' : child.displayname,
+                    }
+                    
+                    argType = child.type.spelling
+                    if argType == 'int':
+                        arg['type'] = "'int'"
+                    elif argType == 'const char *':
+                        arg['type'] = "'string'"
+                    else:
+                        raise Exception('Unknown type for argument "%s" in function "%s()": %s' %
+                                        (child.displayname, node.spelling, argType))
+
+                    f['args'].append(arg)
+
+            if len(f['args']) != 0:
+                f['args'][-1]['last'] = True
+                    
+            functions.append(f)
+
+    for child in node.get_children():
+        Explore(child)
+
+Explore(tu.cursor)
+
+
+
+print(pystache.render(TEMPLATE, {
+    'functions' : functions,
+    'enumerations' : enumerations
+}))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,2266 @@
+/**
+ * Stone of Orthanc
+ * 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 <emscripten.h>
+
+
+#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
+  EM_ASM(                                                       \
+    const customEvent = document.createEvent("CustomEvent");    \
+    customEvent.initCustomEvent(name, false, false, undefined); \
+    window.dispatchEvent(customEvent);                          \
+    );
+
+
+#define EXTERN_CATCH_EXCEPTIONS                         \
+  catch (Orthanc::OrthancException& e)                  \
+  {                                                     \
+    LOG(ERROR) << "OrthancException: " << e.What();     \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (OrthancStone::StoneException& e)               \
+  {                                                     \
+    LOG(ERROR) << "StoneException: " << e.What();       \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (std::exception& e)                             \
+  {                                                     \
+    LOG(ERROR) << "Runtime error: " << e.what();        \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (...)                                           \
+  {                                                     \
+    LOG(ERROR) << "Native exception";                   \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }
+
+
+#include <Cache/MemoryObjectCache.h>
+#include <DicomFormat/DicomArray.h>
+#include <DicomParsing/Internals/DicomImageDecoder.h>
+#include <Images/Image.h>
+#include <Images/ImageProcessing.h>
+#include <Images/JpegReader.h>
+#include <Logging.h>
+
+#include "../../OrthancStone/Sources/Loaders/DicomResourcesLoader.h"
+#include "../../OrthancStone/Sources/Loaders/SeriesMetadataLoader.h"
+#include "../../OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h"
+#include "../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h"
+#include "../../OrthancStone/Sources/Messages/ObserverBase.h"
+#include "../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h"
+#include "../../OrthancStone/Sources/Scene2D/ColorTextureSceneLayer.h"
+#include "../../OrthancStone/Sources/Scene2D/FloatTextureSceneLayer.h"
+#include "../../OrthancStone/Sources/Scene2D/PolylineSceneLayer.h"
+#include "../../OrthancStone/Sources/StoneException.h"
+#include "../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h"
+#include "../../OrthancStone/Sources/Toolbox/GeometryToolbox.h"
+#include "../../OrthancStone/Sources/Toolbox/SortedFrames.h"
+#include "../../OrthancStone/Sources/Viewport/WebGLViewport.h"
+
+#include <boost/make_shared.hpp>
+#include <stdio.h>
+
+
+enum EMSCRIPTEN_KEEPALIVE ThumbnailType
+{
+  ThumbnailType_Image,
+    ThumbnailType_NoPreview,
+    ThumbnailType_Pdf,
+    ThumbnailType_Video,
+    ThumbnailType_Loading,
+    ThumbnailType_Unknown
+};
+
+
+enum EMSCRIPTEN_KEEPALIVE DisplayedFrameQuality
+{
+DisplayedFrameQuality_None,
+  DisplayedFrameQuality_Low,
+  DisplayedFrameQuality_High
+  };
+  
+
+
+static const int PRIORITY_HIGH = -100;
+static const int PRIORITY_LOW = 100;
+static const int PRIORITY_NORMAL = 0;
+
+static const unsigned int QUALITY_JPEG = 0;
+static const unsigned int QUALITY_FULL = 1;
+
+class ResourcesLoader : public OrthancStone::ObserverBase<ResourcesLoader>
+{
+public:
+  class IObserver : public boost::noncopyable
+  {
+  public:
+    virtual ~IObserver()
+    {
+    }
+
+    virtual void SignalResourcesLoaded() = 0;
+
+    virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid,
+                                             const std::string& seriesInstanceUid) = 0;
+
+    virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid,
+                                            const std::string& seriesInstanceUid) = 0;
+  };
+  
+private:
+  std::unique_ptr<IObserver>                               observer_;
+  OrthancStone::DicomSource                                source_;
+  size_t                                                   pending_;
+  boost::shared_ptr<OrthancStone::LoadedDicomResources>    studies_;
+  boost::shared_ptr<OrthancStone::LoadedDicomResources>    series_;
+  boost::shared_ptr<OrthancStone::DicomResourcesLoader>    resourcesLoader_;
+  boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader>  thumbnailsLoader_;
+  boost::shared_ptr<OrthancStone::SeriesMetadataLoader>    metadataLoader_;
+
+  ResourcesLoader(const OrthancStone::DicomSource& source) :
+    source_(source),
+    pending_(0),
+    studies_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID)),
+    series_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID))
+  {
+  }
+
+  void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message)
+  {
+    const Orthanc::SingleValueObject<Orthanc::ResourceType>& payload =
+      dynamic_cast<const Orthanc::SingleValueObject<Orthanc::ResourceType>&>(message.GetUserPayload());
+    
+    OrthancStone::LoadedDicomResources& dicom = *message.GetResources();
+    
+    LOG(INFO) << "resources loaded: " << dicom.GetSize()
+              << ", " << Orthanc::EnumerationToString(payload.GetValue());
+
+    if (payload.GetValue() == Orthanc::ResourceType_Series)
+    {
+      for (size_t i = 0; i < dicom.GetSize(); i++)
+      {
+        std::string studyInstanceUid, seriesInstanceUid;
+        if (dicom.GetResource(i).LookupStringValue(
+              studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
+            dicom.GetResource(i).LookupStringValue(
+              seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
+        {
+          thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid);
+          metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid);
+        }
+      }
+    }
+
+    if (pending_ == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      pending_ --;
+      if (pending_ == 0 &&
+          observer_.get() != NULL)
+      {
+        observer_->SignalResourcesLoaded();
+      }
+    }
+  }
+
+  void Handle(const OrthancStone::SeriesThumbnailsLoader::SuccessMessage& message)
+  {
+    if (observer_.get() != NULL)
+    {
+      observer_->SignalSeriesThumbnailLoaded(
+        message.GetStudyInstanceUid(), message.GetSeriesInstanceUid());
+    }
+  }
+
+  void Handle(const OrthancStone::SeriesMetadataLoader::SuccessMessage& message)
+  {
+    if (observer_.get() != NULL)
+    {
+      observer_->SignalSeriesMetadataLoaded(
+        message.GetStudyInstanceUid(), message.GetSeriesInstanceUid());
+    }
+  }
+
+  void FetchInternal(const std::string& studyInstanceUid,
+                     const std::string& seriesInstanceUid)
+  {
+    // Firstly, load the study
+    Orthanc::DicomMap filter;
+    filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
+
+    std::set<Orthanc::DicomTag> tags;
+    tags.insert(Orthanc::DICOM_TAG_STUDY_DESCRIPTION);  // Necessary for Orthanc DICOMweb plugin
+
+    resourcesLoader_->ScheduleQido(
+      studies_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Study, filter, tags,
+      new Orthanc::SingleValueObject<Orthanc::ResourceType>(Orthanc::ResourceType_Study));
+
+    // Secondly, load the series
+    if (!seriesInstanceUid.empty())
+    {
+      filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false);
+    }
+    
+    tags.insert(Orthanc::DICOM_TAG_SERIES_NUMBER);  // Necessary for Google Cloud Platform
+    
+    resourcesLoader_->ScheduleQido(
+      series_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Series, filter, tags,
+      new Orthanc::SingleValueObject<Orthanc::ResourceType>(Orthanc::ResourceType_Series));
+
+    pending_ += 2;
+  }
+
+public:
+  static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::ILoadersContext::ILock& lock,
+                                                   const OrthancStone::DicomSource& source)
+  {
+    boost::shared_ptr<ResourcesLoader> loader(new ResourcesLoader(source));
+
+    loader->resourcesLoader_ = OrthancStone::DicomResourcesLoader::Create(lock);
+    loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(lock, PRIORITY_LOW);
+    loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(lock);
+    
+    loader->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>(
+      *loader->resourcesLoader_, &ResourcesLoader::Handle);
+
+    loader->Register<OrthancStone::SeriesThumbnailsLoader::SuccessMessage>(
+      *loader->thumbnailsLoader_, &ResourcesLoader::Handle);
+
+    loader->Register<OrthancStone::SeriesMetadataLoader::SuccessMessage>(
+      *loader->metadataLoader_, &ResourcesLoader::Handle);
+    
+    return loader;
+  }
+  
+  void FetchAllStudies()
+  {
+    FetchInternal("", "");
+  }
+  
+  void FetchStudy(const std::string& studyInstanceUid)
+  {
+    FetchInternal(studyInstanceUid, "");
+  }
+  
+  void FetchSeries(const std::string& studyInstanceUid,
+                   const std::string& seriesInstanceUid)
+  {
+    FetchInternal(studyInstanceUid, seriesInstanceUid);
+  }
+
+  size_t GetStudiesCount() const
+  {
+    return studies_->GetSize();
+  }
+
+  size_t GetSeriesCount() const
+  {
+    return series_->GetSize();
+  }
+
+  void GetStudy(Orthanc::DicomMap& target,
+                size_t i)
+  {
+    target.Assign(studies_->GetResource(i));
+  }
+
+  void GetSeries(Orthanc::DicomMap& target,
+                 size_t i)
+  {
+    target.Assign(series_->GetResource(i));
+
+    // Complement with the study-level tags
+    std::string studyInstanceUid;
+    if (target.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
+        studies_->HasResource(studyInstanceUid))
+    {
+      studies_->MergeResource(target, studyInstanceUid);
+    }
+  }
+
+  OrthancStone::SeriesThumbnailType GetSeriesThumbnail(std::string& image,
+                                                       std::string& mime,
+                                                       const std::string& seriesInstanceUid)
+  {
+    return thumbnailsLoader_->GetSeriesThumbnail(image, mime, seriesInstanceUid);
+  }
+
+  void FetchSeriesMetadata(int priority,
+                           const std::string& studyInstanceUid,
+                           const std::string& seriesInstanceUid)
+  {
+    metadataLoader_->ScheduleLoadSeries(priority, source_, studyInstanceUid, seriesInstanceUid);
+  }
+
+  bool IsSeriesComplete(const std::string& seriesInstanceUid)
+  {
+    OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid);
+    return accessor.IsComplete();
+  }
+
+  bool SortSeriesFrames(OrthancStone::SortedFrames& target,
+                        const std::string& seriesInstanceUid)
+  {
+    OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid);
+    
+    if (accessor.IsComplete())
+    {
+      target.Clear();
+
+      for (size_t i = 0; i < accessor.GetInstancesCount(); i++)
+      {
+        target.AddInstance(accessor.GetInstance(i));
+      }
+
+      target.Sort();
+      
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  void AcquireObserver(IObserver* observer)
+  {  
+    observer_.reset(observer);
+  }
+};
+
+
+
+class FramesCache : public boost::noncopyable
+{
+private:
+  class CachedImage : public Orthanc::ICacheable
+  {
+  private:
+    std::unique_ptr<Orthanc::ImageAccessor>  image_;
+    unsigned int                             quality_;
+
+  public:
+    CachedImage(Orthanc::ImageAccessor* image,
+                unsigned int quality) :
+      image_(image),
+      quality_(quality)
+    {
+      assert(image != NULL);
+    }
+
+    virtual size_t GetMemoryUsage() const
+    {    
+      assert(image_.get() != NULL);
+      return (image_->GetBytesPerPixel() * image_->GetPitch() * image_->GetHeight());
+    }
+
+    const Orthanc::ImageAccessor& GetImage() const
+    {
+      assert(image_.get() != NULL);
+      return *image_;
+    }
+
+    unsigned int GetQuality() const
+    {
+      return quality_;
+    }
+  };
+
+
+  static std::string GetKey(const std::string& sopInstanceUid,
+                            size_t frameIndex)
+  {
+    return sopInstanceUid + "|" + boost::lexical_cast<std::string>(frameIndex);
+  }
+  
+
+  Orthanc::MemoryObjectCache  cache_;
+  
+public:
+  FramesCache()
+  {
+    SetMaximumSize(100 * 1024 * 1024);  // 100 MB
+  }
+  
+  size_t GetMaximumSize()
+  {
+    return cache_.GetMaximumSize();
+  }
+    
+  void SetMaximumSize(size_t size)
+  {
+    cache_.SetMaximumSize(size);
+  }
+
+  /**
+   * Returns "true" iff the provided image has better quality than the
+   * previously cached one, or if no cache was previously available.
+   **/
+  bool Acquire(const std::string& sopInstanceUid,
+               size_t frameIndex,
+               Orthanc::ImageAccessor* image /* transfer ownership */,
+               unsigned int quality)
+  {
+    std::unique_ptr<Orthanc::ImageAccessor> protection(image);
+    
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else if (image->GetFormat() != Orthanc::PixelFormat_Float32 &&
+             image->GetFormat() != Orthanc::PixelFormat_RGB24)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    const std::string& key = GetKey(sopInstanceUid, frameIndex);
+
+    bool invalidate = false;
+    
+    {
+      /**
+       * Access the previous cached entry, with side effect of tagging
+       * it as the most recently accessed frame (update of LRU recycling)
+       **/
+      Orthanc::MemoryObjectCache::Accessor accessor(cache_, key, false /* unique lock */);
+
+      if (accessor.IsValid())
+      {
+        const CachedImage& previous = dynamic_cast<const CachedImage&>(accessor.GetValue());
+        
+        // There is already a cached image for this frame
+        if (previous.GetQuality() < quality)
+        {
+          // The previously stored image has poorer quality
+          invalidate = true;
+        }
+        else
+        {
+          // No update in the quality, don't change the cache
+          return false;   
+        }
+      }
+      else
+      {
+        invalidate = false;
+      }
+    }
+
+    if (invalidate)
+    {
+      cache_.Invalidate(key);
+    }
+        
+    cache_.Acquire(key, new CachedImage(protection.release(), quality));
+    return true;
+  }
+
+  class Accessor : public boost::noncopyable
+  {
+  private:
+    Orthanc::MemoryObjectCache::Accessor accessor_;
+
+    const CachedImage& GetCachedImage() const
+    {
+      if (IsValid())
+      {
+        return dynamic_cast<CachedImage&>(accessor_.GetValue());
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+    
+  public:
+    Accessor(FramesCache& that,
+             const std::string& sopInstanceUid,
+             size_t frameIndex) :
+      accessor_(that.cache_, GetKey(sopInstanceUid, frameIndex), false /* shared lock */)
+    {
+    }
+
+    bool IsValid() const
+    {
+      return accessor_.IsValid();
+    }
+
+    const Orthanc::ImageAccessor& GetImage() const
+    {
+      return GetCachedImage().GetImage();
+    }
+
+    unsigned int GetQuality() const
+    {
+      return GetCachedImage().GetQuality();
+    }
+  };
+};
+
+
+
+class SeriesCursor : public boost::noncopyable
+{
+public:
+  enum Action
+  {
+    Action_FastPlus,
+    Action_Plus,
+    Action_None,
+    Action_Minus,
+    Action_FastMinus
+  };
+  
+private:
+  std::vector<size_t>  prefetch_;
+  int                  framesCount_;
+  int                  currentFrame_;
+  bool                 isCircular_;
+  int                  fastDelta_;
+  Action               lastAction_;
+
+  int ComputeNextFrame(int currentFrame,
+                       Action action) const
+  {
+    if (framesCount_ == 0)
+    {
+      assert(currentFrame == 0);
+      return 0;
+    }
+
+    int nextFrame = currentFrame;
+    
+    switch (action)
+    {
+      case Action_FastPlus:
+        nextFrame += fastDelta_;
+        break;
+
+      case Action_Plus:
+        nextFrame += 1;
+        break;
+
+      case Action_None:
+        break;
+
+      case Action_Minus:
+        nextFrame -= 1;
+        break;
+
+      case Action_FastMinus:
+        nextFrame -= fastDelta_;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (isCircular_)
+    {
+      while (nextFrame < 0)
+      {
+        nextFrame += framesCount_;
+      }
+
+      while (nextFrame >= framesCount_)
+      {
+        nextFrame -= framesCount_;
+      }
+    }
+    else
+    {
+      if (nextFrame < 0)
+      {
+        nextFrame = 0;
+      }
+      else if (nextFrame >= framesCount_)
+      {
+        nextFrame = framesCount_ - 1;
+      }
+    }
+
+    return nextFrame;
+  }
+  
+  void UpdatePrefetch()
+  {
+    /**
+     * This method will order the frames of the series according to
+     * the number of "actions" (i.e. mouse wheels) that are necessary
+     * to reach them, starting from the current frame. It is assumed
+     * that once one action is done, it is more likely that the user
+     * will do the same action just afterwards.
+     **/
+    
+    prefetch_.clear();
+
+    if (framesCount_ == 0)
+    {
+      return;
+    }
+
+    prefetch_.reserve(framesCount_);
+    
+    // Breadth-first search using a FIFO. The queue associates a frame
+    // and the action that is the most likely in this frame
+    typedef std::list< std::pair<int, Action> >  Queue;
+
+    Queue queue;
+    std::set<int>  visited;  // Frames that have already been visited
+
+    queue.push_back(std::make_pair(currentFrame_, lastAction_));
+
+    while (!queue.empty())
+    {
+      int frame = queue.front().first;
+      Action previousAction = queue.front().second;
+      queue.pop_front();
+
+      if (visited.find(frame) == visited.end())
+      {
+        visited.insert(frame);
+        prefetch_.push_back(frame);
+
+        switch (previousAction)
+        {
+          case Action_None:
+          case Action_Plus:
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
+            break;
+          
+          case Action_Minus:
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
+            break;
+
+          case Action_FastPlus:
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
+            break;
+              
+          case Action_FastMinus:
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
+            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+      }
+    }
+
+    assert(prefetch_.size() == framesCount_);
+  }
+
+  bool CheckFrameIndex(int frame) const
+  {
+    return ((framesCount_ == 0 && frame == 0) ||
+            (framesCount_ > 0 && frame >= 0 && frame < framesCount_));
+  }
+  
+public:
+  SeriesCursor(size_t framesCount) :
+    framesCount_(framesCount),
+    currentFrame_(framesCount / 2),  // Start at the middle frame    
+    isCircular_(false),
+    lastAction_(Action_None)
+  {
+    SetFastDelta(framesCount / 20);
+    UpdatePrefetch();
+  }
+
+  void SetCircular(bool isCircular)
+  {
+    isCircular_ = isCircular;
+    UpdatePrefetch();
+  }
+
+  void SetFastDelta(int delta)
+  {
+    fastDelta_ = (delta < 0 ? -delta : delta);
+
+    if (fastDelta_ <= 0)
+    {
+      fastDelta_ = 1;
+    }
+  }
+
+  void SetCurrentIndex(size_t frame)
+  {
+    if (frame >= framesCount_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      currentFrame_ = frame;
+      lastAction_ = Action_None;
+      UpdatePrefetch();
+    }
+  }
+
+  size_t GetCurrentIndex() const
+  {
+    assert(CheckFrameIndex(currentFrame_));
+    return static_cast<size_t>(currentFrame_);
+  }
+
+  void Apply(Action action)
+  {
+    currentFrame_ = ComputeNextFrame(currentFrame_, action);
+    lastAction_ = action;
+    UpdatePrefetch();
+  }
+
+  size_t GetPrefetchSize() const
+  {
+    assert(prefetch_.size() == framesCount_);
+    return prefetch_.size();
+  }
+
+  size_t GetPrefetchFrameIndex(size_t i) const
+  {
+    if (i >= prefetch_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(CheckFrameIndex(prefetch_[i]));
+      return static_cast<size_t>(prefetch_[i]);
+    }
+  }
+};
+
+
+
+
+class FrameGeometry
+{
+private:
+  bool                              isValid_;
+  std::string                       frameOfReferenceUid_;
+  OrthancStone::CoordinateSystem3D  coordinates_;
+  double                            pixelSpacingX_;
+  double                            pixelSpacingY_;
+  OrthancStone::Extent2D            extent_;
+
+public:
+  FrameGeometry() :
+    isValid_(false)
+  {
+  }
+    
+  FrameGeometry(const Orthanc::DicomMap& tags) :
+    isValid_(false),
+    coordinates_(tags)
+  {
+    if (!tags.LookupStringValue(
+          frameOfReferenceUid_, Orthanc::DICOM_TAG_FRAME_OF_REFERENCE_UID, false))
+    {
+      frameOfReferenceUid_.clear();
+    }
+
+    OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, tags);
+
+    unsigned int rows, columns;
+    if (tags.HasTag(Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT) &&
+        tags.HasTag(Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) &&
+        tags.ParseUnsignedInteger32(rows, Orthanc::DICOM_TAG_ROWS) &&
+        tags.ParseUnsignedInteger32(columns, Orthanc::DICOM_TAG_COLUMNS))
+    {
+      double ox = -pixelSpacingX_ / 2.0;
+      double oy = -pixelSpacingY_ / 2.0;
+      extent_.AddPoint(ox, oy);
+      extent_.AddPoint(ox + pixelSpacingX_ * static_cast<double>(columns),
+                       oy + pixelSpacingY_ * static_cast<double>(rows));
+
+      isValid_ = true;
+    }
+  }
+
+  bool IsValid() const
+  {
+    return isValid_;
+  }
+
+  const std::string& GetFrameOfReferenceUid() const
+  {
+    if (isValid_)
+    {
+      return frameOfReferenceUid_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  const OrthancStone::CoordinateSystem3D& GetCoordinates() const
+  {
+    if (isValid_)
+    {
+      return coordinates_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  double GetPixelSpacingX() const
+  {
+    if (isValid_)
+    {
+      return pixelSpacingX_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  double GetPixelSpacingY() const
+  {
+    if (isValid_)
+    {
+      return pixelSpacingY_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  bool Intersect(double& x1,  // Coordinates of the clipped line (out)
+                 double& y1,
+                 double& x2,
+                 double& y2,
+                 const FrameGeometry& other) const
+  {
+    if (this == &other)
+    {
+      return false;
+    }
+    
+    OrthancStone::Vector direction, origin;
+        
+    if (IsValid() &&
+        other.IsValid() &&
+        !extent_.IsEmpty() &&
+        frameOfReferenceUid_ == other.frameOfReferenceUid_ &&
+        OrthancStone::GeometryToolbox::IntersectTwoPlanes(
+          origin, direction,
+          coordinates_.GetOrigin(), coordinates_.GetNormal(),
+          other.coordinates_.GetOrigin(), other.coordinates_.GetNormal()))
+    {
+      double ax, ay, bx, by;
+      coordinates_.ProjectPoint(ax, ay, origin);
+      coordinates_.ProjectPoint(bx, by, origin + 100.0 * direction);
+      
+      return OrthancStone::GeometryToolbox::ClipLineToRectangle(
+        x1, y1, x2, y2,
+        ax, ay, bx, by,
+        extent_.GetX1(), extent_.GetY1(), extent_.GetX2(), extent_.GetY2());
+    }
+    else
+    {
+      return false;
+    }
+  }
+};
+  
+
+
+class ViewerViewport : public OrthancStone::ObserverBase<ViewerViewport>
+{
+public:
+  class IObserver : public boost::noncopyable
+  {
+  public:
+    virtual ~IObserver()
+    {
+    }
+
+    virtual void SignalFrameUpdated(const ViewerViewport& viewport,
+                                    size_t currentFrame,
+                                    size_t countFrames,
+                                    DisplayedFrameQuality quality) = 0;
+  };
+
+private:
+  static const int LAYER_TEXTURE = 0;
+  static const int LAYER_REFERENCE_LINES = 1;
+  
+  
+  class ICommand : public Orthanc::IDynamicObject
+  {
+  private:
+    boost::shared_ptr<ViewerViewport>  viewport_;
+    
+  public:
+    ICommand(boost::shared_ptr<ViewerViewport> viewport) :
+      viewport_(viewport)
+    {
+      if (viewport == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+    
+    virtual ~ICommand()
+    {
+    }
+
+    ViewerViewport& GetViewport() const
+    {
+      assert(viewport_ != NULL);
+      return *viewport_;
+    }
+    
+    virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+    
+    virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  };
+
+  class SetDefaultWindowingCommand : public ICommand
+  {
+  public:
+    SetDefaultWindowingCommand(boost::shared_ptr<ViewerViewport> viewport) :
+      ICommand(viewport)
+    {
+    }
+    
+    virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE
+    {
+      if (message.GetResources()->GetSize() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      const Orthanc::DicomMap& dicom = message.GetResources()->GetResource(0);
+      
+      {
+        OrthancStone::DicomInstanceParameters params(dicom);
+
+        if (params.HasDefaultWindowing())
+        {
+          GetViewport().defaultWindowingCenter_ = params.GetDefaultWindowingCenter();
+          GetViewport().defaultWindowingWidth_ = params.GetDefaultWindowingWidth();
+          LOG(INFO) << "Default windowing: " << params.GetDefaultWindowingCenter()
+                    << "," << params.GetDefaultWindowingWidth();
+
+          GetViewport().windowingCenter_ = params.GetDefaultWindowingCenter();
+          GetViewport().windowingWidth_ = params.GetDefaultWindowingWidth();
+        }
+        else
+        {
+          LOG(INFO) << "No default windowing";
+          GetViewport().ResetDefaultWindowing();
+        }
+      }
+
+      GetViewport().DisplayCurrentFrame();
+    }
+  };
+
+  class SetLowQualityFrame : public ICommand
+  {
+  private:
+    std::string   sopInstanceUid_;
+    unsigned int  frameIndex_;
+    float         windowCenter_;
+    float         windowWidth_;
+    bool          isMonochrome1_;
+    bool          isPrefetch_;
+    
+  public:
+    SetLowQualityFrame(boost::shared_ptr<ViewerViewport> viewport,
+                       const std::string& sopInstanceUid,
+                       unsigned int frameIndex,
+                       float windowCenter,
+                       float windowWidth,
+                       bool isMonochrome1,
+                       bool isPrefetch) :
+      ICommand(viewport),
+      sopInstanceUid_(sopInstanceUid),
+      frameIndex_(frameIndex),
+      windowCenter_(windowCenter),
+      windowWidth_(windowWidth),
+      isMonochrome1_(isMonochrome1),
+      isPrefetch_(isPrefetch)
+    {
+    }
+    
+    virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const ORTHANC_OVERRIDE
+    {
+      std::unique_ptr<Orthanc::JpegReader> jpeg(new Orthanc::JpegReader);
+      jpeg->ReadFromMemory(message.GetAnswer());
+
+      bool updatedCache;
+      
+      switch (jpeg->GetFormat())
+      {
+        case Orthanc::PixelFormat_RGB24:
+          updatedCache = GetViewport().cache_->Acquire(
+            sopInstanceUid_, frameIndex_, jpeg.release(), QUALITY_JPEG);
+          break;
+
+        case Orthanc::PixelFormat_Grayscale8:
+        {
+          if (isMonochrome1_)
+          {
+            Orthanc::ImageProcessing::Invert(*jpeg);
+          }
+
+          std::unique_ptr<Orthanc::Image> converted(
+            new Orthanc::Image(Orthanc::PixelFormat_Float32, jpeg->GetWidth(),
+                               jpeg->GetHeight(), false));
+
+          Orthanc::ImageProcessing::Convert(*converted, *jpeg);
+
+          /**
+
+             Orthanc::ImageProcessing::ShiftScale() computes "(x + offset) * scaling".
+             The system to solve is thus:           
+
+             (0 + offset) * scaling = windowingCenter - windowingWidth / 2     [a]
+             (255 + offset) * scaling = windowingCenter + windowingWidth / 2   [b]
+
+             Resolution:
+
+             [b - a] => 255 * scaling = windowingWidth
+             [a] => offset = (windowingCenter - windowingWidth / 2) / scaling
+
+          **/
+
+          const float scaling = windowWidth_ / 255.0f;
+          const float offset = (OrthancStone::LinearAlgebra::IsCloseToZero(scaling) ? 0 :
+                                (windowCenter_ - windowWidth_ / 2.0f) / scaling);
+
+          Orthanc::ImageProcessing::ShiftScale(*converted, offset, scaling, false);
+          updatedCache = GetViewport().cache_->Acquire(
+            sopInstanceUid_, frameIndex_, converted.release(), QUALITY_JPEG);
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      if (updatedCache)
+      {
+        GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_);
+      }
+
+      if (isPrefetch_)
+      {
+        GetViewport().ScheduleNextPrefetch();
+      }
+    }
+  };
+
+
+  class SetFullDicomFrame : public ICommand
+  {
+  private:
+    std::string   sopInstanceUid_;
+    unsigned int  frameIndex_;
+    bool          isPrefetch_;
+    
+  public:
+    SetFullDicomFrame(boost::shared_ptr<ViewerViewport> viewport,
+                      const std::string& sopInstanceUid,
+                      unsigned int frameIndex,
+                      bool isPrefetch) :
+      ICommand(viewport),
+      sopInstanceUid_(sopInstanceUid),
+      frameIndex_(frameIndex),
+      isPrefetch_(isPrefetch)
+    {
+    }
+    
+    virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const ORTHANC_OVERRIDE
+    {
+      Orthanc::DicomMap tags;
+      message.GetDicom().ExtractDicomSummary(tags, ORTHANC_STONE_MAX_TAG_LENGTH);
+
+      std::string s;
+      if (!tags.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
+      {
+        // Safety check
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }      
+      
+      std::unique_ptr<Orthanc::ImageAccessor> frame(
+        Orthanc::DicomImageDecoder::Decode(message.GetDicom(), frameIndex_));
+
+      if (frame.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      bool updatedCache;
+
+      if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
+      {
+        updatedCache = GetViewport().cache_->Acquire(
+          sopInstanceUid_, frameIndex_, frame.release(), QUALITY_FULL);
+      }
+      else
+      {
+        double a = 1;
+        double b = 0;
+
+        double doseScaling;
+        if (tags.ParseDouble(doseScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
+        {
+          a = doseScaling;
+        }
+      
+        double rescaleIntercept, rescaleSlope;
+        if (tags.ParseDouble(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
+            tags.ParseDouble(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE))
+        {
+          a *= rescaleSlope;
+          b = rescaleIntercept;
+        }
+
+        std::unique_ptr<Orthanc::ImageAccessor> converted(
+          new Orthanc::Image(Orthanc::PixelFormat_Float32, frame->GetWidth(), frame->GetHeight(), false));
+        Orthanc::ImageProcessing::Convert(*converted, *frame);
+        Orthanc::ImageProcessing::ShiftScale2(*converted, b, a, false);
+
+        updatedCache = GetViewport().cache_->Acquire(
+          sopInstanceUid_, frameIndex_, converted.release(), QUALITY_FULL);
+      }
+      
+      if (updatedCache)
+      {
+        GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_);
+      }
+
+      if (isPrefetch_)
+      {
+        GetViewport().ScheduleNextPrefetch();
+      }
+    }
+  };
+
+
+  class PrefetchItem
+  {
+  private:
+    size_t   frameIndex_;
+    bool     isFull_;
+
+  public:
+    PrefetchItem(size_t frameIndex,
+                 bool isFull) :
+      frameIndex_(frameIndex),
+      isFull_(isFull)
+    {
+    }
+
+    size_t GetFrameIndex() const
+    {
+      return frameIndex_;
+    }
+
+    bool IsFull() const
+    {
+      return isFull_;
+    }
+  };
+  
+
+  std::unique_ptr<IObserver>                    observer_;
+  OrthancStone::ILoadersContext&               context_;
+  boost::shared_ptr<OrthancStone::WebGLViewport>   viewport_;
+  boost::shared_ptr<OrthancStone::DicomResourcesLoader> loader_;
+  OrthancStone::DicomSource                    source_;
+  boost::shared_ptr<FramesCache>               cache_;  
+  std::unique_ptr<OrthancStone::SortedFrames>  frames_;
+  std::unique_ptr<SeriesCursor>                cursor_;
+  float                                        windowingCenter_;
+  float                                        windowingWidth_;
+  float                                        defaultWindowingCenter_;
+  float                                        defaultWindowingWidth_;
+  bool                                         inverted_;
+  bool                                         fitNextContent_;
+  bool                                         isCtrlDown_;
+  FrameGeometry                                currentFrameGeometry_;
+  std::list<PrefetchItem>                      prefetchQueue_;
+
+  void ScheduleNextPrefetch()
+  {
+    while (!prefetchQueue_.empty())
+    {
+      size_t index = prefetchQueue_.front().GetFrameIndex();
+      bool isFull = prefetchQueue_.front().IsFull();
+      prefetchQueue_.pop_front();
+      
+      const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
+      unsigned int frame = frames_->GetFrameIndex(index);
+
+      {
+        FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame);
+        if (!accessor.IsValid() ||
+            (isFull && accessor.GetQuality() == 0))
+        {
+          if (isFull)
+          {
+            ScheduleLoadFullDicomFrame(index, PRIORITY_NORMAL, true);
+          }
+          else
+          {
+            ScheduleLoadRenderedFrame(index, PRIORITY_NORMAL, true);
+          }
+          return;
+        }
+      }
+    }
+  }
+  
+  
+  void ResetDefaultWindowing()
+  {
+    defaultWindowingCenter_ = 128;
+    defaultWindowingWidth_ = 256;
+
+    windowingCenter_ = defaultWindowingCenter_;
+    windowingWidth_ = defaultWindowingWidth_;
+
+    inverted_ = false;
+  }
+
+  void SignalUpdatedFrame(const std::string& sopInstanceUid,
+                          unsigned int frameIndex)
+  {
+    if (cursor_.get() != NULL &&
+        frames_.get() != NULL)
+    {
+      size_t index = cursor_->GetCurrentIndex();
+
+      if (frames_->GetFrameSopInstanceUid(index) == sopInstanceUid &&
+          frames_->GetFrameIndex(index) == frameIndex)
+      {
+        DisplayCurrentFrame();
+      }
+    }
+  }
+
+  void DisplayCurrentFrame()
+  {
+    DisplayedFrameQuality quality = DisplayedFrameQuality_None;
+    
+    if (cursor_.get() != NULL &&
+        frames_.get() != NULL)
+    {
+      const size_t index = cursor_->GetCurrentIndex();
+      
+      unsigned int cachedQuality;
+      if (!DisplayFrame(cachedQuality, index))
+      {
+        // This frame is not cached yet: Load it
+        if (source_.HasDicomWebRendered())
+        {
+          ScheduleLoadRenderedFrame(index, PRIORITY_HIGH, false /* not a prefetch */);
+        }
+        else
+        {
+          ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */);
+        }
+      }
+      else if (cachedQuality < QUALITY_FULL)
+      {
+        // This frame is only available in low-res: Download the full DICOM
+        ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */);
+        quality = DisplayedFrameQuality_Low;
+      }
+      else
+      {
+        quality = DisplayedFrameQuality_High;
+      }
+
+      currentFrameGeometry_ = FrameGeometry(frames_->GetFrameTags(index));
+
+      {
+        // Prepare prefetching
+        prefetchQueue_.clear();
+        for (size_t i = 0; i < cursor_->GetPrefetchSize() && i < 16; i++)
+        {
+          size_t a = cursor_->GetPrefetchFrameIndex(i);
+          if (a != index)
+          {
+            prefetchQueue_.push_back(PrefetchItem(a, i < 2));
+          }
+        }
+
+        ScheduleNextPrefetch();
+      }      
+
+      if (observer_.get() != NULL)
+      {
+        observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(),
+                                      frames_->GetFramesCount(), quality);
+      }
+    }
+    else
+    {
+      currentFrameGeometry_ = FrameGeometry();
+    }
+  }
+
+  void ClearViewport()
+  {
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().DeleteLayer(LAYER_TEXTURE);
+      //lock->GetCompositor().Refresh(lock->GetController().GetScene());
+      lock->Invalidate();
+    }
+  }
+
+  bool DisplayFrame(unsigned int& quality,
+                    size_t index)
+  {
+    if (frames_.get() == NULL)
+    {
+      return false;
+    }
+
+    const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
+    unsigned int frame = frames_->GetFrameIndex(index);
+
+    FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame);
+    if (accessor.IsValid())
+    {
+      {
+        std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+        OrthancStone::Scene2D& scene = lock->GetController().GetScene();
+
+        // Save the current windowing (that could have been altered by
+        // GrayscaleWindowingSceneTracker), so that it can be reused
+        // by the next frames
+        if (scene.HasLayer(LAYER_TEXTURE) &&
+            scene.GetLayer(LAYER_TEXTURE).GetType() == OrthancStone::ISceneLayer::Type_FloatTexture)
+        {
+          OrthancStone::FloatTextureSceneLayer& layer =
+            dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(scene.GetLayer(LAYER_TEXTURE));
+          layer.GetWindowing(windowingCenter_, windowingWidth_);
+        }
+      }
+      
+      quality = accessor.GetQuality();
+
+      std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer;
+
+      switch (accessor.GetImage().GetFormat())
+      {
+        case Orthanc::PixelFormat_RGB24:
+          layer.reset(new OrthancStone::ColorTextureSceneLayer(accessor.GetImage()));
+          break;
+
+        case Orthanc::PixelFormat_Float32:
+        {
+          std::unique_ptr<OrthancStone::FloatTextureSceneLayer> tmp(
+            new OrthancStone::FloatTextureSceneLayer(accessor.GetImage()));
+          tmp->SetCustomWindowing(windowingCenter_, windowingWidth_);
+          tmp->SetInverted(inverted_ ^ frames_->IsFrameMonochrome1(index));
+          layer.reset(tmp.release());
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+
+      layer->SetLinearInterpolation(true);
+
+      double pixelSpacingX, pixelSpacingY;
+      OrthancStone::GeometryToolbox::GetPixelSpacing(
+        pixelSpacingX, pixelSpacingY, frames_->GetFrameTags(index));
+      layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY);
+
+      if (layer.get() == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+        OrthancStone::Scene2D& scene = lock->GetController().GetScene();
+
+        scene.SetLayer(LAYER_TEXTURE, layer.release());
+
+        if (fitNextContent_)
+        {
+          lock->GetCompositor().RefreshCanvasSize();
+          lock->GetCompositor().FitContent(scene);
+          fitNextContent_ = false;
+        }
+        
+        //lock->GetCompositor().Refresh(scene);
+        lock->Invalidate();
+        return true;
+      }
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  void ScheduleLoadFullDicomFrame(size_t index,
+                                  int priority,
+                                  bool isPrefetch)
+  {
+    if (frames_.get() != NULL)
+    {
+      std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
+      unsigned int frame = frames_->GetFrameIndex(index);
+      
+      {
+        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
+        lock->Schedule(
+          GetSharedObserver(), priority, OrthancStone::ParseDicomFromWadoCommand::Create(
+            source_, frames_->GetStudyInstanceUid(), frames_->GetSeriesInstanceUid(),
+            sopInstanceUid, false /* transcoding (TODO) */,
+            Orthanc::DicomTransferSyntax_LittleEndianExplicit /* TODO */,
+            new SetFullDicomFrame(GetSharedObserver(), sopInstanceUid, frame, isPrefetch)));
+      }
+    }
+  }
+
+  void ScheduleLoadRenderedFrame(size_t index,
+                                 int priority,
+                                 bool isPrefetch)
+  {
+    if (!source_.HasDicomWebRendered())
+    {
+      ScheduleLoadFullDicomFrame(index, priority, isPrefetch);
+    }
+    else if (frames_.get() != NULL)
+    {
+      std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
+      unsigned int frame = frames_->GetFrameIndex(index);
+      bool isMonochrome1 = frames_->IsFrameMonochrome1(index);
+
+      const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() +
+                               "/series/" + frames_->GetSeriesInstanceUid() +
+                               "/instances/" + sopInstanceUid +
+                               "/frames/" + boost::lexical_cast<std::string>(frame + 1) + "/rendered");
+
+      std::map<std::string, std::string> headers, arguments;
+      arguments["window"] = (
+        boost::lexical_cast<std::string>(windowingCenter_) + ","  +
+        boost::lexical_cast<std::string>(windowingWidth_) + ",linear");
+
+      std::unique_ptr<OrthancStone::IOracleCommand> command(
+        source_.CreateDicomWebCommand(
+          uri, arguments, headers, new SetLowQualityFrame(
+            GetSharedObserver(), sopInstanceUid, frame,
+            windowingCenter_, windowingWidth_, isMonochrome1, isPrefetch)));
+
+      {
+        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
+        lock->Schedule(GetSharedObserver(), priority, command.release());
+      }
+    }
+  }
+
+  ViewerViewport(OrthancStone::ILoadersContext& context,
+                 const OrthancStone::DicomSource& source,
+                 const std::string& canvas,
+                 boost::shared_ptr<FramesCache> cache) :
+    context_(context),
+    source_(source),
+    viewport_(OrthancStone::WebGLViewport::Create(canvas)),
+    cache_(cache),
+    fitNextContent_(true),
+    isCtrlDown_(false)
+  {
+    if (!cache_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    
+    emscripten_set_wheel_callback(viewport_->GetCanvasCssSelector().c_str(), this, true, OnWheel);
+    emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey);
+    emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey);
+
+    ResetDefaultWindowing();
+  }
+
+  static EM_BOOL OnKey(int eventType,
+                       const EmscriptenKeyboardEvent *event,
+                       void *userData)
+  {
+    /**
+     * WARNING: There is a problem with Firefox 71 that seems to mess
+     * the "ctrlKey" value.
+     **/
+    
+    ViewerViewport& that = *reinterpret_cast<ViewerViewport*>(userData);
+    that.isCtrlDown_ = event->ctrlKey;
+    return false;
+  }
+
+  
+  static EM_BOOL OnWheel(int eventType,
+                         const EmscriptenWheelEvent *wheelEvent,
+                         void *userData)
+  {
+    ViewerViewport& that = *reinterpret_cast<ViewerViewport*>(userData);
+
+    if (that.cursor_.get() != NULL)
+    {
+      if (wheelEvent->deltaY < 0)
+      {
+        that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastMinus : SeriesCursor::Action_Minus);
+      }
+      else if (wheelEvent->deltaY > 0)
+      {
+        that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastPlus : SeriesCursor::Action_Plus);
+      }
+    }
+    
+    return true;
+  }
+
+  void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message)
+  {
+    dynamic_cast<const ICommand&>(message.GetUserPayload()).Handle(message);
+  }
+
+  void Handle(const OrthancStone::HttpCommand::SuccessMessage& message)
+  {
+    dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message);
+  }
+
+  void Handle(const OrthancStone::ParseDicomSuccessMessage& message)
+  {
+    dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message);
+  }
+  
+public:
+  static boost::shared_ptr<ViewerViewport> Create(OrthancStone::ILoadersContext::ILock& lock,
+                                                  const OrthancStone::DicomSource& source,
+                                                  const std::string& canvas,
+                                                  boost::shared_ptr<FramesCache> cache)
+  {
+    boost::shared_ptr<ViewerViewport> viewport(
+      new ViewerViewport(lock.GetContext(), source, canvas, cache));
+
+    viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(lock);
+    viewport->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>(
+      *viewport->loader_, &ViewerViewport::Handle);
+
+    viewport->Register<OrthancStone::HttpCommand::SuccessMessage>(
+      lock.GetOracleObservable(), &ViewerViewport::Handle);
+
+    viewport->Register<OrthancStone::ParseDicomSuccessMessage>(
+      lock.GetOracleObservable(), &ViewerViewport::Handle);
+
+    return viewport;    
+  }
+
+  void SetFrames(OrthancStone::SortedFrames* frames)
+  {
+    if (frames == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    fitNextContent_ = true;
+
+    frames_.reset(frames);
+    cursor_.reset(new SeriesCursor(frames_->GetFramesCount()));
+
+    LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount();
+
+    ResetDefaultWindowing();
+    ClearViewport();
+    prefetchQueue_.clear();
+    currentFrameGeometry_ = FrameGeometry();
+
+    if (observer_.get() != NULL)
+    {
+      observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(),
+                                    frames_->GetFramesCount(), DisplayedFrameQuality_None);
+    }
+    
+    if (frames_->GetFramesCount() != 0)
+    {
+      const std::string& sopInstanceUid = frames_->GetFrameSopInstanceUid(cursor_->GetCurrentIndex());
+
+      {
+        // Fetch the default windowing for the central instance
+        const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() +
+                                 "/series/" + frames_->GetSeriesInstanceUid() +
+                                 "/instances/" + sopInstanceUid + "/metadata");
+        
+        loader_->ScheduleGetDicomWeb(
+          boost::make_shared<OrthancStone::LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
+          0, source_, uri, new SetDefaultWindowingCommand(GetSharedObserver()));
+      }
+    }
+  }
+
+  // This method is used when the layout of the HTML page changes,
+  // which does not trigger the "emscripten_set_resize_callback()"
+  void UpdateSize(bool fitContent)
+  {
+    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+    lock->GetCompositor().RefreshCanvasSize();
+
+    if (fitContent)
+    {
+      lock->GetCompositor().FitContent(lock->GetController().GetScene());
+    }
+
+    lock->Invalidate();
+  }
+
+  void AcquireObserver(IObserver* observer)
+  {  
+    observer_.reset(observer);
+  }
+
+  const std::string& GetCanvasId() const
+  {
+    assert(viewport_);
+    return viewport_->GetCanvasId();
+  }
+
+  void ChangeFrame(SeriesCursor::Action action)
+  {
+    if (cursor_.get() != NULL)
+    {
+      size_t previous = cursor_->GetCurrentIndex();
+      
+      cursor_->Apply(action);
+      
+      size_t current = cursor_->GetCurrentIndex();
+      if (previous != current)
+      {
+        DisplayCurrentFrame();
+      }
+    }
+  }
+
+  const FrameGeometry& GetCurrentFrameGeometry() const
+  {
+    return currentFrameGeometry_;
+  }
+
+  void UpdateReferenceLines(const std::list<const FrameGeometry*>& planes)
+  {
+    std::unique_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer);
+    
+    if (GetCurrentFrameGeometry().IsValid())
+    {
+      for (std::list<const FrameGeometry*>::const_iterator
+             it = planes.begin(); it != planes.end(); ++it)
+      {
+        assert(*it != NULL);
+        
+        double x1, y1, x2, y2;
+        if (GetCurrentFrameGeometry().Intersect(x1, y1, x2, y2, **it))
+        {
+          OrthancStone::PolylineSceneLayer::Chain chain;
+          chain.push_back(OrthancStone::ScenePoint2D(x1, y1));
+          chain.push_back(OrthancStone::ScenePoint2D(x2, y2));
+          layer->AddChain(chain, false, 0, 255, 0);
+        }
+      }
+    }
+    
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+      if (layer->GetChainsCount() == 0)
+      {
+        lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES);
+      }
+      else
+      {
+        lock->GetController().GetScene().SetLayer(LAYER_REFERENCE_LINES, layer.release());
+      }
+      
+      //lock->GetCompositor().Refresh(lock->GetController().GetScene());
+      lock->Invalidate();
+    }
+  }
+
+
+  void ClearReferenceLines()
+  {
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES);
+      lock->Invalidate();
+    }
+  }
+
+
+  void SetDefaultWindowing()
+  {
+    SetWindowing(defaultWindowingCenter_, defaultWindowingWidth_);
+  }
+
+  void SetWindowing(float windowingCenter,
+                    float windowingWidth)
+  {
+    windowingCenter_ = windowingCenter;
+    windowingWidth_ = windowingWidth;
+
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+      if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) &&
+          lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() ==
+          OrthancStone::ISceneLayer::Type_FloatTexture)
+      {
+        dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(
+          lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)).
+          SetCustomWindowing(windowingCenter_, windowingWidth_);
+        lock->Invalidate();
+      }
+    }
+  }
+
+  void Invert()
+  {
+    inverted_ = !inverted_;
+    
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+      if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) &&
+          lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() ==
+          OrthancStone::ISceneLayer::Type_FloatTexture)
+      {
+        OrthancStone::FloatTextureSceneLayer& layer = 
+          dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(
+            lock->GetController().GetScene().GetLayer(LAYER_TEXTURE));
+
+        // NB: Using "IsInverted()" instead of "inverted_" is for
+        // compatibility with MONOCHROME1 images
+        layer.SetInverted(!layer.IsInverted());
+        lock->Invalidate();
+      }
+    }
+  }
+};
+
+
+
+
+
+typedef std::map<std::string, boost::shared_ptr<ViewerViewport> >  Viewports;
+static Viewports allViewports_;
+static bool showReferenceLines_ = true;
+
+
+static void UpdateReferenceLines()
+{
+  if (showReferenceLines_)
+  {
+    std::list<const FrameGeometry*> planes;
+    
+    for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      planes.push_back(&it->second->GetCurrentFrameGeometry());
+    }
+
+    for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      it->second->UpdateReferenceLines(planes);
+    }
+  }
+  else
+  {
+    for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      it->second->ClearReferenceLines();
+    }
+  }
+}
+
+
+class WebAssemblyObserver : public ResourcesLoader::IObserver,
+                            public ViewerViewport::IObserver
+{
+public:
+  virtual void SignalResourcesLoaded() ORTHANC_OVERRIDE
+  {
+    DISPATCH_JAVASCRIPT_EVENT("ResourcesLoaded");
+  }
+
+  virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid,
+                                           const std::string& seriesInstanceUid) ORTHANC_OVERRIDE
+  {
+    EM_ASM({
+        const customEvent = document.createEvent("CustomEvent");
+        customEvent.initCustomEvent("ThumbnailLoaded", false, false,
+                                    { "studyInstanceUid" : UTF8ToString($0),
+                                        "seriesInstanceUid" : UTF8ToString($1) });
+        window.dispatchEvent(customEvent);
+      },
+      studyInstanceUid.c_str(),
+      seriesInstanceUid.c_str());
+  }
+
+  virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid,
+                                          const std::string& seriesInstanceUid) ORTHANC_OVERRIDE
+  {
+    EM_ASM({
+        const customEvent = document.createEvent("CustomEvent");
+        customEvent.initCustomEvent("MetadataLoaded", false, false,
+                                    { "studyInstanceUid" : UTF8ToString($0),
+                                        "seriesInstanceUid" : UTF8ToString($1) });
+        window.dispatchEvent(customEvent);
+      },
+      studyInstanceUid.c_str(),
+      seriesInstanceUid.c_str());
+  }
+
+  virtual void SignalFrameUpdated(const ViewerViewport& viewport,
+                                  size_t currentFrame,
+                                  size_t countFrames,
+                                  DisplayedFrameQuality quality) ORTHANC_OVERRIDE
+  {
+    EM_ASM({
+        const customEvent = document.createEvent("CustomEvent");
+        customEvent.initCustomEvent("FrameUpdated", false, false,
+                                    { "canvasId" : UTF8ToString($0),
+                                        "currentFrame" : $1,
+                                        "framesCount" : $2,
+                                        "quality" : $3 });
+        window.dispatchEvent(customEvent);
+      },
+      viewport.GetCanvasId().c_str(),
+      static_cast<int>(currentFrame),
+      static_cast<int>(countFrames),
+      quality);
+
+
+    UpdateReferenceLines();
+  };
+};
+
+
+
+static OrthancStone::DicomSource source_;
+static boost::shared_ptr<FramesCache> cache_;
+static boost::shared_ptr<OrthancStone::WebAssemblyLoadersContext> context_;
+static std::string stringBuffer_;
+
+
+
+static void FormatTags(std::string& target,
+                       const Orthanc::DicomMap& tags)
+{
+  Orthanc::DicomArray arr(tags);
+  Json::Value v = Json::objectValue;
+
+  for (size_t i = 0; i < arr.GetSize(); i++)
+  {
+    const Orthanc::DicomElement& element = arr.GetElement(i);
+    if (!element.GetValue().IsBinary() &&
+        !element.GetValue().IsNull())
+    {
+      v[element.GetTag().Format()] = element.GetValue().GetContent();
+    }
+  }
+
+  target = v.toStyledString();
+}
+
+
+static ResourcesLoader& GetResourcesLoader()
+{
+  static boost::shared_ptr<ResourcesLoader>  resourcesLoader_;
+
+  if (!resourcesLoader_)
+  {
+    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock());
+    resourcesLoader_ = ResourcesLoader::Create(*lock, source_);
+    resourcesLoader_->AcquireObserver(new WebAssemblyObserver);
+  }
+
+  return *resourcesLoader_;
+}
+
+
+static boost::shared_ptr<ViewerViewport> GetViewport(const std::string& canvas)
+{
+  Viewports::iterator found = allViewports_.find(canvas);
+  if (found == allViewports_.end())
+  {
+    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock());
+    boost::shared_ptr<ViewerViewport> viewport(ViewerViewport::Create(*lock, source_, canvas, cache_));
+    viewport->AcquireObserver(new WebAssemblyObserver);
+    allViewports_[canvas] = viewport;
+    return viewport;
+  }
+  else
+  {
+    return found->second;
+  }
+}
+
+
+extern "C"
+{
+  int main(int argc, char const *argv[]) 
+  {
+    printf("OK\n");
+    Orthanc::InitializeFramework("", true);
+    Orthanc::Logging::EnableInfoLevel(true);
+    //Orthanc::Logging::EnableTraceLevel(true);
+
+    context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
+    cache_.reset(new FramesCache);
+    
+    DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetOrthancRoot(const char* uri,
+                      int useRendered)
+  {
+    try
+    {
+      context_->SetLocalOrthanc(uri);  // For "source_.SetDicomWebThroughOrthancSource()"
+      source_.SetDicomWebSource(std::string(uri) + "/dicom-web");
+      source_.SetDicomWebRendered(useRendered != 0);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+  
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetDicomWebServer(const char* serverName,
+                         int hasRendered)
+  {
+    try
+    {
+      source_.SetDicomWebThroughOrthancSource(serverName);
+      source_.SetDicomWebRendered(hasRendered != 0);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+  
+
+  EMSCRIPTEN_KEEPALIVE
+  void FetchAllStudies()
+  {
+    try
+    {
+      GetResourcesLoader().FetchAllStudies();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void FetchStudy(const char* studyInstanceUid)
+  {
+    try
+    {
+      GetResourcesLoader().FetchStudy(studyInstanceUid);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void FetchSeries(const char* studyInstanceUid,
+                   const char* seriesInstanceUid)
+  {
+    try
+    {
+      GetResourcesLoader().FetchSeries(studyInstanceUid, seriesInstanceUid);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+  
+  EMSCRIPTEN_KEEPALIVE
+  int GetStudiesCount()
+  {
+    try
+    {
+      return GetResourcesLoader().GetStudiesCount();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+    return 0;  // on exception
+  }
+  
+  EMSCRIPTEN_KEEPALIVE
+  int GetSeriesCount()
+  {
+    try
+    {
+      return GetResourcesLoader().GetSeriesCount();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+    return 0;  // on exception
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  const char* GetStringBuffer()
+  {
+    return stringBuffer_.c_str();
+  }
+  
+
+  EMSCRIPTEN_KEEPALIVE
+  void LoadStudyTags(int i)
+  {
+    try
+    {
+      if (i < 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      Orthanc::DicomMap dicom;
+      GetResourcesLoader().GetStudy(dicom, i);
+      FormatTags(stringBuffer_, dicom);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+  
+
+  EMSCRIPTEN_KEEPALIVE
+  void LoadSeriesTags(int i)
+  {
+    try
+    {
+      if (i < 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      Orthanc::DicomMap dicom;
+      GetResourcesLoader().GetSeries(dicom, i);
+      FormatTags(stringBuffer_, dicom);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+  
+
+  EMSCRIPTEN_KEEPALIVE
+  int LoadSeriesThumbnail(const char* seriesInstanceUid)
+  {
+    try
+    {
+      std::string image, mime;
+      switch (GetResourcesLoader().GetSeriesThumbnail(image, mime, seriesInstanceUid))
+      {
+        case OrthancStone::SeriesThumbnailType_Image:
+          Orthanc::Toolbox::EncodeDataUriScheme(stringBuffer_, mime, image);
+          return ThumbnailType_Image;
+          
+        case OrthancStone::SeriesThumbnailType_Pdf:
+          return ThumbnailType_Pdf;
+          
+        case OrthancStone::SeriesThumbnailType_Video:
+          return ThumbnailType_Video;
+          
+        case OrthancStone::SeriesThumbnailType_NotLoaded:
+          return ThumbnailType_Loading;
+          
+        case OrthancStone::SeriesThumbnailType_Unsupported:
+          return ThumbnailType_NoPreview;
+
+        default:
+          return ThumbnailType_Unknown;
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+    return ThumbnailType_Unknown;
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void SpeedUpFetchSeriesMetadata(const char* studyInstanceUid,
+                                  const char* seriesInstanceUid)
+  {
+    try
+    {
+      GetResourcesLoader().FetchSeriesMetadata(PRIORITY_HIGH, studyInstanceUid, seriesInstanceUid);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  int IsSeriesComplete(const char* seriesInstanceUid)
+  {
+    try
+    {
+      return GetResourcesLoader().IsSeriesComplete(seriesInstanceUid) ? 1 : 0;
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+    return 0;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  int LoadSeriesInViewport(const char* canvas,
+                           const char* seriesInstanceUid)
+  {
+    try
+    {
+      std::unique_ptr<OrthancStone::SortedFrames> frames(new OrthancStone::SortedFrames);
+      
+      if (GetResourcesLoader().SortSeriesFrames(*frames, seriesInstanceUid))
+      {
+        GetViewport(canvas)->SetFrames(frames.release());
+        return 1;
+      }
+      else
+      {
+        return 0;
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+    return 0;
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void AllViewportsUpdateSize(int fitContent)
+  {
+    try
+    {
+      for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        it->second->UpdateSize(fitContent != 0);
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void DecrementFrame(const char* canvas,
+                      int fitContent)
+  {
+    try
+    {
+      GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Minus);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void IncrementFrame(const char* canvas,
+                      int fitContent)
+  {
+    try
+    {
+      GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Plus);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void ShowReferenceLines(int show)
+  {
+    try
+    {
+      showReferenceLines_ = (show != 0);
+      UpdateReferenceLines();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetDefaultWindowing(const char* canvas)
+  {
+    try
+    {
+      GetViewport(canvas)->SetDefaultWindowing();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetWindowing(const char* canvas,
+                    int center,
+                    int width)
+  {
+    try
+    {
+      GetViewport(canvas)->SetWindowing(center, width);
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void InvertContrast(const char* canvas)
+  {
+    try
+    {
+      GetViewport(canvas)->Invert();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/docker-build.sh	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -ex
+
+IMAGE=jodogne/wasm-builder:1.39.17-upstream
+#IMAGE=wasm-builder
+
+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}/wasm-binaries
+
+docker run -t ${DOCKER_FLAGS} --rm \
+    --user $(id -u):$(id -g) \
+    -v ${ROOT_DIR}:/source:ro \
+    -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \
+    bash /source/StoneWebViewer/WebAssembly/docker-internal.sh $1
+
+ls -lR ${ROOT_DIR}/wasm-binaries/StoneWebViewer/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/docker-internal.sh	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -ex
+
+source /opt/emsdk/emsdk_env.sh
+
+# Use a folder that is writeable by non-root users for the Emscripten cache
+export EM_CACHE=/tmp/emscripten-cache
+
+# Get the Orthanc framework
+cd /tmp/
+hg clone https://hg.orthanc-server.com/orthanc/
+
+# Make a copy of the read-only folder containing the source code into
+# a writeable folder, because of "DownloadPackage.cmake" that writes
+# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt"
+cd /source
+hg clone /source /tmp/source-writeable
+
+mkdir /tmp/build
+cd /tmp/build
+
+cmake /tmp/source-writeable/StoneWebViewer/WebAssembly \
+      -DCMAKE_BUILD_TYPE=$1 \
+      -DCMAKE_INSTALL_PREFIX=/target/StoneWebViewer \
+      -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
+      -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \
+      -DSTATIC_BUILD=ON \
+      -G Ninja
+
+ninja -j2 install
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Docs/Conventions.txt	Tue Aug 11 13:24:38 2020 +0200
@@ -0,0 +1,88 @@
+
+Some notes about the lifetime of objects
+========================================
+
+Stone applications
+------------------
+
+A typical Stone application can be split in 3 parts:
+
+1- The "loaders part" and the associated "IOracle", that communicate
+   through "IMessage" objects. The lifetime of these objects is
+   governed by the "IStoneContext".
+
+2- The "data part" holds the data loaded by the "loaders part". The
+   related objects must not be aware of the oracle, neither of the
+   messages. It is up to the user application to store these objects.
+
+3- The "viewport part" is based upon the "Scene2D" class.
+
+
+Multithreading
+--------------
+
+* Stone makes the hypothesis that its objects live in a single thread.
+  All the content of the "Framework" folder (with the exception of
+  the "Oracle" stuff) must not use "boost::thread".
+
+* The "IOracleCommand" classes represent commands that must be
+  executed asynchronously from the Stone thread. Their actual
+  execution is done by the "IOracle".
+
+* In WebAssembly, the "IOracle" corresponds to the "html5.h"
+  facilities (notably for the Fetch API). There is no mutex here, as
+  JavaScript is inherently single-threaded.
+
+* In plain C++ applications, the "IOracle" corresponds to a FIFO queue
+  of commands that are executed by a pool of threads. The Stone
+  context holds a global mutex, that must be properly locked by the
+  user application, and by the "IOracle" when it sends back messages
+  to the Stone loaders (cf. class "IMessageEmitter").
+
+* Multithreading is thus achieved by defining new oracle commands by
+  subclassing "IOracleCommand", then by defining a way to execute them
+  (cf. class "GenericCommandRunner").
+
+
+References between objects
+--------------------------
+
+* An object allocated on the heap must never store a reference/pointer
+  to another object.
+
+* A class designed to be allocated only on the stack can store a
+  reference/pointer to another object. Here is the list of
+  such classes:
+
+  - IMessage and its derived classes: All the messages are allocated
+    on the stack.
+
+
+Pointers
+--------
+
+* As we are targeting C++03 (for VS2008 and LSB compatibility), use
+  "std::unique_ptr<>" and "boost::shared_ptr<>" (*not*
+  "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for
+  pre-C++11 compilers.
+
+* The fact of transfering the ownership of one object to another must
+  be tagged by naming the method "Acquire...()", and by providing a
+  raw pointer.
+
+* Use "std::unique_ptr<>" if the goal is to internally store a pointer
+  whose lifetime corresponds to the host object.
+
+* The use of "boost::weak_ptr<>" should be restricted to
+  oracle/message handling.
+
+* The use of "boost::shared_ptr<>" should be minimized to avoid
+  clutter. The "loaders" and "data parts" objects must however
+  be created as "boost::shared_ptr<>".
+
+
+Global context
+--------------
+
+* As the global Stone context can be created/destroyed by other
+  languages than C++, we don't use a "boost:shared_ptr<>".
--- a/OrthancStone/Resources/Conventions.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-
-Some notes about the lifetime of objects
-========================================
-
-Stone applications
-------------------
-
-A typical Stone application can be split in 3 parts:
-
-1- The "loaders part" and the associated "IOracle", that communicate
-   through "IMessage" objects. The lifetime of these objects is
-   governed by the "IStoneContext".
-
-2- The "data part" holds the data loaded by the "loaders part". The
-   related objects must not be aware of the oracle, neither of the
-   messages. It is up to the user application to store these objects.
-
-3- The "viewport part" is based upon the "Scene2D" class.
-
-
-Multithreading
---------------
-
-* Stone makes the hypothesis that its objects live in a single thread.
-  All the content of the "Framework" folder (with the exception of
-  the "Oracle" stuff) must not use "boost::thread".
-
-* The "IOracleCommand" classes represent commands that must be
-  executed asynchronously from the Stone thread. Their actual
-  execution is done by the "IOracle".
-
-* In WebAssembly, the "IOracle" corresponds to the "html5.h"
-  facilities (notably for the Fetch API). There is no mutex here, as
-  JavaScript is inherently single-threaded.
-
-* In plain C++ applications, the "IOracle" corresponds to a FIFO queue
-  of commands that are executed by a pool of threads. The Stone
-  context holds a global mutex, that must be properly locked by the
-  user application, and by the "IOracle" when it sends back messages
-  to the Stone loaders (cf. class "IMessageEmitter").
-
-* Multithreading is thus achieved by defining new oracle commands by
-  subclassing "IOracleCommand", then by defining a way to execute them
-  (cf. class "GenericCommandRunner").
-
-
-References between objects
---------------------------
-
-* An object allocated on the heap must never store a reference/pointer
-  to another object.
-
-* A class designed to be allocated only on the stack can store a
-  reference/pointer to another object. Here is the list of
-  such classes:
-
-  - IMessage and its derived classes: All the messages are allocated
-    on the stack.
-
-
-Pointers
---------
-
-* As we are targeting C++03 (for VS2008 and LSB compatibility), use
-  "std::unique_ptr<>" and "boost::shared_ptr<>" (*not*
-  "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for
-  pre-C++11 compilers.
-
-* The fact of transfering the ownership of one object to another must
-  be tagged by naming the method "Acquire...()", and by providing a
-  raw pointer.
-
-* Use "std::unique_ptr<>" if the goal is to internally store a pointer
-  whose lifetime corresponds to the host object.
-
-* The use of "boost::weak_ptr<>" should be restricted to
-  oracle/message handling.
-
-* The use of "boost::shared_ptr<>" should be minimized to avoid
-  clutter. The "loaders" and "data parts" objects must however
-  be created as "boost::shared_ptr<>".
-
-
-Global context
---------------
-
-* As the global Stone context can be created/destroyed by other
-  languages than C++, we don't use a "boost:shared_ptr<>".
--- a/OrthancStone/Samples/Common/RtViewerApp.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-/**
- * Stone of Orthanc
- * 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/>.
- **/
-
-// Sample app
-#include "RtViewerApp.h"
-#include "RtViewerView.h"
-#include "SampleHelpers.h"
-
-// Stone of Orthanc
-#include "../../Sources/StoneInitialization.h"
-#include "../../Sources/Scene2D/CairoCompositor.h"
-#include "../../Sources/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Sources/Scene2D/OpenGLCompositor.h"
-#include "../../Sources/Scene2D/PanSceneTracker.h"
-#include "../../Sources/Scene2D/ZoomSceneTracker.h"
-#include "../../Sources/Scene2D/RotateSceneTracker.h"
-#include "../../Sources/Scene2DViewport/UndoStack.h"
-#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h"
-#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h"
-#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h"
-#include "../../Sources/Scene2DViewport/MeasureTool.h"
-#include "../../Sources/Scene2DViewport/PredeclaredTypes.h"
-#include "../../Sources/Volumes/VolumeSceneLayerSource.h"
-#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h"
-#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h"
-#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h"
-#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h"
-#include "../../Sources/StoneException.h"
-
-// Orthanc
-#include <Logging.h>
-#include <OrthancException.h>
-
-// System 
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-#include <boost/make_shared.hpp>
-
-#include <stdio.h>
-
-
-namespace OrthancStone
-{
-  void RtViewerApp::InvalidateAllViewports()
-  {
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->Invalidate();
-    }
-  }
-
-  const VolumeImageGeometry& RtViewerApp::GetMainGeometry()
-  {
-    ORTHANC_ASSERT(geometryProvider_.get() != NULL);
-    ORTHANC_ASSERT(geometryProvider_->HasGeometry());
-    const VolumeImageGeometry& geometry = geometryProvider_->GetImageGeometry();
-    return geometry;
-  }
-
-  RtViewerApp::RtViewerApp()
-    : undoStack_(new UndoStack)
-  {
-    // Create the volumes that will be filled later on
-    ctVolume_ = boost::make_shared<DicomVolumeImage>();
-    doseVolume_ = boost::make_shared<DicomVolumeImage>();
-  }
-
-  boost::shared_ptr<RtViewerApp> RtViewerApp::Create()
-  {
-    boost::shared_ptr<RtViewerApp> thisOne(new RtViewerApp());
-    return thisOne;
-  }
-
-  void RtViewerApp::DisableTracker()
-  {
-    if (activeTracker_)
-    {
-      activeTracker_->Cancel();
-      activeTracker_.reset();
-    }
-  }
-
-  void RtViewerApp::CreateView(const std::string& canvasId, VolumeProjection projection)
-  {
-    boost::shared_ptr<RtViewerView> 
-      view(new RtViewerView(shared_from_this(), canvasId, projection));
-
-    view->RegisterMessages();
-
-    view->CreateLayers(ctLoader_, doseLoader_, doseVolume_, rtstructLoader_);
-
-    views_.push_back(view);
-  }
-
-  void RtViewerApp::CreateLoaders()
-  {
-    // the viewport hosts the scene
-    {
-      // "true" means use progressive quality (jpeg 50 --> jpeg 90 --> 16-bit raw)
-      // "false" means only using hi quality
-      // TODO: add flag for quality
-      ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, true);
-      
-      // better priority for CT vs dose and struct
-      ctLoader_->SetSchedulingPriority(-100);
-
-
-      // we need to store the CT loader to ask from geometry details later on when geometry is loaded
-      geometryProvider_ = ctLoader_;
-
-      doseLoader_ = OrthancMultiframeVolumeLoader::Create(*loadersContext_, doseVolume_);
-      rtstructLoader_ = DicomStructureSetLoader::Create(*loadersContext_);
-    }
-
-    /**
-    Register for notifications issued by the loaders
-    */
-
-    Register<DicomVolumeImage::GeometryReadyMessage>
-      (*ctLoader_, &RtViewerApp::HandleGeometryReady);
-
-    Register<OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality>
-      (*ctLoader_, &RtViewerApp::HandleCTLoaded);
-
-    Register<DicomVolumeImage::ContentUpdatedMessage>
-      (*ctLoader_, &RtViewerApp::HandleCTContentUpdated);
-
-    Register<DicomVolumeImage::ContentUpdatedMessage>
-      (*doseLoader_, &RtViewerApp::HandleDoseLoaded);
-
-    Register<DicomStructureSetLoader::StructuresReady>
-      (*rtstructLoader_, &RtViewerApp::HandleStructuresReady);
-
-    Register<DicomStructureSetLoader::StructuresUpdated>
-      (*rtstructLoader_, &RtViewerApp::HandleStructuresUpdated);
-  }
-
-  void RtViewerApp::StartLoaders()
-  {
-    ORTHANC_ASSERT(HasArgument("ctseries") && HasArgument("rtdose") && HasArgument("rtstruct"));
-
-    LOG(INFO) << "About to load:";
-
-    if (GetArgument("ctseries") == "")
-    {
-      LOG(INFO) << "  CT       : <unspecified>";
-    }
-    else
-    {
-      LOG(INFO) << "  CT       : " << GetArgument("ctseries");
-      ctLoader_->LoadSeries(GetArgument("ctseries"));
-    }
-    
-    if (GetArgument("rtdose") == "")
-    {
-      LOG(INFO) << "  RTDOSE   : <unspecified>";
-    }
-    else
-    {
-      LOG(INFO) << "  RTDOSE   : " << GetArgument("rtdose");
-      doseLoader_->LoadInstance(GetArgument("rtdose"));
-    }
-
-    if (GetArgument("rtstruct") == "")
-    {
-      LOG(INFO) << "  RTSTRUCT : : <unspecified>";
-    }
-    else
-    {
-      LOG(INFO) << "  RTSTRUCT : : " << GetArgument("rtstruct");
-      rtstructLoader_->LoadInstanceFullVisibility(GetArgument("rtstruct"));
-    }
-  }
-
-  void RtViewerApp::HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message)
-  {
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->RetrieveGeometry();
-    }
-    FitContent();
-    UpdateLayersInAllViews();
-  }
-
-  void RtViewerApp::FitContent()
-  {
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->FitContent();
-    }
-  }
-
-  void RtViewerApp::UpdateLayersInAllViews()
-  {
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->UpdateLayers();
-    }
-  }
-
-  void RtViewerApp::HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message)
-  {
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->RetrieveGeometry();
-    }
-    UpdateLayersInAllViews();
-  }
-
-  void RtViewerApp::HandleCTContentUpdated(const DicomVolumeImage::ContentUpdatedMessage& message)
-  {
-    UpdateLayersInAllViews();
-  }
-
-  void RtViewerApp::HandleDoseLoaded(const DicomVolumeImage::ContentUpdatedMessage& message)
-  {
-    //TODO: compute dose extent, with outlier rejection
-    UpdateLayersInAllViews();
-  }
-
-  void RtViewerApp::HandleStructuresReady(const DicomStructureSetLoader::StructuresReady& message)
-  {
-    UpdateLayersInAllViews();
-  }
-
-  void RtViewerApp::HandleStructuresUpdated(const DicomStructureSetLoader::StructuresUpdated& message)
-  {
-    UpdateLayersInAllViews();
-  }
-
-  void RtViewerApp::SetArgument(const std::string& key, const std::string& value)
-  {
-    if (key == "loglevel")
-      OrthancStoneHelpers::SetLogLevel(value);
-    else
-      arguments_[key] = value;
-  }
-
-  std::string RtViewerApp::GetArgument(const std::string& key) const
-  {
-    std::map<std::string, std::string>::const_iterator found = arguments_.find(key);
-    if (found == arguments_.end())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return found->second;
-    }
-  }
-
-  bool RtViewerApp::HasArgument(const std::string& key) const
-  {
-    return (arguments_.find(key) != arguments_.end());
-  }
-}
-
--- a/OrthancStone/Samples/Common/RtViewerApp.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../Sources/Loaders/DicomStructureSetLoader.h"
-#include "../../Sources/Loaders/ILoadersContext.h"
-#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h"
-#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
-#include "../../Sources/Messages/IMessageEmitter.h"
-#include "../../Sources/Messages/IObserver.h"
-#include "../../Sources/Messages/ObserverBase.h"
-#include "../../Sources/Oracle/OracleCommandExceptionMessage.h"
-#include "../../Sources/Scene2DViewport/ViewportController.h"
-#include "../../Sources/Viewport/IViewport.h"
-#include "../../Sources/Volumes/DicomVolumeImage.h"
-
-#include <boost/enable_shared_from_this.hpp>
-#include <boost/thread.hpp>
-#include <boost/noncopyable.hpp>
-
-#if ORTHANC_ENABLE_SDL
-#include <SDL.h>
-#endif
-
-namespace OrthancStone
-{
-  class OpenGLCompositor;
-  class IVolumeSlicer;
-  class ILayerStyleConfigurator;
-  class DicomStructureSetLoader;
-  class IOracle;
-  class ThreadedOracle;
-  class VolumeSceneLayerSource;
-  class SdlOpenGLViewport;
-  class RtViewerView;
-   
-  static const unsigned int FONT_SIZE_0 = 32;
-  static const unsigned int FONT_SIZE_1 = 24;
-
-  class Scene2D;
-  class UndoStack;
-
-  /**
-  This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that
-  can be sent from multiple threads)
-  */
-  class RtViewerApp : public ObserverBase<RtViewerApp>
-  {
-  public:
-
-    void PrepareScene();
-
-#if ORTHANC_ENABLE_SDL
-  public:
-    void RunSdl(int argc, char* argv[]);
-    void SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
-                    OrthancStone::DefaultViewportInteractor& interactor);
-  private:
-    void ProcessOptions(int argc, char* argv[]);
-    void HandleApplicationEvent(const SDL_Event& event);
-#elif ORTHANC_ENABLE_WASM
-  public:
-    void RunWasm();
-#else
-#  error Either ORTHANC_ENABLE_SDL or ORTHANC_ENABLE_WASM must be enabled
-#endif
-
-  public:
-    void DisableTracker();
-
-    /**
-    Called by command-line option processing or when parsing the URL 
-    parameters.
-    */
-    void SetArgument(const std::string& key, const std::string& value);
-
-    const VolumeImageGeometry& GetMainGeometry();
-
-    static boost::shared_ptr<RtViewerApp> Create();
-
-    void CreateView(const std::string& canvasId, VolumeProjection projection);
-
-  protected:
-    RtViewerApp();
-
-  private:
-    void CreateLoaders();
-    void StartLoaders();
-    void SelectNextTool();
-
-    // argument handling
-    // SetArgument is above (public section)
-    std::map<std::string, std::string> arguments_;
-
-    std::string GetArgument(const std::string& key) const;
-    bool HasArgument(const std::string& key) const;
-
-    /**
-      This adds the command at the top of the undo stack
-    */
-    //void Commit(boost::shared_ptr<TrackerCommand> cmd);
-    void Undo();
-    void Redo();
-
-    void HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message);
-    
-    // TODO: wire this
-    void HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message);
-    void HandleCTContentUpdated(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message);
-    void HandleDoseLoaded(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message);
-    void HandleStructuresReady(const OrthancStone::DicomStructureSetLoader::StructuresReady& message);
-    void HandleStructuresUpdated(const OrthancStone::DicomStructureSetLoader::StructuresUpdated& message);
-
-
-  private:
-    void RetrieveGeometry();
-    void FitContent();
-    void InvalidateAllViewports();
-    void UpdateLayersInAllViews();
-
-  private:
-    boost::shared_ptr<DicomVolumeImage>  ctVolume_;
-    boost::shared_ptr<DicomVolumeImage>  doseVolume_;
-
-    std::vector<boost::shared_ptr<RtViewerView> >  views_;
-
-    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader_;
-    boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader_;
-    boost::shared_ptr<DicomStructureSetLoader>  rtstructLoader_;
-
-    /** encapsulates resources shared by loaders */
-    boost::shared_ptr<ILoadersContext>                  loadersContext_;
-
-    /**
-    another interface to the ctLoader object (that also implements the IVolumeSlicer interface), that serves as the 
-    reference for the geometry (position and dimensions of the volume + size of each voxel). It could be changed to be 
-    the dose instead, but the CT is chosen because it usually has a better spatial resolution.
-    */
-    boost::shared_ptr<OrthancStone::IGeometryProvider>  geometryProvider_;
-
-
-    boost::shared_ptr<IFlexiblePointerTracker> activeTracker_;
-
-    boost::shared_ptr<UndoStack> undoStack_;
-  };
-
-}
-
-
- 
--- a/OrthancStone/Samples/Common/RtViewerView.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,352 +0,0 @@
-/**
- * Stone of Orthanc
- * 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/>.
- **/
-
-
-// Sample app
-#include "RtViewerView.h"
-#include "RtViewerApp.h"
-#include "SampleHelpers.h"
-
-#include <EmbeddedResources.h>
-
-// Stone of Orthanc
-#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h"
-#include "../../Sources/Scene2D/CairoCompositor.h"
-#include "../../Sources/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h"
-#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h"
-#include "../../Sources/Scene2D/OpenGLCompositor.h"
-#include "../../Sources/Scene2D/PanSceneTracker.h"
-#include "../../Sources/Scene2D/RotateSceneTracker.h"
-#include "../../Sources/Scene2D/ZoomSceneTracker.h"
-#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h"
-#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h"
-#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h"
-#include "../../Sources/Scene2DViewport/MeasureTool.h"
-#include "../../Sources/Scene2DViewport/PredeclaredTypes.h"
-#include "../../Sources/Scene2DViewport/UndoStack.h"
-#include "../../Sources/StoneException.h"
-#include "../../Sources/StoneInitialization.h"
-#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h"
-#include "../../Sources/Volumes/VolumeSceneLayerSource.h"
-
-// Orthanc
-#include <Compatibility.h>  // For std::unique_ptr<>
-#include <Logging.h>
-#include <OrthancException.h>
-
-// System 
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-#include <boost/make_shared.hpp>
-
-#include <stdio.h>
-
-
-namespace OrthancStone
-{
-  boost::shared_ptr<RtViewerApp> RtViewerView::GetApp()
-  {
-    return app_.lock();
-  }
-
-  void RtViewerView::DisplayInfoText()
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-
-    // do not try to use stuff too early!
-    OrthancStone::ICompositor& compositor = lock->GetCompositor();
-
-    std::stringstream msg;
-
-    for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin();
-         kv != infoTextMap_.end(); ++kv)
-    {
-      msg << kv->first << " : " << kv->second << std::endl;
-    }
-    std::string msgS = msg.str();
-
-    TextSceneLayer* layerP = NULL;
-    if (scene.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
-    {
-      TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>(
-        scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
-      layerP = &layer;
-    }
-    else
-    {
-      std::unique_ptr<TextSceneLayer> layer(new TextSceneLayer);
-      layerP = layer.get();
-      layer->SetColor(0, 255, 0);
-      layer->SetFontIndex(1);
-      layer->SetBorder(20);
-      layer->SetAnchor(BitmapAnchor_TopLeft);
-      //layer->SetPosition(0,0);
-      scene.SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
-    }
-    // position the fixed info text in the upper right corner
-    layerP->SetText(msgS.c_str());
-    double cX = compositor.GetCanvasWidth() * (-0.5);
-    double cY = compositor.GetCanvasHeight() * (-0.5);
-    scene.GetCanvasToSceneTransform().Apply(cX, cY);
-    layerP->SetPosition(cX, cY);
-    lock->Invalidate();
-  }
-
-  void RtViewerView::DisplayFloatingCtrlInfoText(const PointerEvent& e)
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-
-    ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
-
-    char buf[128];
-    sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)",
-            p.GetX(), p.GetY(),
-            e.GetMainPosition().GetX(), e.GetMainPosition().GetY());
-
-    if (scene.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
-    {
-      TextSceneLayer& layer =
-        dynamic_cast<TextSceneLayer&>(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
-      layer.SetText(buf);
-      layer.SetPosition(p.GetX(), p.GetY());
-    }
-    else
-    {
-      std::unique_ptr<TextSceneLayer> layer(new TextSceneLayer);
-      layer->SetColor(0, 255, 0);
-      layer->SetText(buf);
-      layer->SetBorder(20);
-      layer->SetAnchor(BitmapAnchor_BottomCenter);
-      layer->SetPosition(p.GetX(), p.GetY());
-      scene.SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
-    }
-  }
-
-  void RtViewerView::HideInfoText()
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-
-    scene.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
-  }
-
-  void RtViewerView::OnSceneTransformChanged(
-    const ViewportController::SceneTransformChanged& message)
-  {
-    DisplayInfoText();
-  }
-
-  void RtViewerView::Invalidate()
-  {
-    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-    lock->GetCompositor().FitContent(lock->GetController().GetScene());
-    lock->Invalidate();
-  }
-
-  void RtViewerView::FitContent()
-  {
-    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-    lock->GetCompositor().FitContent(lock->GetController().GetScene());
-    lock->Invalidate();
-  }
-
-  void RtViewerView::Scroll(int delta)
-  {
-    if (!planes_.empty())
-    {
-      int next = 0;
-      int temp = static_cast<int>(currentPlane_) + delta;
-
-      if (temp < 0)
-      {
-        next = 0;
-      }
-      else if (temp >= static_cast<int>(planes_.size()))
-      {
-        next = static_cast<unsigned int>(planes_.size()) - 1;
-      }
-      else
-      {
-        next = static_cast<size_t>(temp);
-      }
-      LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next;
-
-      if (next != static_cast<int>(currentPlane_))
-      {
-        currentPlane_ = next;
-        UpdateLayers();
-      }
-    }
-  }
-
-  void RtViewerView::RetrieveGeometry()
-  {
-    const VolumeImageGeometry& geometry = GetApp()->GetMainGeometry();
-
-    const unsigned int depth = geometry.GetProjectionDepth(projection_);
-    currentPlane_ = depth / 2;
-
-    planes_.resize(depth);
-
-    for (unsigned int z = 0; z < depth; z++)
-    {
-      planes_[z] = geometry.GetProjectionSlice(projection_, z);
-    }
-
-    UpdateLayers();
-  }
-
-  void RtViewerView::UpdateLayers()
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-
-    if (planes_.size() == 0)
-    {
-      RetrieveGeometry();
-    }
-
-    if (currentPlane_ < planes_.size())
-    {
-      if (ctVolumeLayerSource_.get() != NULL)
-      {
-        ctVolumeLayerSource_->Update(planes_[currentPlane_]);
-      }
-      if (doseVolumeLayerSource_.get() != NULL)
-      {
-        doseVolumeLayerSource_->Update(planes_[currentPlane_]);
-      }
-      if (structLayerSource_.get() != NULL)
-      {
-        structLayerSource_->Update(planes_[currentPlane_]);
-      }
-    }
-    lock->Invalidate();
-  }
-
-  void RtViewerView::PrepareViewport()
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    ICompositor& compositor = lock->GetCompositor();
-
-    // False means we do NOT let a hi-DPI aware desktop managedr treat this as a legacy application that requires
-    // scaling.
-    controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight());
-
-    std::string ttf;
-    Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT);
-    compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1);
-    compositor.SetFont(1, ttf, FONT_SIZE_1, Orthanc::Encoding_Latin1);
-  }
-
-  void RtViewerView::SetInfoDisplayMessage(
-    std::string key, std::string value)
-  {
-    if (value == "")
-      infoTextMap_.erase(key);
-    else
-      infoTextMap_[key] = value;
-    DisplayInfoText();
-  }
-
-  void RtViewerView::RegisterMessages()
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Register<ViewportController::SceneTransformChanged>(controller, &RtViewerView::OnSceneTransformChanged);
-  }
-
-  void RtViewerView::CreateLayers(boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader,
-                                  boost::shared_ptr<OrthancMultiframeVolumeLoader>        doseLoader,
-                                  boost::shared_ptr<DicomVolumeImage>                     doseVolume,
-                                  boost::shared_ptr<DicomStructureSetLoader>              rtstructLoader)
-  {
-    /**
-    Configure the CT
-    */
-    std::unique_ptr<GrayscaleStyleConfigurator> style(new GrayscaleStyleConfigurator);
-    style->SetLinearInterpolation(true);
-
-    this->SetCtVolumeSlicer(ctLoader, style.release());
-
-    {
-      std::string lut;
-      Orthanc::EmbeddedResources::GetFileResource(lut, Orthanc::EmbeddedResources::COLORMAP_HOT);
-
-      std::unique_ptr<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator);
-      config->SetLookupTable(lut);
-
-      boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(doseVolume));
-      this->SetDoseVolumeSlicer(tmp, config.release());
-    }
-
-    this->SetStructureSet(rtstructLoader);
-  }
-
-  void RtViewerView::SetCtVolumeSlicer(const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
-                                       OrthancStone::ILayerStyleConfigurator* style)
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-    int depth = scene.GetMaxDepth() + 1;
-
-    ctVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume));
-
-    if (style != NULL)
-    {
-      ctVolumeLayerSource_->SetConfigurator(style);
-    }
-
-    ctLayer_ = depth;
-  }
-
-  void RtViewerView::SetDoseVolumeSlicer(const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
-                                         OrthancStone::ILayerStyleConfigurator* style)
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-    int depth = scene.GetMaxDepth() + 1;
-
-    doseVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume));
-
-    if (style != NULL)
-    {
-      doseVolumeLayerSource_->SetConfigurator(style);
-    }
-  }
-
-  void RtViewerView::SetStructureSet(const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-    int depth = scene.GetMaxDepth() + 1;
-
-    structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume));
-  }
-}
--- a/OrthancStone/Samples/Common/RtViewerView.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../Sources/Loaders/DicomStructureSetLoader.h"
-#include "../../Sources/Loaders/ILoadersContext.h"
-#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h"
-#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
-#include "../../Sources/Messages/IMessageEmitter.h"
-#include "../../Sources/Messages/IObserver.h"
-#include "../../Sources/Messages/ObserverBase.h"
-#include "../../Sources/Oracle/OracleCommandExceptionMessage.h"
-#include "../../Sources/Scene2DViewport/ViewportController.h"
-#include "../../Sources/Viewport/IViewport.h"
-#include "../../Sources/Volumes/DicomVolumeImage.h"
-#include "../../Sources/Volumes/VolumeSceneLayerSource.h"
-
-#include <boost/enable_shared_from_this.hpp>
-#include <boost/thread.hpp>
-#include <boost/noncopyable.hpp>
-
-namespace OrthancStone
-{
-  class RtViewerApp;
-
-  class RtViewerView : public ObserverBase<RtViewerView>
-  {
-  public:
-    RtViewerView(boost::weak_ptr<RtViewerApp> app, 
-                 const std::string& canvasId, 
-                 VolumeProjection projection) 
-      : app_(app)
-      , currentPlane_(0)
-      , projection_(projection)
-      , ctLayer_(0)
-    {
-      viewport_ = CreateViewport(canvasId);
-      FLOATING_INFOTEXT_LAYER_ZINDEX = 6;
-      FIXED_INFOTEXT_LAYER_ZINDEX = 7;
-    }
-
-    /**
-    This method is called when the scene transform changes. It allows to
-    recompute the visual elements whose content depend upon the scene transform
-    */
-    void OnSceneTransformChanged(
-      const ViewportController::SceneTransformChanged& message);
-
-    /**
-    This method will ask the VolumeSceneLayerSource, that are responsible to
-    generated 2D content based on a volume and a cutting plane, to regenerate
-    it. This is required if the volume itself changes (during loading) or if
-    the cutting plane is changed
-    */
-    void UpdateLayers();
-
-    void Refresh();
-
-    void TakeScreenshot(
-      const std::string& target,
-      unsigned int canvasWidth,
-      unsigned int canvasHeight);
-
-    void Scroll(int delta);
-
-    void Invalidate();
-    void FitContent();
-    void RetrieveGeometry();
-    void PrepareViewport();
-    void RegisterMessages();
-
-#if ORTHANC_ENABLE_SDL == 1
-    void EnableGLDebugOutput();
-#endif
-
-    void CreateLayers(boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader,
-                      boost::shared_ptr<OrthancMultiframeVolumeLoader>        doseLoader,
-                      boost::shared_ptr<DicomVolumeImage>                     doseVolume,
-                      boost::shared_ptr<DicomStructureSetLoader>              rtstructLoader);
-
-    boost::shared_ptr<IViewport> GetViewport()
-    {
-      return viewport_;
-    }
-
-    int GetCtLayerIndex() const
-    {
-      return ctLayer_;
-    }
-
-  private:
-    void SetInfoDisplayMessage(std::string key, std::string value);
-    boost::shared_ptr<RtViewerApp> GetApp();
-    boost::shared_ptr<IViewport> CreateViewport(const std::string& canvasId);
-    void DisplayInfoText();
-    void HideInfoText();
-    void DisplayFloatingCtrlInfoText(const PointerEvent& e);
-
-    void SetCtVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume,
-                           ILayerStyleConfigurator* style);
-
-    void SetDoseVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume,
-                             ILayerStyleConfigurator* style);
-
-    void SetStructureSet(const boost::shared_ptr<DicomStructureSetLoader>& volume);
-
-  private:
-    boost::weak_ptr<RtViewerApp> app_;
-    boost::shared_ptr<VolumeSceneLayerSource>  ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_;
-
-  // collection of cutting planes for this particular view
-    std::vector<OrthancStone::CoordinateSystem3D>       planes_;
-    size_t                                              currentPlane_;
-
-    VolumeProjection                                    projection_;
-
-    std::map<std::string, std::string> infoTextMap_;
-
-    int FLOATING_INFOTEXT_LAYER_ZINDEX;
-    int FIXED_INFOTEXT_LAYER_ZINDEX;
-    boost::shared_ptr<IViewport> viewport_;
-
-    int ctLayer_;
-  };
-}
--- a/OrthancStone/Samples/Common/SampleHelpers.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 <Logging.h>
-
-#include <boost/algorithm/string.hpp>
-
-#include <string>
-#include <iostream>
-
-namespace OrthancStoneHelpers
-{
-  inline void SetLogLevel(std::string logLevel)
-  {
-    boost::to_lower(logLevel);
-    if (logLevel == "warning")
-    {
-      Orthanc::Logging::EnableInfoLevel(false);
-      Orthanc::Logging::EnableTraceLevel(false);
-    }
-    else if (logLevel == "info")
-    {
-      Orthanc::Logging::EnableInfoLevel(true);
-      Orthanc::Logging::EnableTraceLevel(false);
-    }
-    else if (logLevel == "trace")
-    {
-      Orthanc::Logging::EnableInfoLevel(true);
-      Orthanc::Logging::EnableTraceLevel(true);
-    }
-    else
-    {
-      std::cerr << "Unknown log level \"" << logLevel << "\". Will use TRACE as default!";
-      Orthanc::Logging::EnableInfoLevel(true);
-      Orthanc::Logging::EnableTraceLevel(true);
-    }
-  }
-}
--- a/OrthancStone/Samples/README.md	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,267 +0,0 @@
-General
-=======
-These samples assume that a recent version of Orthanc is checked out in an
-`orthanc` folder next to the `orthanc-stone` folder. Let's call the top folder
-the `devroot` folder. This name does not matter and is not used anywhere.
-
-Here's the directory layout that we suggest:
-
-```
-devroot/
- |
- +- orthanc/
- |
- +- orthanc-stone/
- |
- ...
-```
-
- Orthanc can be retrieved with:
- ```
- hg clone https://hg.orthanc-server.com/orthanc
- ```
-
-Furthermore, the samples usually assume that an Orthanc is running locally,
-without authentication, on port 8042. The samples can easily be tweaked if 
-your setup is different.
-
-When Dicom resources are to be displayed, their IDs can be supplied in the 
-various ways suitable for the platform (command-line arguments, URL parameters
-or through the GUI)
-
-
-This repo contains two sample projects:
-
-SingleFrameViewer
------------------
-
-This sample application displays a single frame of a Dicom instance that can
-be loaded from Orthanc, either by using the Orthanc REST API or through the 
-Dicomweb server functionality of Orthanc.
-
-RtViewer
---------
-
-This sample application displays set of Radiotherapy data:
-- a CT scan
-- an RT-Dose
-- an RT-Struct
-
-The WebAssembly version displays 3 viewports with MPR data
-while the SDL sample displays a single viewport.
-
- 
-WebAssembly samples
-===================
-
-Building the WebAssembly samples require the Emscripten SDK 
-(https://emscripten.org/). This SDK goes far beyond the simple compilation to
-the wasm (Web Assembly) bytecode and provides a comprehensive library that 
-eases porting native C and C++ programs and libraries. The Emscripten SDK also
-makes it easy to generate the companion Javascript files requires to use a 
-wasm module in a web application.
-
-Although Emscripten runs on all major platforms, Stone of Orthanc is developed
-and tested with the Linux version of Emscripten.
-
-Emscripten runs perfectly fine under the Windows Subsystem for Linux (that is
-the environment used quite often by the Stone of Orthanc team)
-
-**Important note:** The following examples **and the build scripts** will 
-assume that you have installed the Emscripten SDK in `~/apps/emsdk`.
-
-The following packages should get you going (a Debian-like distribution such 
-as Debian or Ubuntu is assumed)
-
-```
-sudo apt-get update 
-sudo apt-get install -y build-essential curl wget git python cmake pkg-config
-sudo apt-get install -y mercurial unzip npm ninja-build p7zip-full gettext-base 
-```
-
-To build the Wasm samples, just launch the `build-wasm-samples.sh` script from
-this folder.  Optionaly, you can pass the build type as an argument.
-We suggest that you do *not* use the `Debug` configuration unless you really 
-need it, for the additional checks that are made will lead to a very long 
-build time and much slower execution (more severe than with a native non-wasm
-target)
-
-In order to run the sample, you may serve it with the ServeFolders plugin.
-You can i.e: add such a section in your orthanc configuration file:
-
-```
-{
-  "Plugins" : ["LibServeFolders.so],
-  "ServeFolders" : {
-    "/single-frame-viewer" : "..../out/install-stone-wasm-samples-RelWithDebInfo/SingleFrameViewer",
-    "/rt-viewer": "..../out/install-stone-wasm-samples-RelWithDebInfo/RtViewer"
-  }
-}
-```
-
-You'll then be able to open the single-frame-viewer demo at `http://localhost:8042/single-frame-viewer/index.html` 
-
-The rt-viewer demo at
-`http://localhost:8044/rt-viewer/index.html?ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9`.  Note that you must provide 3 ids in the url:
-
-- the CT series Orthanc ID
-- the RT-Dose instance Orthanc ID
-- the RT-Struct instance Orthanc ID
-
-
-RtViewer
------------------
-
-This sample application displays three MPR views of a CT+RTDOSE+RTSTRUCT dataset, loaded from Orthanc. The Orthanc IDs of the dataset must be supplied as URL parameters like:
-
-```
-http://localhost:9979/stone-rtviewer/index.html?loglevel=info&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
-```
-
-(notice the loglevel parameter that can be `warning`, `info` or `trace`, in increasing verbosity order)
-
-This sample uses plain Javascript and requires the 
-Emscripten toolchain and cmake, in addition to a few standard packages.
-
-To build it, just launch the `build-wasm-RtViewer.sh` script from
-this folder.  Optionaly, you can pass the build type as an argument.
-We suggest that you do *not* use the `Debug` configuration unless you really 
-need it, for the additional checks that are made will lead to a very long 
-build time and much slower execution (more severe than with a native non-wasm
-target)
-
-In order to run the sample, you may serve it with the ServeFolders plugin.
-You can i.e: add such a section in your orthanc configuration file:
-
-```
-{
-  "Plugins" : ["LibServeFolders.so],
-  "ServeFolders" : {
-    "/rt-viewer" : "..../out/install-stone-wasm-RtViewer-RelWithDebInfo"
-  }
-}
-```
-
-You'll then be able to open the demo at `http://localhost:8042/rt-viewer/index.html`
-
-
-RtViewerPlugin
----------------
-This C++ plugin allows to extend the Orthanc Explorer to add a button labeled "Stone RT Viewer" 
-in the series page. 
-
-It also embeds and serves the RT Viewer files and is thus a standalone way of using this viewer.
-
-Please note that building this plugin requires that the RtViewer be built inside the wasm-binaries 
-folder of the repo.
-
-This will automatically be the case if you use the `<REPO-ROOT>/OrthancStone/Samples/WebAssembly/docker-build.sh` script.
-
-If you use the `build-wasm-samples.sh` script above, you will have the copy `RtViewer` **folder**
-from `<REPO-ROOT>/out/install-stone-wasm-RtViewer-RelWithDebInfo` to `<REPO-ROOT>/wasm-binaries/`.
-
-TL;DR: Build like this (assuming `~/orthanc-stone` is the repo ):
-
-```
-~/orthanc-stone/OrthancStone/Samples/WebAssembly/docker-build.sh
-~/orthanc-stone/OrthancStone/Samples/RtViewerPlugin/docker-build.sh
-```
-
-Once this is done, the plugin can be found in:
-
-```
-~/orthanc-stone/wasm-binaries/share/orthanc/plugins/libRtViewerPlugin.so
-```
-
-Add this path to the `"Plugins"` section of your Orthanc configuration, start Orthanc, and you 
-should now see a "Stone MPR RT Viewer" button in the Orthanc Explorer, at the *series* level.
-
-Open it on a CT series, and the RTSTRUCT and RTDOSE series of the same study will be loaded in
-the viewer.
-
-Native samples
-=================
-
-### Windows build 
-
-Here's how to build the SdlSimpleViewer example using Visual Studio 2019
-(the shell is Powershell, but the legacy shell can also be used with some 
-tweaks). This example is meant to be launched from the folder above 
-orthanc-stone.
-
-```
-  # create the build folder and navigate to it
-  $buildDir = "build-stone-sdlviewer-msvc16-x64"
-
-  if (-not (Test-Path $buildDir)) {
-    mkdir -p $buildDir | Out-Null
-  }
-  
-  cd $buildDir
-  
-  # perform the configuration
-  cmake -G "Visual Studio 16 2019" -A x64 `
-    -DMSVC_MULTIPLE_PROCESSES=ON `
-    -DALLOW_DOWNLOADS=ON `
-    -DSTATIC_BUILD=ON `
-    -DOPENSSL_NO_CAPIENG=ON `
-    ../orthanc-stone/OrthancStone/Samples/Sdl
-  
-  $solutionPath = ls -filter *.sln
-  Write-Host "Solution file(s) available at: $solutionPath"
-```
-
-The initial configuration step will be quite lengthy, for CMake needs to 
-setup its internal cache based on your environment and build tools.
-
-Subsequent runs will be several orders of magnitude faster!
-
-One the solution (.sln) file is ready, you can open it using the Visual Studio
-IDE and choose Build --> Build solution.
-
-An alternative is to execute `cmake --build .` in the build folder created by
-the script.
-
-In order to run the sample, make sure you've an Orthanc server running i.e. on 
-port 8042 and launch:
-
-```
-./SdlSimpleViewer --orthanc http://localhost:8042 --instance 7fc84013-abef174e-3354ca83-b9cdb2a4-f1a74368
-
-./RtViewerSdl --orthanc http://localhost:8042 --ctseries a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --rtdose 830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --rtstruct 54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
-```
-
-RtViewer
----------------
-
-### Windows build 
-
-Here's how to build the SdlSimpleViewer example using Visual Studio 2019
-(the shell is Powershell, but the legacy shell can also be used with some 
-tweaks). This example is meant to be launched from the folder above 
-orthanc-stone.
-
-```
-  $buildRootDir = "out"
-  $buildDirName = "build-stone-sdl-RtViewer-msvc16-x64"
-  $buildDir = Join-Path -Path $buildRootDir -ChildPath $buildDirName
-
-  if (-not (Test-Path $buildDir)) {
-    mkdir -p $buildDir | Out-Null
-  }
-  
-  cd $buildDir
-  
-  cmake -G "Visual Studio 16 2019" -A x64 `
-    -DMSVC_MULTIPLE_PROCESSES=ON `
-    -DALLOW_DOWNLOADS=ON `
-    -DSTATIC_BUILD=ON `
-    -DOPENSSL_NO_CAPIENG=ON `
-    ../../orthanc-stone/OrthancStone/Samples/Sdl/RtViewer
-```
-
-Executing `cmake --build .` in the build folder will launch the Microsoft 
-builder on the solution.
-
-Alternatively, you can open and build the solution using Visual Studio 2019.
-
--- a/OrthancStone/Samples/RtViewerPlugin/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-cmake_minimum_required(VERSION 2.8.3)
-
-project(StoneWebViewerPlugin)
-
-set(ORTHANC_PLUGIN_VERSION "mainline")
-
-if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
-  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
-else()
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2")
-  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
-endif()
-
-set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/RtViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder")
-
-# 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 \"system\", \"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_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
-set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked")
-mark_as_advanced(ORTHANC_FRAMEWORK_STATIC)
-
-
-# Download and setup the Orthanc framework
-include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
-
-include_directories(${ORTHANC_FRAMEWORK_ROOT})
-
-if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES})
-
-else()
-  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
-  set(ENABLE_MODULE_IMAGES OFF)
-  set(ENABLE_MODULE_JOBS OFF)
-  set(ENABLE_MODULE_DICOM OFF)
-  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
-endif()
-
-include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake)
-
-
-if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
-  include_directories(${CMAKE_SOURCE_DIR}/Resources/OrthancSdk-1.0.0)
-else ()
-  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
-  if (NOT HAVE_ORTHANC_H)
-    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
-  endif()
-endif()
-
-
-add_definitions(
-  -DHAS_ORTHANC_EXCEPTION=1
-  -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
-  -DPLUGIN_NAME="stone-rtviewer"
-  )
-
-
-EmbedResources(
-  # Web Viewer Folders
-  # IMAGES                 ${STONE_BINARIES_WEB_VIEWER}/img/
-  # WEB_APPLICATION        ${CMAKE_SOURCE_DIR}/../WebApplication
-
-  # Explorer extension code
-  ORTHANC_EXPLORER       ${CMAKE_SOURCE_DIR}/OrthancExplorer.js
-
-  # RtViewer individual files
-  RT_VIEWER_WASM_JS      ${STONE_BINARIES}/RtViewerWasm.js
-  RT_VIEWER_WASM         ${STONE_BINARIES}/RtViewerWasm.wasm
-  RT_VIEWER_WASM_APP_JS  ${STONE_BINARIES}/RtViewerWasmApp.js
-  RT_VIEWER_INDEX_HTML   ${STONE_BINARIES}/index.html
-  )
-
-add_library(RtViewerPlugin SHARED
-  Plugin.cpp
-  ${AUTOGENERATED_SOURCES}
-  ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
-  ${ORTHANC_CORE_SOURCES}
-  )
-
-set_target_properties(RtViewerPlugin PROPERTIES 
-  VERSION ${ORTHANC_PLUGIN_VERSION} 
-  SOVERSION ${ORTHANC_PLUGIN_VERSION})
-
-install(
-  TARGETS RtViewerPlugin
-  RUNTIME DESTINATION lib    # Destination for Windows
-  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
-  )
--- a/OrthancStone/Samples/RtViewerPlugin/OrthancExplorer.js	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-$('#series').live('pagebeforecreate', function() {
-  var b = $('<a>')
-      .attr('data-role', 'button')
-      .attr('href', '#')
-      .attr('data-icon', 'search')
-      .attr('data-theme', 'e')
-      .text('Stone MPR RT Sample Viewer');``
-
-  b.insertBefore($('#series-delete').parent().parent());
-  b.click(function() {
-    if ($.mobile.pageData) {
-      $.ajax({
-        url: '../series/' + $.mobile.pageData.uuid,
-        dataType: 'json',
-        cache: false,
-        success: function(series) {
-
-          // we consider that the imaging series to display is the 
-          // current one.
-          // we will look for RTDOSE and RTSTRUCT instances in the 
-          // sibling series from the same study. The first one of 
-          // each modality will be grabbed.
-          let ctSeries = $.mobile.pageData.uuid;
-
-          $.ajax({
-            url: '../studies/' + series.ParentStudy,
-            dataType: 'json',
-            cache: false,
-            success: function(study) {
-              // Loop on the study series and find the first RTSTRUCT and RTDOSE instances,
-              // if any.
-              let rtStructInstance = null;
-              let rtDoseInstance = null;
-              let rtPetInstance = null;
-              let seriesRequests = []
-
-              study.Series.forEach( function(studySeriesUuid) {
-                let request = $.ajax({
-                  url: '../series/' + studySeriesUuid,
-                  dataType: 'json',
-                  cache: false,
-                });
-                seriesRequests.push(request);
-              });
-
-              $.when.apply($,seriesRequests).then(function() {
-                [].forEach.call(arguments, function(response) {
-                  siblingSeries = response[0]
-                  if (siblingSeries.MainDicomTags.Modality == "RTDOSE") {
-                    // we have found an RTDOSE series. Let's grab the first instance
-                    if (siblingSeries.Instances.length > 0) {
-                      if(rtDoseInstance == null) {
-                        rtDoseInstance = siblingSeries.Instances[0];
-                      }
-                    }
-                  }
-                  if (siblingSeries.MainDicomTags.Modality == "PT") {
-                    // we have found an RTDOSE series. Let's grab the first instance
-                    if (siblingSeries.Instances.length > 0) {
-                      if(rtPetInstance == null) {
-                        rtPetInstance = siblingSeries.Instances[0];
-                      }
-                    }
-                  }
-                  if (siblingSeries.MainDicomTags.Modality == "RTSTRUCT") {
-                    // we have found an RTDOSE series. Let's grab the first instance
-                    if (siblingSeries.Instances.length > 0) {
-                      if(rtStructInstance == null) {
-                        rtStructInstance = siblingSeries.Instances[0];
-                      }
-                    }
-                  }
-                });
-                let mprViewerUrl = '../stone-rtviewer/index.html?ctseries=' + ctSeries + 
-                '&rtdose=' + rtDoseInstance + 
-                '&rtstruct=' + rtStructInstance;
-                //console.log("About to open: " + mprViewerUrl);
-                window.open(mprViewerUrl);
-              });
-            }
-          });      
-        }
-      });      
-    }
-  });
-});
-
--- a/OrthancStone/Samples/RtViewerPlugin/Plugin.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
-
-#include <EmbeddedResources.h>
-
-#include <SystemToolbox.h>
-#include <Toolbox.h>
-
-OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
-                                        OrthancPluginResourceType resourceType,
-                                        const char* resourceId)
-{
-  try
-  {
-    if (changeType == OrthancPluginChangeType_OrthancStarted)
-    {
-      Json::Value info;
-      if (!OrthancPlugins::RestApiGet(info, "/plugins/web-viewer", false))
-      {
-        throw Orthanc::OrthancException(
-          Orthanc::ErrorCode_InternalError,
-          "The Stone MPR RT viewer requires the Web Viewer plugin to be installed");
-      }
-    }
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "Exception: " << e.What();
-    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-  }
-
-  return OrthancPluginErrorCode_Success;
-}
-
-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);
-  }
-}
-
-
-template <enum Orthanc::EmbeddedResources::FileResourceId file>
-void ServeEmbeddedFile(OrthancPluginRestOutput* output,
-                       const char* url,
-                       const OrthancPluginHttpRequest* request)
-{
-  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
-
-  if (request->method != OrthancPluginHttpMethod_Get)
-  {
-    OrthancPluginSendMethodNotAllowed(context, output, "GET");
-  }
-  else
-  {
-    const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url));
-
-    std::string s;
-    Orthanc::EmbeddedResources::GetFileResource(s, file);
-
-    const char* resource = s.size() ? s.c_str() : NULL;
-    OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime);
-  }
-}
-
-
-extern "C"
-{
-  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
-  {
-    OrthancPlugins::SetGlobalContext(context);
-
-#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2)
-    Orthanc::Logging::InitializePluginContext(context);
-#else
-    Orthanc::Logging::Initialize(context);
-#endif
-
-    /* Check the version of the Orthanc core */
-    if (OrthancPluginCheckVersion(context) == 0)
-    {
-      char info[1024];
-      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
-              context->orthancVersion,
-              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
-      OrthancPluginLogError(context, info);
-      return -1;
-    }
-
-    try
-    {
-      std::string explorer;
-      Orthanc::EmbeddedResources::GetFileResource(
-        explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
-      OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str());
-      
-      // RtViewer files below.
-      // ---------------------
-      // we do not serve the whole directory at once (with ServeEmbeddedFolder)
-      // because it contains uppercase characters that are forbidden by the
-      // resource embedding system
-
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_JS> >
-        ("/stone-rtviewer/RtViewerWasm.js", true);
-
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM> >
-        ("/stone-rtviewer/RtViewerWasm.wasm", true);
-
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_APP_JS> >
-        ("/stone-rtviewer/RtViewerWasmApp.js", true);
-
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_INDEX_HTML> >
-        ("/stone-rtviewer/index.html", true);
-
-      OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
-    }
-    catch (...)
-    {
-      OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin");
-      return -1;
-    }
-
-    return 0;
-  }
-
-
-  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
-  {
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
-  {
-    return PLUGIN_NAME;
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
-  {
-    return PLUGIN_VERSION;
-  }
-}
--- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list	Tue Aug 11 12:47:59 2020 +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/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3383 +0,0 @@
-/**
- * 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.
- * 
- * 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 <boost/algorithm/string/predicate.hpp>
-#include <boost/move/unique_ptr.hpp>
-#include <boost/thread.hpp>
-#include <json/reader.h>
-#include <json/writer.h>
-
-
-#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
-static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
-#endif
-
-
-namespace OrthancPlugins
-{
-  static OrthancPluginContext* globalContext_ = NULL;
-
-
-  void SetGlobalContext(OrthancPluginContext* context)
-  {
-    if (context == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
-    }
-    else if (globalContext_ == NULL)
-    {
-      globalContext_ = context;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
-    }
-  }
-
-
-  bool HasGlobalContext()
-  {
-    return globalContext_ != NULL;
-  }
-
-
-  OrthancPluginContext* GetGlobalContext()
-  {
-    if (globalContext_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
-    }
-    else
-    {
-      return globalContext_;
-    }
-  }
-
-
-  void MemoryBuffer::Check(OrthancPluginErrorCode code)
-  {
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      // Prevent using garbage information
-      buffer_.data = NULL;
-      buffer_.size = 0;
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-
-
-  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
-  {
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      // Prevent using garbage information
-      buffer_.data = NULL;
-      buffer_.size = 0;
-    }
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (code == OrthancPluginErrorCode_UnknownResource ||
-             code == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-
-
-  MemoryBuffer::MemoryBuffer()
-  {
-    buffer_.data = NULL;
-    buffer_.size = 0;
-  }
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  MemoryBuffer::MemoryBuffer(const void* buffer,
-                             size_t size)
-  {
-    uint32_t s = static_cast<uint32_t>(size);
-    if (static_cast<size_t>(s) != size)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
-    }
-    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
-             OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
-    }
-    else
-    {
-      memcpy(buffer_.data, buffer, size);
-    }
-  }
-#endif
-
-
-  void MemoryBuffer::Clear()
-  {
-    if (buffer_.data != NULL)
-    {
-      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &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::Swap(MemoryBuffer& other)
-  {
-    std::swap(buffer_.data, other.buffer_.data);
-    std::swap(buffer_.size, other.buffer_.size);
-  }
-
-
-  OrthancPluginMemoryBuffer MemoryBuffer::Release()
-  {
-    OrthancPluginMemoryBuffer result = buffer_;
-
-    buffer_.data = NULL;
-    buffer_.size = 0;
-
-    return result;
-  }
-
-
-  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)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
-
-    Json::Reader reader;
-    if (!reader.parse(tmp, tmp + buffer_.size, target))
-    {
-      LogError("Cannot convert some memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
-    }
-  }
-
-  bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                const std::map<std::string, std::string>& httpHeaders,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    std::vector<const char*> headersKeys;
-    std::vector<const char*> headersValues;
-    
-    for (std::map<std::string, std::string>::const_iterator
-           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
-    {
-      headersKeys.push_back(it->first.c_str());
-      headersValues.push_back(it->second.c_str());
-    }
-
-    return CheckHttp(OrthancPluginRestApiGet2(
-                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
-                       (headersKeys.empty() ? NULL : &headersKeys[0]),
-                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
-  }
-
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const void* body,
-                                 size_t bodySize,
-                                 bool applyPlugins)
-  {
-    Clear();
-    
-    // Cast for compatibility with Orthanc SDK <= 1.5.6
-    const char* b = reinterpret_cast<const char*>(body);
-
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const void* body,
-                                size_t bodySize,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    // Cast for compatibility with Orthanc SDK <= 1.5.6
-    const char* b = reinterpret_cast<const char*>(body);
-
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const Json::Value& body,
-                                 bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPost(uri, writer.write(body), applyPlugins);
-  }
-
-
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const Json::Value& body,
-                                bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPut(uri, writer.write(body), applyPlugins);
-  }
-
-
-  void MemoryBuffer::CreateDicom(const Json::Value& tags,
-                                 OrthancPluginCreateDicomFlags flags)
-  {
-    Clear();
-
-    Json::FastWriter writer;
-    std::string s = writer.write(tags);
-
-    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
-  }
-
-  void MemoryBuffer::CreateDicom(const Json::Value& tags,
-                                 const OrthancImage& pixelData,
-                                 OrthancPluginCreateDicomFlags flags)
-  {
-    Clear();
-
-    Json::FastWriter writer;
-    std::string s = writer.write(tags);
-
-    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
-  }
-
-
-  void MemoryBuffer::ReadFile(const std::string& path)
-  {
-    Clear();
-    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
-  }
-
-
-  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
-  {
-    Clear();
-    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
-  }
-
-
-  void OrthancString::Assign(char* str)
-  {
-    Clear();
-
-    if (str != NULL)
-    {
-      str_ = str;
-    }
-  }
-
-
-  void OrthancString::Clear()
-  {
-    if (str_ != NULL)
-    {
-      OrthancPluginFreeString(GetGlobalContext(), 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)
-    {
-      LogError("Cannot convert an empty memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Reader reader;
-    if (!reader.parse(str_, target))
-    {
-      LogError("Cannot convert some memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  void MemoryBuffer::DicomToJson(Json::Value& target,
-                                 OrthancPluginDicomToJsonFormat format,
-                                 OrthancPluginDicomToJsonFlags flags,
-                                 uint32_t maxStringLength)
-  {
-    OrthancString str;
-    str.Assign(OrthancPluginDicomBufferToJson
-               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
-    str.ToJson(target);
-  }
-
-
-  bool MemoryBuffer::HttpGet(const std::string& url,
-                             const std::string& username,
-                             const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
-                                          username.empty() ? NULL : username.c_str(),
-                                          password.empty() ? NULL : password.c_str()));
-  }
-
-
-  bool MemoryBuffer::HttpPost(const std::string& url,
-                              const std::string& body,
-                              const std::string& username,
-                              const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
-                                           body.c_str(), body.size(),
-                                           username.empty() ? NULL : username.c_str(),
-                                           password.empty() ? NULL : password.c_str()));
-  }
-
-
-  bool MemoryBuffer::HttpPut(const std::string& url,
-                             const std::string& body,
-                             const std::string& username,
-                             const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
-                                          body.empty() ? NULL : body.c_str(),
-                                          body.size(),
-                                          username.empty() ? NULL : username.c_str(),
-                                          password.empty() ? NULL : password.c_str()));
-  }
-
-
-  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
-  {
-    Clear();
-    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
-  }
-
-
-  bool HttpDelete(const std::string& url,
-                  const std::string& username,
-                  const std::string& password)
-  {
-    OrthancPluginErrorCode error = OrthancPluginHttpDelete
-      (GetGlobalContext(), url.c_str(),
-       username.empty() ? NULL : username.c_str(),
-       password.empty() ? NULL : password.c_str());
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-
-
-  void LogError(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogError(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void LogWarning(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void LogInfo(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void OrthancConfiguration::LoadConfiguration()
-  {
-    OrthancString str;
-    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
-
-    if (str.GetContent() == NULL)
-    {
-      LogError("Cannot access the Orthanc configuration");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    str.ToJson(configuration_);
-
-    if (configuration_.type() != Json::objectValue)
-    {
-      LogError("Unable to read the Orthanc configuration");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-    
-
-  OrthancConfiguration::OrthancConfiguration()
-  {
-    LoadConfiguration();
-  }
-
-
-  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
-  {
-    if (loadConfiguration)
-    {
-      LoadConfiguration();
-    }
-    else
-    {
-      configuration_ = Json::objectValue;
-    }
-  }
-
-
-  std::string OrthancConfiguration::GetPath(const std::string& key) const
-  {
-    if (path_.empty())
-    {
-      return key;
-    }
-    else
-    {
-      return path_ + "." + key;
-    }
-  }
-
-
-  bool OrthancConfiguration::IsSection(const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    return (configuration_.isMember(key) &&
-            configuration_[key].type() == Json::objectValue);
-  }
-
-
-  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
-                                        const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.path_ = GetPath(key);
-
-    if (!configuration_.isMember(key))
-    {
-      target.configuration_ = Json::objectValue;
-    }
-    else
-    {
-      if (configuration_[key].type() != Json::objectValue)
-      {
-        LogError("The configuration section \"" + target.path_ +
-                 "\" is not an associative array as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(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)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(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:
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not an integer as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
-                                                        const std::string& key) const
-  {
-    int tmp;
-    if (!LookupIntegerValue(tmp, key))
-    {
-      return false;
-    }
-
-    if (tmp < 0)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a positive integer as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(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)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a Boolean as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(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 = static_cast<float>(configuration_[key].asInt());
-        return true;
-
-      case Json::uintValue:
-        target = static_cast<float>(configuration_[key].asUInt());
-        return true;
-
-      default:
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not an integer as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
-                                                 const std::string& key,
-                                                 bool allowSingleString) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.clear();
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-      case Json::arrayValue:
-      {
-        bool ok = true;
-
-        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
-        {
-          if (configuration_[key][i].type() == Json::stringValue)
-          {
-            target.push_back(configuration_[key][i].asString());
-          }
-          else
-          {
-            ok = false;
-          }
-        }
-
-        if (ok)
-        {
-          return true;
-        }
-
-        break;
-      }
-
-      case Json::stringValue:
-        if (allowSingleString)
-        {
-          target.push_back(configuration_[key].asString());
-          return true;
-        }
-
-        break;
-
-      default:
-        break;
-    }
-
-    LogError("The configuration option \"" + GetPath(key) +
-             "\" is not a list of strings as expected");
-
-    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-  }
-
-
-  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
-                                                const std::string& key,
-                                                bool allowSingleString) const
-  {
-    std::list<std::string> lst;
-
-    if (LookupListOfStrings(lst, key, allowSingleString))
-    {
-      target.clear();
-
-      for (std::list<std::string>::const_iterator
-             it = lst.begin(); it != lst.end(); ++it)
-      {
-        target.insert(*it);
-      }
-
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  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 OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
-                                           const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.clear();
-
-    if (!configuration_.isMember(key))
-    {
-      return;
-    }
-
-    if (configuration_[key].type() != Json::objectValue)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    Json::Value::Members members = configuration_[key].getMemberNames();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& value = configuration_[key][members[i]];
-
-      if (value.type() == Json::stringValue)
-      {
-        target[members[i]] = value.asString();
-      }
-      else
-      {
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not a dictionary mapping strings to strings");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-      }
-    }
-  }
-
-
-  void OrthancImage::Clear()
-  {
-    if (image_ != NULL)
-    {
-      OrthancPluginFreeImage(GetGlobalContext(), image_);
-      image_ = NULL;
-    }
-  }
-
-
-  void OrthancImage::CheckImageAvailable() const
-  {
-    if (image_ == NULL)
-    {
-      LogError("Trying to access a NULL image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancImage::OrthancImage() :
-    image_(NULL)
-  {
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
-    image_(image)
-  {
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height)
-  {
-    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot create an image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height,
-                             uint32_t                  pitch,
-                             void*                     buffer)
-  {
-    image_ = OrthancPluginCreateImageAccessor
-      (GetGlobalContext(), format, width, height, pitch, buffer);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot create an image accessor");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-  void OrthancImage::UncompressPngImage(const void* data,
-                                        size_t size)
-  {
-    Clear();
-
-    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a PNG image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::UncompressJpegImage(const void* data,
-                                         size_t size)
-  {
-    Clear();
-    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a JPEG image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::DecodeDicomImage(const void* data,
-                                      size_t size,
-                                      unsigned int frame)
-  {
-    Clear();
-    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a DICOM image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetWidth() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetHeight() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetPitch() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
-  }
-
-
-  void* OrthancImage::GetBuffer() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
-  }
-
-
-  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
-  {
-    CheckImageAvailable();
-
-    OrthancPlugins::MemoryBuffer answer;
-    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
-                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-
-    target.Swap(answer);
-  }
-
-
-  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
-                                       uint8_t quality) const
-  {
-    CheckImageAvailable();
-
-    OrthancPlugins::MemoryBuffer answer;
-    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
-                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-
-    target.Swap(answer);
-  }
-
-
-  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
-                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-  }
-
-
-  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
-                                     uint8_t quality) const
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
-                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-  }
-
-
-  OrthancPluginImage* OrthancImage::Release()
-  {
-    CheckImageAvailable();
-    OrthancPluginImage* tmp = image_;
-    image_ = NULL;
-    return tmp;
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
-  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
-    matcher_(NULL),
-    worklist_(worklist)
-  {
-    if (worklist_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void FindMatcher::SetupDicom(const void*  query,
-                               uint32_t     size)
-  {
-    worklist_ = NULL;
-
-    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
-    if (matcher_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-
-  FindMatcher::~FindMatcher()
-  {
-    // The "worklist_" field
-
-    if (matcher_ != NULL)
-    {
-      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
-    }
-  }
-
-
-
-  bool FindMatcher::IsMatch(const void*  dicom,
-                            uint32_t     size) const
-  {
-    int32_t result;
-
-    if (matcher_ != NULL)
-    {
-      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
-    }
-    else if (worklist_ != NULL)
-    {
-      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    if (result == 0)
-    {
-      return false;
-    }
-    else if (result == 1)
-    {
-      return true;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
-
-  void AnswerJson(const Json::Value& value,
-                  OrthancPluginRestOutput* output
-    )
-  {
-    Json::StyledWriter writer;
-    std::string bodyString = writer.write(value);
-
-    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
-  }
-
-  void AnswerString(const std::string& answer,
-                    const char* mimeType,
-                    OrthancPluginRestOutput* output
-    )
-  {
-    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
-  }
-
-  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
-  {
-    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
-  }
-
-  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
-  {
-    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
-  }
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToString(result);
-      return true;
-    }
-  }
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        const std::map<std::string, std::string>& httpHeaders,
-                        bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToString(result);
-      return true;
-    }
-  }
-
-
-
-  bool RestApiGet(Json::Value& result,
-                  const std::string& uri,
-                  bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(std::string& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        result.assign(answer.GetData(), answer.GetSize());
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const Json::Value& body,
-                   bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPost(result, uri, writer.write(body), applyPlugins);
-  }
-
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const void* body,
-                  size_t bodySize,
-                  bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const Json::Value& body,
-                  bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPut(result, uri, writer.write(body), applyPlugins);
-  }
-
-
-  bool RestApiDelete(const std::string& uri,
-                     bool applyPlugins)
-  {
-    OrthancPluginErrorCode error;
-
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
-    }
-    else
-    {
-      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-
-
-  void ReportMinimalOrthancVersion(unsigned int major,
-                                   unsigned int minor,
-                                   unsigned int revision)
-  {
-    LogError("Your version of the Orthanc core (" +
-             std::string(GetGlobalContext()->orthancVersion) +
-             ") is too old to run this plugin (version " +
-             boost::lexical_cast<std::string>(major) + "." +
-             boost::lexical_cast<std::string>(minor) + "." +
-             boost::lexical_cast<std::string>(revision) +
-             " is required)");
-  }
-
-
-  bool CheckMinimalOrthancVersion(unsigned int major,
-                                  unsigned int minor,
-                                  unsigned int revision)
-  {
-    if (!HasGlobalContext())
-    {
-      LogError("Bad Orthanc context in the plugin");
-      return false;
-    }
-
-    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
-    {
-      // Assume compatibility with the mainline
-      return true;
-    }
-
-    // Parse the version of the Orthanc core
-    int aa, bb, cc;
-    if (
-#ifdef _MSC_VER
-      sscanf_s
-#else
-      sscanf
-#endif
-      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
-      aa < 0 ||
-      bb < 0 ||
-      cc < 0)
-    {
-      return false;
-    }
-
-    unsigned int a = static_cast<unsigned int>(aa);
-    unsigned int b = static_cast<unsigned int>(bb);
-    unsigned int c = static_cast<unsigned int>(cc);
-
-    // Check the major version number
-
-    if (a > major)
-    {
-      return true;
-    }
-
-    if (a < major)
-    {
-      return false;
-    }
-
-
-    // Check the minor version number
-    assert(a == major);
-
-    if (b > minor)
-    {
-      return true;
-    }
-
-    if (b < minor)
-    {
-      return false;
-    }
-
-    // Check the patch level version number
-    assert(a == major && b == minor);
-
-    if (c >= revision)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-  const char* AutodetectMimeType(const std::string& path)
-  {
-    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
-
-    if (mime == NULL)
-    {
-      // Should never happen, just for safety
-      return "application/octet-stream";
-    }
-    else
-    {
-      return mime;
-    }
-  }
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_PEERS == 1
-  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
-  {
-    size_t index;
-    if (LookupName(index, name))
-    {
-      return index;
-    }
-    else
-    {
-      LogError("Inexistent peer: " + name);
-      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
-    }
-  }
-
-
-  OrthancPeers::OrthancPeers() :
-    peers_(NULL),
-    timeout_(0)
-  {
-    peers_ = OrthancPluginGetPeers(GetGlobalContext());
-
-    if (peers_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-
-    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
-
-    for (uint32_t i = 0; i < count; i++)
-    {
-      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
-      if (name == NULL)
-      {
-        OrthancPluginFreePeers(GetGlobalContext(), peers_);
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-
-      index_[name] = i;
-    }
-  }
-
-
-  OrthancPeers::~OrthancPeers()
-  {
-    if (peers_ != NULL)
-    {
-      OrthancPluginFreePeers(GetGlobalContext(), peers_);
-    }
-  }
-
-
-  bool OrthancPeers::LookupName(size_t& target,
-                                const std::string& name) const
-  {
-    Index::const_iterator found = index_.find(name);
-
-    if (found == index_.end())
-    {
-      return false;
-    }
-    else
-    {
-      target = found->second;
-      return true;
-    }
-  }
-
-
-  std::string OrthancPeers::GetPeerName(size_t index) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
-      if (s == NULL)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-      else
-      {
-        return s;
-      }
-    }
-  }
-
-
-  std::string OrthancPeers::GetPeerUrl(size_t index) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
-      if (s == NULL)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-      else
-      {
-        return s;
-      }
-    }
-  }
-
-
-  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
-  {
-    return GetPeerUrl(GetPeerIndex(name));
-  }
-
-
-  bool OrthancPeers::LookupUserProperty(std::string& value,
-                                        size_t index,
-                                        const std::string& key) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
-      if (s == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        value.assign(s);
-        return true;
-      }
-    }
-  }
-
-
-  bool OrthancPeers::LookupUserProperty(std::string& value,
-                                        const std::string& peer,
-                                        const std::string& key) const
-  {
-    return LookupUserProperty(value, GetPeerIndex(peer), key);
-  }
-
-
-  bool OrthancPeers::DoGet(MemoryBuffer& target,
-                           size_t index,
-                           const std::string& uri) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      target.Swap(answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoGet(MemoryBuffer& target,
-                           const std::string& name,
-                           const std::string& uri) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoGet(target, index, uri));
-  }
-
-
-  bool OrthancPeers::DoGet(Json::Value& target,
-                           size_t index,
-                           const std::string& uri) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoGet(buffer, index, uri))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoGet(Json::Value& target,
-                           const std::string& name,
-                           const std::string& uri) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoGet(buffer, name, uri))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPost(MemoryBuffer& target,
-                            const std::string& name,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoPost(target, index, uri, body));
-  }
-
-
-  bool OrthancPeers::DoPost(Json::Value& target,
-                            size_t index,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoPost(buffer, index, uri, body))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPost(Json::Value& target,
-                            const std::string& name,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoPost(buffer, name, uri, body))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPost(MemoryBuffer& target,
-                            size_t index,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      target.Swap(answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPut(size_t index,
-                           const std::string& uri,
-                           const std::string& body) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPut(const std::string& name,
-                           const std::string& uri,
-                           const std::string& body) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoPut(index, uri, body));
-  }
-
-
-  bool OrthancPeers::DoDelete(size_t index,
-                              const std::string& uri) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoDelete(const std::string& name,
-                              const std::string& uri) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoDelete(index, uri));
-  }
-#endif
-
-
-
-
-
-  /******************************************************************
-   ** JOBS
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_JOB == 1
-  void OrthancJob::CallbackFinalize(void* job)
-  {
-    if (job != NULL)
-    {
-      delete reinterpret_cast<OrthancJob*>(job);
-    }
-  }
-
-
-  float OrthancJob::CallbackGetProgress(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->progress_;
-    }
-    catch (...)
-    {
-      return 0;
-    }
-  }
-
-
-  const char* OrthancJob::CallbackGetContent(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
-    }
-    catch (...)
-    {
-      return 0;
-    }
-  }
-
-
-  const char* OrthancJob::CallbackGetSerialized(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
-
-      if (tmp.hasSerialized_)
-      {
-        return tmp.serialized_.c_str();
-      }
-      else
-      {
-        return NULL;
-      }
-    }
-    catch (...)
-    {
-      return 0;
-    }
-  }
-
-
-  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->Step();
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
-    {
-      return OrthancPluginJobStepStatus_Failure;
-    }
-    catch (...)
-    {
-      return OrthancPluginJobStepStatus_Failure;
-    }
-  }
-
-
-  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
-                                                  OrthancPluginJobStopReason reason)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-
-
-  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      reinterpret_cast<OrthancJob*>(job)->Reset();
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-
-
-  void OrthancJob::ClearContent()
-  {
-    Json::Value empty = Json::objectValue;
-    UpdateContent(empty);
-  }
-
-
-  void OrthancJob::UpdateContent(const Json::Value& content)
-  {
-    if (content.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      Json::FastWriter writer;
-      content_ = writer.write(content);
-    }
-  }
-
-
-  void OrthancJob::ClearSerialized()
-  {
-    hasSerialized_ = false;
-    serialized_.clear();
-  }
-
-
-  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
-  {
-    if (serialized.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      Json::FastWriter writer;
-      serialized_ = writer.write(serialized);
-      hasSerialized_ = true;
-    }
-  }
-
-
-  void OrthancJob::UpdateProgress(float progress)
-  {
-    if (progress < 0 ||
-        progress > 1)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    progress_ = progress;
-  }
-
-
-  OrthancJob::OrthancJob(const std::string& jobType) :
-    jobType_(jobType),
-    progress_(0)
-  {
-    ClearContent();
-    ClearSerialized();
-  }
-
-
-  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
-  {
-    if (job == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
-    }
-
-    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
-      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
-      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
-      CallbackStep, CallbackStop, CallbackReset);
-
-    if (orthanc == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      return orthanc;
-    }
-  }
-
-
-  std::string OrthancJob::Submit(OrthancJob* job,
-                                 int priority)
-  {
-    if (job == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
-    }
-
-    OrthancPluginJob* orthanc = Create(job);
-
-    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
-
-    if (id == NULL)
-    {
-      LogError("Plugin cannot submit job");
-      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      std::string tmp(id);
-      tmp.assign(id);
-      OrthancPluginFreeString(GetGlobalContext(), id);
-
-      return tmp;
-    }
-  }
-
-
-  void OrthancJob::SubmitAndWait(Json::Value& result,
-                                 OrthancJob* job /* takes ownership */,
-                                 int priority)
-  {
-    std::string id = Submit(job, priority);
-
-    for (;;)
-    {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
-      Json::Value status;
-      if (!RestApiGet(status, "/jobs/" + id, false) ||
-          !status.isMember("State") ||
-          status["State"].type() != Json::stringValue)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
-      }
-
-      const std::string state = status["State"].asString();
-      if (state == "Success")
-      {
-        if (status.isMember("Content"))
-        {
-          result = status["Content"];
-        }
-        else
-        {
-          result = Json::objectValue;
-        }
-
-        return;
-      }
-      else if (state == "Running")
-      {
-        continue;
-      }
-      else if (!status.isMember("ErrorCode") ||
-               status["ErrorCode"].type() != Json::intValue)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
-      }
-      else
-      {
-        if (!status.isMember("ErrorDescription") ||
-            status["ErrorDescription"].type() != Json::stringValue)
-        {
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
-        }
-        else
-        {
-#if HAS_ORTHANC_EXCEPTION == 1
-          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
-                                          status["ErrorDescription"].asString());
-#else
-          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
-#endif
-        }
-      }
-    }
-  }
-
-
-  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
-                                         const Json::Value& body,
-                                         OrthancJob* job)
-  {
-    static const char* KEY_SYNCHRONOUS = "Synchronous";
-    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
-    static const char* KEY_PRIORITY = "Priority";
-
-    boost::movelib::unique_ptr<OrthancJob> protection(job);
-  
-    if (body.type() != Json::objectValue)
-    {
-#if HAS_ORTHANC_EXCEPTION == 1
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                      "Expected a JSON object in the body");
-#else
-      LogError("Expected a JSON object in the body");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-    }
-
-    bool synchronous = true;
-  
-    if (body.isMember(KEY_SYNCHRONOUS))
-    {
-      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
-                                        "\" must be Boolean");
-#else
-        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-      }
-      else
-      {
-        synchronous = body[KEY_SYNCHRONOUS].asBool();
-      }
-    }
-
-    if (body.isMember(KEY_ASYNCHRONOUS))
-    {
-      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
-                                        "\" must be Boolean");
-#else
-        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-      }
-      else
-      {
-        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
-      }
-    }
-
-    int priority = 0;
-
-    if (body.isMember(KEY_PRIORITY))
-    {
-      if (body[KEY_PRIORITY].type() != Json::booleanValue)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                        "Option \"" + std::string(KEY_PRIORITY) +
-                                        "\" must be an integer");
-#else
-        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-      }
-      else
-      {
-        priority = !body[KEY_PRIORITY].asInt();
-      }
-    }
-  
-    Json::Value result;
-
-    if (synchronous)
-    {
-      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
-    }
-    else
-    {
-      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
-
-      result = Json::objectValue;
-      result["ID"] = id;
-      result["Path"] = "/jobs/" + id;
-    }
-
-    std::string s = result.toStyledString();
-    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
-                              s.size(), "application/json");
-  }
-
-#endif
-
-
-
-
-  /******************************************************************
-   ** METRICS
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_METRICS == 1
-  MetricsTimer::MetricsTimer(const char* name) :
-    name_(name)
-  {
-    start_ = boost::posix_time::microsec_clock::universal_time();
-  }
-  
-  MetricsTimer::~MetricsTimer()
-  {
-    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
-    const boost::posix_time::time_duration diff = stop - start_;
-    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
-                                 OrthancPluginMetricsType_Timer);
-  }
-#endif
-
-
-
-
-  /******************************************************************
-   ** HTTP CLIENT
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
-  class HttpClient::RequestBodyWrapper : public boost::noncopyable
-  {
-  private:
-    static RequestBodyWrapper& GetObject(void* body)
-    {
-      assert(body != NULL);
-      return *reinterpret_cast<RequestBodyWrapper*>(body);
-    }
-
-    IRequestBody&  body_;
-    bool           done_;
-    std::string    chunk_;
-
-  public:
-    RequestBodyWrapper(IRequestBody& body) :
-      body_(body),
-      done_(false)
-    {
-    }      
-
-    static uint8_t IsDone(void* body)
-    {
-      return GetObject(body).done_;
-    }
-    
-    static const void* GetChunkData(void* body)
-    {
-      return GetObject(body).chunk_.c_str();
-    }
-    
-    static uint32_t GetChunkSize(void* body)
-    {
-      return static_cast<uint32_t>(GetObject(body).chunk_.size());
-    }
-
-    static OrthancPluginErrorCode Next(void* body)
-    {
-      RequestBodyWrapper& that = GetObject(body);
-        
-      if (that.done_)
-      {
-        return OrthancPluginErrorCode_BadSequenceOfCalls;
-      }
-      else
-      {
-        try
-        {
-          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
-          return OrthancPluginErrorCode_Success;
-        }
-        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-        {
-          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-        }
-        catch (...)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-      }
-    }    
-  };
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
-                                                        const char* key,
-                                                        const char* value)
-  {
-    assert(answer != NULL && key != NULL && value != NULL);
-
-    try
-    {
-      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
-                                                       const void* data,
-                                                       uint32_t size)
-  {
-    assert(answer != NULL);
-
-    try
-    {
-      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-#endif
-
-
-  HttpClient::HttpClient() :
-    httpStatus_(0),
-    method_(OrthancPluginHttpMethod_Get),
-    timeout_(0),
-    pkcs11_(false),
-    chunkedBody_(NULL),
-    allowChunkedTransfers_(true)
-  {
-  }
-
-
-  void HttpClient::AddHeaders(const HttpHeaders& headers)
-  {
-    for (HttpHeaders::const_iterator it = headers.begin();
-         it != headers.end(); ++it)
-    {
-      headers_[it->first] = it->second;
-    }
-  }
-
-  
-  void HttpClient::SetCredentials(const std::string& username,
-                                  const std::string& password)
-  {
-    username_ = username;
-    password_ = password;
-  }
-
-  
-  void HttpClient::ClearCredentials()
-  {
-    username_.empty();
-    password_.empty();
-  }
-
-
-  void HttpClient::SetCertificate(const std::string& certificateFile,
-                                  const std::string& keyFile,
-                                  const std::string& keyPassword)
-  {
-    certificateFile_ = certificateFile;
-    certificateKeyFile_ = keyFile;
-    certificateKeyPassword_ = keyPassword;
-  }
-
-  
-  void HttpClient::ClearCertificate()
-  {
-    certificateFile_.clear();
-    certificateKeyFile_.clear();
-    certificateKeyPassword_.clear();
-  }
-
-
-  void HttpClient::ClearBody()
-  {
-    fullBody_.clear();
-    chunkedBody_ = NULL;
-  }
-
-  
-  void HttpClient::SwapBody(std::string& body)
-  {
-    fullBody_.swap(body);
-    chunkedBody_ = NULL;
-  }
-
-  
-  void HttpClient::SetBody(const std::string& body)
-  {
-    fullBody_ = body;
-    chunkedBody_ = NULL;
-  }
-
-  
-  void HttpClient::SetBody(IRequestBody& body)
-  {
-    fullBody_.clear();
-    chunkedBody_ = &body;
-  }
-
-
-  namespace
-  {
-    class HeadersWrapper : public boost::noncopyable
-    {
-    private:
-      std::vector<const char*>  headersKeys_;
-      std::vector<const char*>  headersValues_;
-
-    public:
-      HeadersWrapper(const HttpClient::HttpHeaders& headers)
-      {
-        headersKeys_.reserve(headers.size());
-        headersValues_.reserve(headers.size());
-
-        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
-        {
-          headersKeys_.push_back(it->first.c_str());
-          headersValues_.push_back(it->second.c_str());
-        }
-      }
-
-      void AddStaticString(const char* key,
-                           const char* value)
-      {
-        headersKeys_.push_back(key);
-        headersValues_.push_back(value);
-      }
-
-      uint32_t GetCount() const
-      {
-        return headersKeys_.size();
-      }
-
-      const char* const* GetKeys() const
-      {
-        return headersKeys_.empty() ? NULL : &headersKeys_[0];
-      }
-
-      const char* const* GetValues() const
-      {
-        return headersValues_.empty() ? NULL : &headersValues_[0];
-      }
-    };
-
-
-    class MemoryRequestBody : public HttpClient::IRequestBody
-    {
-    private:
-      std::string  body_;
-      bool         done_;
-
-    public:
-      MemoryRequestBody(const std::string& body) :
-        body_(body),
-        done_(false)
-      {
-        if (body_.empty())
-        {
-          done_ = true;
-        }
-      }
-
-      virtual bool ReadNextChunk(std::string& chunk)
-      {
-        if (done_)
-        {
-          return false;
-        }
-        else
-        {
-          chunk.swap(body_);
-          done_ = true;
-          return true;
-        }
-      }
-    };
-
-
-    // This class mimics Orthanc::ChunkedBuffer
-    class ChunkedBuffer : public boost::noncopyable
-    {
-    private:
-      typedef std::list<std::string*>  Content;
-
-      Content  content_;
-      size_t   size_;
-
-    public:
-      ChunkedBuffer() :
-        size_(0)
-      {
-      }
-
-      ~ChunkedBuffer()
-      {
-        Clear();
-      }
-
-      void Clear()
-      {
-        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
-        {
-          assert(*it != NULL);
-          delete *it;
-        }
-
-        content_.clear();
-      }
-
-      void Flatten(std::string& target) const
-      {
-        target.resize(size_);
-
-        size_t pos = 0;
-
-        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
-        {
-          assert(*it != NULL);
-          size_t s = (*it)->size();
-
-          if (s != 0)
-          {
-            memcpy(&target[pos], (*it)->c_str(), s);
-            pos += s;
-          }
-        }
-
-        assert(size_ == 0 ||
-               pos == target.size());
-      }
-
-      void AddChunk(const void* data,
-                    size_t size)
-      {
-        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
-        size_ += size;
-      }
-
-      void AddChunk(const std::string& chunk)
-      {
-        content_.push_back(new std::string(chunk));
-        size_ += chunk.size();
-      }
-    };
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    class MemoryAnswer : public HttpClient::IAnswer
-    {
-    private:
-      HttpClient::HttpHeaders  headers_;
-      ChunkedBuffer            body_;
-
-    public:
-      const HttpClient::HttpHeaders& GetHeaders() const
-      {
-        return headers_;
-      }
-
-      const ChunkedBuffer& GetBody() const
-      {
-        return body_;
-      }
-
-      virtual void AddHeader(const std::string& key,
-                             const std::string& value)
-      {
-        headers_[key] = value;
-      }
-
-      virtual void AddChunk(const void* data,
-                            size_t size)
-      {
-        body_.AddChunk(data, size);
-      }
-    };
-#endif
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
-                                     IAnswer& answer,
-                                     IRequestBody& body) const
-  {
-    HeadersWrapper h(headers_);
-
-    if (method_ == OrthancPluginHttpMethod_Post ||
-        method_ == OrthancPluginHttpMethod_Put)
-    {
-      // Automatically set the "Transfer-Encoding" header if absent
-      bool found = false;
-
-      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
-      {
-        if (boost::iequals(it->first, "Transfer-Encoding"))
-        {
-          found = true;
-          break;
-        }
-      }
-
-      if (!found)
-      {
-        h.AddStaticString("Transfer-Encoding", "chunked");
-      }
-    }
-
-    RequestBodyWrapper request(body);
-        
-    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
-      GetGlobalContext(),
-      &answer,
-      AnswerAddChunkCallback,
-      AnswerAddHeaderCallback,
-      &httpStatus,
-      method_,
-      url_.c_str(),
-      h.GetCount(),
-      h.GetKeys(),
-      h.GetValues(),
-      &request,
-      RequestBodyWrapper::IsDone,
-      RequestBodyWrapper::GetChunkData,
-      RequestBodyWrapper::GetChunkSize,
-      RequestBodyWrapper::Next,
-      username_.empty() ? NULL : username_.c_str(),
-      password_.empty() ? NULL : password_.c_str(),
-      timeout_,
-      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
-      pkcs11_ ? 1 : 0);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-#endif    
-
-
-  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
-                                        HttpHeaders& answerHeaders,
-                                        std::string& answerBody,
-                                        const std::string& body) const
-  {
-    HeadersWrapper headers(headers_);
-
-    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
-
-    OrthancPluginErrorCode error = OrthancPluginHttpClient(
-      GetGlobalContext(),
-      *answerBodyBuffer,
-      *answerHeadersBuffer,
-      &httpStatus,
-      method_,
-      url_.c_str(),
-      headers.GetCount(),
-      headers.GetKeys(),
-      headers.GetValues(),
-      body.empty() ? NULL : body.c_str(),
-      body.size(),
-      username_.empty() ? NULL : username_.c_str(),
-      password_.empty() ? NULL : password_.c_str(),
-      timeout_,
-      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
-      pkcs11_ ? 1 : 0);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-
-    Json::Value v;
-    answerHeadersBuffer.ToJson(v);
-
-    if (v.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Value::Members members = v.getMemberNames();
-    answerHeaders.clear();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& h = v[members[i]];
-      if (h.type() != Json::stringValue)
-      {
-        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-      }
-      else
-      {
-        answerHeaders[members[i]] = h.asString();
-      }
-    }
-
-    answerBodyBuffer.ToString(answerBody);
-  }
-
-
-  void HttpClient::Execute(IAnswer& answer)
-  {
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    if (allowChunkedTransfers_)
-    {
-      if (chunkedBody_ != NULL)
-      {
-        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
-      }
-      else
-      {
-        MemoryRequestBody wrapper(fullBody_);
-        ExecuteWithStream(httpStatus_, answer, wrapper);
-      }
-
-      return;
-    }
-#endif
-    
-    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
-    // transfers are disabled. This results in higher memory usage
-    // (all chunks from the answer body are sent at once)
-
-    HttpHeaders answerHeaders;
-    std::string answerBody;
-    Execute(answerHeaders, answerBody);
-
-    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
-         it != answerHeaders.end(); ++it)
-    {
-      answer.AddHeader(it->first, it->second);      
-    }
-
-    if (!answerBody.empty())
-    {
-      answer.AddChunk(answerBody.c_str(), answerBody.size());
-    }
-  }
-
-
-  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
-                           std::string& answerBody /* out */)
-  {
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    if (allowChunkedTransfers_)
-    {
-      MemoryAnswer answer;
-      Execute(answer);
-      answerHeaders = answer.GetHeaders();
-      answer.GetBody().Flatten(answerBody);
-      return;
-    }
-#endif
-    
-    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
-    // transfers are disabled. This results in higher memory usage
-    // (all chunks from the request body are sent at once)
-
-    if (chunkedBody_ != NULL)
-    {
-      ChunkedBuffer buffer;
-      
-      std::string chunk;
-      while (chunkedBody_->ReadNextChunk(chunk))
-      {
-        buffer.AddChunk(chunk);
-      }
-
-      std::string body;
-      buffer.Flatten(body);
-
-      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
-    }
-    else
-    {
-      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
-    }
-  }
-
-
-  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
-                           Json::Value& answerBody /* out */)
-  {
-    std::string body;
-    Execute(answerHeaders, body);
-    
-    Json::Reader reader;
-    if (!reader.parse(body, answerBody))
-    {
-      LogError("Cannot convert HTTP answer body to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  void HttpClient::Execute()
-  {
-    HttpHeaders answerHeaders;
-    std::string body;
-    Execute(answerHeaders, body);
-  }
-
-#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
-
-
-
-
-
-  /******************************************************************
-   ** CHUNKED HTTP SERVER
-   ******************************************************************/
-
-  namespace Internals
-  {
-    void NullRestCallback(OrthancPluginRestOutput* output,
-                          const char* url,
-                          const OrthancPluginHttpRequest* request)
-    {
-    }
-  
-    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
-                                                   const OrthancPluginHttpRequest* request)
-    {
-      return NULL;
-    }
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
-
-    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
-      OrthancPluginServerChunkedRequestReader* reader,
-      const void*                              data,
-      uint32_t                                 size)
-    {
-      try
-      {
-        if (reader == NULL)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-
-        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-
-    
-    OrthancPluginErrorCode ChunkedRequestReaderExecute(
-      OrthancPluginServerChunkedRequestReader* reader,
-      OrthancPluginRestOutput*                 output)
-    {
-      try
-      {
-        if (reader == NULL)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-
-        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-
-    
-    void ChunkedRequestReaderFinalize(
-      OrthancPluginServerChunkedRequestReader* reader)
-    {
-      if (reader != NULL)
-      {
-        delete reinterpret_cast<IChunkedRequestReader*>(reader);
-      }
-    }
-
-#else
-    
-    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
-                                                    const char* url,
-                                                    const OrthancPluginHttpRequest* request,
-                                                    RestCallback         GetHandler,
-                                                    ChunkedRestCallback  PostHandler,
-                                                    RestCallback         DeleteHandler,
-                                                    ChunkedRestCallback  PutHandler)
-    {
-      try
-      {
-        std::string allowed;
-
-        if (GetHandler != Internals::NullRestCallback)
-        {
-          allowed += "GET";
-        }
-
-        if (PostHandler != Internals::NullChunkedRestCallback)
-        {
-          if (!allowed.empty())
-          {
-            allowed += ",";
-          }
-        
-          allowed += "POST";
-        }
-
-        if (DeleteHandler != Internals::NullRestCallback)
-        {
-          if (!allowed.empty())
-          {
-            allowed += ",";
-          }
-        
-          allowed += "DELETE";
-        }
-
-        if (PutHandler != Internals::NullChunkedRestCallback)
-        {
-          if (!allowed.empty())
-          {
-            allowed += ",";
-          }
-        
-          allowed += "PUT";
-        }
-      
-        switch (request->method)
-        {
-          case OrthancPluginHttpMethod_Get:
-            if (GetHandler == Internals::NullRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              GetHandler(output, url, request);
-            }
-
-            break;
-
-          case OrthancPluginHttpMethod_Post:
-            if (PostHandler == Internals::NullChunkedRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
-              if (reader.get() == NULL)
-              {
-                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-              }
-              else
-              {
-                reader->AddChunk(request->body, request->bodySize);
-                reader->Execute(output);
-              }
-            }
-
-            break;
-
-          case OrthancPluginHttpMethod_Delete:
-            if (DeleteHandler == Internals::NullRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              DeleteHandler(output, url, request);
-            }
-
-            break;
-
-          case OrthancPluginHttpMethod_Put:
-            if (PutHandler == Internals::NullChunkedRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
-              if (reader.get() == NULL)
-              {
-                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-              }
-              else
-              {
-                reader->AddChunk(request->body, request->bodySize);
-                reader->Execute(output);
-              }
-            }
-
-            break;
-
-          default:
-            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-        }
-
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
-        if (HasGlobalContext() &&
-            e.HasDetails())
-        {
-          // The "false" instructs Orthanc not to log the detailed
-          // error message. This is to avoid duplicating the details,
-          // because "OrthancException" already does it on construction.
-          OrthancPluginSetHttpErrorDetails
-            (GetGlobalContext(), output, e.GetDetails(), false);
-        }
-#endif
-
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-#endif
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
-  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
-    OrthancPluginStorageCommitmentFailureReason* target,
-    void* rawHandler,
-    const char* sopClassUid,
-    const char* sopInstanceUid)
-  {
-    assert(target != NULL &&
-           rawHandler != NULL);
-      
-    try
-    {
-      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
-      *target = handler.Lookup(sopClassUid, sopInstanceUid);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
-  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
-  {
-    assert(rawHandler != NULL);
-    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
-  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
-    toFree_(false),
-    instance_(instance)
-  {
-  }
-#else
-  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
-    toFree_(false),
-    instance_(instance)
-  {
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  DicomInstance::DicomInstance(const void* buffer,
-                               size_t size) :
-    toFree_(true),
-    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
-  {
-    if (instance_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
-    }
-  }
-#endif
-
-
-  DicomInstance::~DicomInstance()
-  {
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    if (toFree_ &&
-        instance_ != NULL)
-    {
-      OrthancPluginFreeDicomInstance(
-        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
-    }
-#endif
-  }
-
-  
-  std::string DicomInstance::GetRemoteAet() const
-  {
-    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
-    if (s == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      return std::string(s);
-    }
-  }
-
-
-  void DicomInstance::GetJson(Json::Value& target) const
-  {
-    OrthancString s;
-    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
-    s.ToJson(target);
-  }
-  
-
-  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
-  {
-    OrthancString s;
-    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
-    s.ToJson(target);
-  }
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-  std::string DicomInstance::GetTransferSyntaxUid() const
-  {
-    OrthancString s;
-    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
-
-    std::string result;
-    s.ToString(result);
-    return result;
-  }
-#endif
-
-  
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-  bool DicomInstance::HasPixelData() const
-  {
-    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
-    if (result < 0)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      return (result != 0);
-    }
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
-  void DicomInstance::GetRawFrame(std::string& target,
-                                  unsigned int frameIndex) const
-  {
-    MemoryBuffer buffer;
-    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
-      GetGlobalContext(), *buffer, instance_, frameIndex);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      buffer.ToString(target);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
-  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
-  {
-    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
-      GetGlobalContext(), instance_, frameIndex);
-
-    if (image == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      return new OrthancImage(image);
-    }
-  }
-#endif  
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  void DicomInstance::Serialize(std::string& target) const
-  {
-    MemoryBuffer buffer;
-    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
-      GetGlobalContext(), *buffer, instance_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      buffer.ToString(target);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-#endif
-  
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  DicomInstance* DicomInstance::Transcode(const void* buffer,
-                                          size_t size,
-                                          const std::string& transferSyntax)
-  {
-    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
-      GetGlobalContext(), buffer, size, transferSyntax.c_str());
-
-    if (instance == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
-      result->toFree_ = true;
-      return result.release();
-    }
-  }
-#endif
-}
--- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1228 +0,0 @@
-/**
- * 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.
- * 
- * 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 "OrthancPluginException.h"
-
-#include <orthanc/OrthancCPlugin.h>
-#include <boost/noncopyable.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <json/value.h>
-#include <vector>
-#include <list>
-#include <set>
-#include <map>
-
-
-
-/**
- * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
- * backward compatibility with Orthanc SDK <= 1.3.0.
- * 
- *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
- *
- **/
-#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
-
-
-#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
-#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
-  (ORTHANC_VERSION_MAJOR > major ||                                     \
-   (ORTHANC_VERSION_MAJOR == major &&                                   \
-    (ORTHANC_VERSION_MINOR > minor ||                                   \
-     (ORTHANC_VERSION_MINOR == minor &&                                 \
-      ORTHANC_VERSION_REVISION >= revision))))
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
-// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
-#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
-#else
-#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
-#  define HAS_ORTHANC_PLUGIN_PEERS  1
-#  define HAS_ORTHANC_PLUGIN_JOB    1
-#else
-#  define HAS_ORTHANC_PLUGIN_PEERS  0
-#  define HAS_ORTHANC_PLUGIN_JOB    0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
-#else
-#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
-#  define HAS_ORTHANC_PLUGIN_METRICS  1
-#else
-#  define HAS_ORTHANC_PLUGIN_METRICS  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
-#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  1
-#else
-#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  1
-#else
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
-#else
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
-#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
-#else
-#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
-#endif
-
-
-
-namespace OrthancPlugins
-{
-  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
-                                const char* url,
-                                const OrthancPluginHttpRequest* request);
-
-  void SetGlobalContext(OrthancPluginContext* context);
-
-  bool HasGlobalContext();
-
-  OrthancPluginContext* GetGlobalContext();
-
-  
-  class OrthancImage;
-
-
-  class MemoryBuffer : public boost::noncopyable
-  {
-  private:
-    OrthancPluginMemoryBuffer  buffer_;
-
-    void Check(OrthancPluginErrorCode code);
-
-    bool CheckHttp(OrthancPluginErrorCode code);
-
-  public:
-    MemoryBuffer();
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    // This constructor makes a copy of the given buffer in the memory
-    // handled by the Orthanc core
-    MemoryBuffer(const void* buffer,
-                 size_t size);
-#endif
-
-    ~MemoryBuffer()
-    {
-      Clear();
-    }
-
-    OrthancPluginMemoryBuffer* operator*()
-    {
-      return &buffer_;
-    }
-
-    // This transfers ownership from "other" to "this"
-    void Assign(OrthancPluginMemoryBuffer& other);
-
-    void Swap(MemoryBuffer& other);
-
-    OrthancPluginMemoryBuffer Release();
-
-    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;
-    }
-
-    bool IsEmpty() const
-    {
-      return GetSize() == 0 || GetData() == NULL;
-    }
-
-    void Clear();
-
-    void ToString(std::string& target) const;
-
-    void ToJson(Json::Value& target) const;
-
-    bool RestApiGet(const std::string& uri,
-                    bool applyPlugins);
-
-    bool RestApiGet(const std::string& uri,
-                    const std::map<std::string, std::string>& httpHeaders,
-                    bool applyPlugins);
-
-    bool RestApiPost(const std::string& uri,
-                     const void* body,
-                     size_t bodySize,
-                     bool applyPlugins);
-
-    bool RestApiPut(const std::string& uri,
-                    const void* body,
-                    size_t bodySize,
-                    bool applyPlugins);
-
-    bool RestApiPost(const std::string& uri,
-                     const Json::Value& body,
-                     bool applyPlugins);
-
-    bool RestApiPut(const std::string& uri,
-                    const Json::Value& body,
-                    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);
-    }
-
-    void CreateDicom(const Json::Value& tags,
-                     OrthancPluginCreateDicomFlags flags);
-
-    void CreateDicom(const Json::Value& tags,
-                     const OrthancImage& pixelData,
-                     OrthancPluginCreateDicomFlags flags);
-
-    void ReadFile(const std::string& path);
-
-    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
-
-    void DicomToJson(Json::Value& target,
-                     OrthancPluginDicomToJsonFormat format,
-                     OrthancPluginDicomToJsonFlags flags,
-                     uint32_t maxStringLength);
-
-    bool HttpGet(const std::string& url,
-                 const std::string& username,
-                 const std::string& password);
-
-    bool HttpPost(const std::string& url,
-                  const std::string& body,
-                  const std::string& username,
-                  const std::string& password);
-
-    bool HttpPut(const std::string& url,
-                 const std::string& body,
-                 const std::string& username,
-                 const std::string& password);
-
-    void GetDicomInstance(const std::string& instanceId);
-  };
-
-
-  class OrthancString : public boost::noncopyable
-  {
-  private:
-    char*   str_;
-
-    void Clear();
-
-  public:
-    OrthancString() :
-      str_(NULL)
-    {
-    }
-
-    ~OrthancString()
-    {
-      Clear();
-    }
-
-    // This transfers ownership, warning: The string must have been
-    // allocated by the Orthanc core
-    void Assign(char* str);
-
-    const char* GetContent() const
-    {
-      return str_;
-    }
-
-    void ToString(std::string& target) const;
-
-    void ToJson(Json::Value& target) const;
-  };
-
-
-  class OrthancConfiguration : public boost::noncopyable
-  {
-  private:
-    Json::Value  configuration_;  // Necessarily a Json::objectValue
-    std::string  path_;
-
-    std::string GetPath(const std::string& key) const;
-
-    void LoadConfiguration();
-    
-  public:
-    OrthancConfiguration();
-
-    explicit OrthancConfiguration(bool load);
-
-    const Json::Value& GetJson() const
-    {
-      return configuration_;
-    }
-
-    bool IsSection(const std::string& key) const;
-
-    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;
-
-    bool LookupListOfStrings(std::list<std::string>& target,
-                             const std::string& key,
-                             bool allowSingleString) const;
-
-    bool LookupSetOfStrings(std::set<std::string>& target,
-                            const std::string& key,
-                            bool allowSingleString) 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;
-
-    void GetDictionary(std::map<std::string, std::string>& target,
-                       const std::string& key) const;
-  };
-
-  class OrthancImage : public boost::noncopyable
-  {
-  private:
-    OrthancPluginImage*    image_;
-
-    void Clear();
-
-    void CheckImageAvailable() const;
-
-  public:
-    OrthancImage();
-
-    explicit OrthancImage(OrthancPluginImage* image);
-
-    OrthancImage(OrthancPluginPixelFormat  format,
-                 uint32_t                  width,
-                 uint32_t                  height);
-
-    OrthancImage(OrthancPluginPixelFormat  format,
-                 uint32_t                  width,
-                 uint32_t                  height,
-                 uint32_t                  pitch,
-                 void*                     buffer);
-
-    ~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() const;
-
-    unsigned int GetWidth() const;
-
-    unsigned int GetHeight() const;
-
-    unsigned int GetPitch() const;
-    
-    void* GetBuffer() const;
-
-    const OrthancPluginImage* GetObject() const
-    {
-      return image_;
-    }
-
-    void CompressPngImage(MemoryBuffer& target) const;
-
-    void CompressJpegImage(MemoryBuffer& target,
-                           uint8_t quality) const;
-
-    void AnswerPngImage(OrthancPluginRestOutput* output) const;
-
-    void AnswerJpegImage(OrthancPluginRestOutput* output,
-                         uint8_t quality) const;
-    
-    void* GetWriteableBuffer();
-
-    OrthancPluginImage* Release();
-  };
-
-
-#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
-  class FindMatcher : public boost::noncopyable
-  {
-  private:
-    OrthancPluginFindMatcher*          matcher_;
-    const OrthancPluginWorklistQuery*  worklist_;
-
-    void SetupDicom(const void*            query,
-                    uint32_t               size);
-
-  public:
-    explicit FindMatcher(const OrthancPluginWorklistQuery*  worklist);
-
-    FindMatcher(const void*  query,
-                uint32_t     size)
-    {
-      SetupDicom(query, size);
-    }
-
-    explicit FindMatcher(const MemoryBuffer&  dicom)
-    {
-      SetupDicom(dicom.GetData(), dicom.GetSize());
-    }
-
-    ~FindMatcher();
-
-    bool IsMatch(const void*  dicom,
-                 uint32_t     size) const;
-
-    bool IsMatch(const MemoryBuffer& dicom) const
-    {
-      return IsMatch(dicom.GetData(), dicom.GetSize());
-    }
-  };
-#endif
-
-
-  bool RestApiGet(Json::Value& result,
-                  const std::string& uri,
-                  bool applyPlugins);
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        bool applyPlugins);
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        const std::map<std::string, std::string>& httpHeaders,
-                        bool applyPlugins);
-
-  bool RestApiPost(std::string& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins);
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins);
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const Json::Value& body,
-                   bool applyPlugins);
-
-  inline bool RestApiPost(Json::Value& result,
-                          const std::string& uri,
-                          const std::string& body,
-                          bool applyPlugins)
-  {
-    return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
-                       body.size(), applyPlugins);
-  }
-
-  inline bool RestApiPost(Json::Value& result,
-                          const std::string& uri,
-                          const MemoryBuffer& body,
-                          bool applyPlugins)
-  {
-    return RestApiPost(result, uri, body.GetData(),
-                       body.GetSize(), applyPlugins);
-  }
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const void* body,
-                  size_t bodySize,
-                  bool applyPlugins);
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const Json::Value& body,
-                  bool applyPlugins);
-
-  inline bool RestApiPut(Json::Value& result,
-                         const std::string& uri,
-                         const std::string& body,
-                         bool applyPlugins)
-  {
-    return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
-                      body.size(), applyPlugins);
-  }
-
-  bool RestApiDelete(const std::string& uri,
-                     bool applyPlugins);
-
-  bool HttpDelete(const std::string& url,
-                  const std::string& username,
-                  const std::string& password);
-
-  void AnswerJson(const Json::Value& value,
-                  OrthancPluginRestOutput* output);
-
-  void AnswerString(const std::string& answer,
-                    const char* mimeType,
-                    OrthancPluginRestOutput* output);
-
-  void AnswerHttpError(uint16_t httpError,
-                       OrthancPluginRestOutput* output);
-
-  void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-  const char* AutodetectMimeType(const std::string& path);
-#endif
-
-  void LogError(const std::string& message);
-
-  void LogWarning(const std::string& message);
-
-  void LogInfo(const std::string& message);
-
-  void ReportMinimalOrthancVersion(unsigned int major,
-                                   unsigned int minor,
-                                   unsigned int revision);
-  
-  bool CheckMinimalOrthancVersion(unsigned int major,
-                                  unsigned int minor,
-                                  unsigned int revision);
-
-
-  namespace Internals
-  {
-    template <RestCallback Callback>
-    static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request)
-    {
-      try
-      {
-        Callback(output, url, request);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
-        if (HasGlobalContext() &&
-            e.HasDetails())
-        {
-          // The "false" instructs Orthanc not to log the detailed
-          // error message. This is to avoid duplicating the details,
-          // because "OrthancException" already does it on construction.
-          OrthancPluginSetHttpErrorDetails
-            (GetGlobalContext(), output, e.GetDetails(), false);
-        }
-#endif
-
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-  }
-
-  
-  template <RestCallback Callback>
-  void RegisterRestCallback(const std::string& uri,
-                            bool isThreadSafe)
-  {
-    if (isThreadSafe)
-    {
-      OrthancPluginRegisterRestCallbackNoLock
-        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
-    }
-    else
-    {
-      OrthancPluginRegisterRestCallback
-        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
-    }
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_PEERS == 1
-  class OrthancPeers : public boost::noncopyable
-  {
-  private:
-    typedef std::map<std::string, uint32_t>   Index;
-
-    OrthancPluginPeers   *peers_;
-    Index                 index_;
-    uint32_t              timeout_;
-
-    size_t GetPeerIndex(const std::string& name) const;
-
-  public:
-    OrthancPeers();
-
-    ~OrthancPeers();
-
-    uint32_t GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    void SetTimeout(uint32_t timeout)
-    {
-      timeout_ = timeout;
-    }
-
-    bool LookupName(size_t& target,
-                    const std::string& name) const;
-
-    std::string GetPeerName(size_t index) const;
-
-    std::string GetPeerUrl(size_t index) const;
-
-    std::string GetPeerUrl(const std::string& name) const;
-
-    size_t GetPeersCount() const
-    {
-      return index_.size();
-    }
-
-    bool LookupUserProperty(std::string& value,
-                            size_t index,
-                            const std::string& key) const;
-
-    bool LookupUserProperty(std::string& value,
-                            const std::string& peer,
-                            const std::string& key) const;
-
-    bool DoGet(MemoryBuffer& target,
-               size_t index,
-               const std::string& uri) const;
-
-    bool DoGet(MemoryBuffer& target,
-               const std::string& name,
-               const std::string& uri) const;
-
-    bool DoGet(Json::Value& target,
-               size_t index,
-               const std::string& uri) const;
-
-    bool DoGet(Json::Value& target,
-               const std::string& name,
-               const std::string& uri) const;
-
-    bool DoPost(MemoryBuffer& target,
-                size_t index,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPost(MemoryBuffer& target,
-                const std::string& name,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPost(Json::Value& target,
-                size_t index,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPost(Json::Value& target,
-                const std::string& name,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPut(size_t index,
-               const std::string& uri,
-               const std::string& body) const;
-
-    bool DoPut(const std::string& name,
-               const std::string& uri,
-               const std::string& body) const;
-
-    bool DoDelete(size_t index,
-                  const std::string& uri) const;
-
-    bool DoDelete(const std::string& name,
-                  const std::string& uri) const;
-  };
-#endif
-
-
-
-#if HAS_ORTHANC_PLUGIN_JOB == 1
-  class OrthancJob : public boost::noncopyable
-  {
-  private:
-    std::string   jobType_;
-    std::string   content_;
-    bool          hasSerialized_;
-    std::string   serialized_;
-    float         progress_;
-
-    static void CallbackFinalize(void* job);
-
-    static float CallbackGetProgress(void* job);
-
-    static const char* CallbackGetContent(void* job);
-
-    static const char* CallbackGetSerialized(void* job);
-
-    static OrthancPluginJobStepStatus CallbackStep(void* job);
-
-    static OrthancPluginErrorCode CallbackStop(void* job,
-                                               OrthancPluginJobStopReason reason);
-
-    static OrthancPluginErrorCode CallbackReset(void* job);
-
-  protected:
-    void ClearContent();
-
-    void UpdateContent(const Json::Value& content);
-
-    void ClearSerialized();
-
-    void UpdateSerialized(const Json::Value& serialized);
-
-    void UpdateProgress(float progress);
-    
-  public:
-    OrthancJob(const std::string& jobType);
-    
-    virtual ~OrthancJob()
-    {
-    }
-
-    virtual OrthancPluginJobStepStatus Step() = 0;
-
-    virtual void Stop(OrthancPluginJobStopReason reason) = 0;
-    
-    virtual void Reset() = 0;
-
-    static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
-
-    static std::string Submit(OrthancJob* job /* takes ownership */,
-                              int priority);
-
-    static void SubmitAndWait(Json::Value& result,
-                              OrthancJob* job /* takes ownership */,
-                              int priority);
-
-    // Submit a job from a POST on the REST API with the same
-    // conventions as in the Orthanc core (according to the
-    // "Synchronous" and "Priority" options)
-    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
-                                      const Json::Value& body,
-                                      OrthancJob* job);
-  };
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_METRICS == 1
-  inline void SetMetricsValue(char* name,
-                              float value)
-  {
-    OrthancPluginSetMetricsValue(GetGlobalContext(), name,
-                                 value, OrthancPluginMetricsType_Default);
-  }
-
-  class MetricsTimer : public boost::noncopyable
-  {
-  private:
-    std::string               name_;
-    boost::posix_time::ptime  start_;
-
-  public:
-    explicit MetricsTimer(const char* name);
-
-    ~MetricsTimer();
-  };
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
-  class HttpClient : public boost::noncopyable
-  {
-  public:
-    typedef std::map<std::string, std::string>  HttpHeaders;
-
-    class IRequestBody : public boost::noncopyable
-    {
-    public:
-      virtual ~IRequestBody()
-      {
-      }
-
-      virtual bool ReadNextChunk(std::string& chunk) = 0;
-    };
-
-
-    class IAnswer : public boost::noncopyable
-    {
-    public:
-      virtual ~IAnswer()
-      {
-      }
-
-      virtual void AddHeader(const std::string& key,
-                             const std::string& value) = 0;
-
-      virtual void AddChunk(const void* data,
-                            size_t size) = 0;
-    };
-
-
-  private:
-    class RequestBodyWrapper;
-
-    uint16_t                 httpStatus_;
-    OrthancPluginHttpMethod  method_;
-    std::string              url_;
-    HttpHeaders              headers_;
-    std::string              username_;
-    std::string              password_;
-    uint32_t                 timeout_;
-    std::string              certificateFile_;
-    std::string              certificateKeyFile_;
-    std::string              certificateKeyPassword_;
-    bool                     pkcs11_;
-    std::string              fullBody_;
-    IRequestBody*            chunkedBody_;
-    bool                     allowChunkedTransfers_;
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    void ExecuteWithStream(uint16_t& httpStatus,  // out
-                           IAnswer& answer,       // out
-                           IRequestBody& body) const;
-#endif
-
-    void ExecuteWithoutStream(uint16_t& httpStatus,        // out
-                              HttpHeaders& answerHeaders,  // out
-                              std::string& answerBody,     // out
-                              const std::string& body) const;
-    
-  public:
-    HttpClient();
-
-    uint16_t GetHttpStatus() const
-    {
-      return httpStatus_;
-    }
-
-    void SetMethod(OrthancPluginHttpMethod method)
-    {
-      method_ = method;
-    }
-
-    const std::string& GetUrl() const
-    {
-      return url_;
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    void SetHeaders(const HttpHeaders& headers)
-    {
-      headers_ = headers;
-    }
-
-    void AddHeader(const std::string& key,
-                   const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    void AddHeaders(const HttpHeaders& headers);
-
-    void SetCredentials(const std::string& username,
-                        const std::string& password);
-
-    void ClearCredentials();
-
-    void SetTimeout(unsigned int timeout)  // 0 for default timeout
-    {
-      timeout_ = timeout;
-    }
-
-    void SetCertificate(const std::string& certificateFile,
-                        const std::string& keyFile,
-                        const std::string& keyPassword);
-
-    void ClearCertificate();
-
-    void SetPkcs11(bool pkcs11)
-    {
-      pkcs11_ = pkcs11;
-    }
-
-    void ClearBody();
-
-    void SwapBody(std::string& body);
-
-    void SetBody(const std::string& body);
-
-    void SetBody(IRequestBody& body);
-
-    // This function can be used to disable chunked transfers if the
-    // remote server is Orthanc with a version <= 1.5.6.
-    void SetChunkedTransfersAllowed(bool allow)
-    {
-      allowChunkedTransfers_ = allow;
-    }
-
-    bool IsChunkedTransfersAllowed() const
-    {
-      return allowChunkedTransfers_;
-    }
-
-    void Execute(IAnswer& answer);
-
-    void Execute(HttpHeaders& answerHeaders /* out */,
-                 std::string& answerBody /* out */);
-
-    void Execute(HttpHeaders& answerHeaders /* out */,
-                 Json::Value& answerBody /* out */);
-
-    void Execute();
-  };
-#endif
-
-
-
-  class IChunkedRequestReader : public boost::noncopyable
-  {
-  public:
-    virtual ~IChunkedRequestReader()
-    {
-    }
-
-    virtual void AddChunk(const void* data,
-                          size_t size) = 0;
-
-    virtual void Execute(OrthancPluginRestOutput* output) = 0;
-  };
-
-
-  typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
-                                                         const OrthancPluginHttpRequest* request);
-
-
-  namespace Internals
-  {
-    void NullRestCallback(OrthancPluginRestOutput* output,
-                          const char* url,
-                          const OrthancPluginHttpRequest* request);
-  
-    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
-                                                   const OrthancPluginHttpRequest* request);
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
-    template <ChunkedRestCallback Callback>
-    static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
-                                                const char* url,
-                                                const OrthancPluginHttpRequest* request)
-    {
-      try
-      {
-        if (reader == NULL)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-        else
-        {
-          *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
-          if (*reader == NULL)
-          {
-            return OrthancPluginErrorCode_Plugin;
-          }
-          else
-          {
-            return OrthancPluginErrorCode_Success;
-          }
-        }
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-
-    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
-      OrthancPluginServerChunkedRequestReader* reader,
-      const void*                              data,
-      uint32_t                                 size);
-
-    OrthancPluginErrorCode ChunkedRequestReaderExecute(
-      OrthancPluginServerChunkedRequestReader* reader,
-      OrthancPluginRestOutput*                 output);
-
-    void ChunkedRequestReaderFinalize(
-      OrthancPluginServerChunkedRequestReader* reader);
-
-#else  
-
-    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
-                                                    const char* url,
-                                                    const OrthancPluginHttpRequest* request,
-                                                    RestCallback GetHandler,
-                                                    ChunkedRestCallback PostHandler,
-                                                    RestCallback DeleteHandler,
-                                                    ChunkedRestCallback PutHandler);
-
-    template<
-      RestCallback         GetHandler,
-      ChunkedRestCallback  PostHandler,
-      RestCallback         DeleteHandler,
-      ChunkedRestCallback  PutHandler
-      >
-    inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
-                                                           const char* url,
-                                                           const OrthancPluginHttpRequest* request)
-    {
-      return ChunkedRestCompatibility(output, url, request, GetHandler,
-                                      PostHandler, DeleteHandler, PutHandler);
-    }
-#endif
-  }
-
-
-
-  // NB: We use a templated class instead of a templated function, because
-  // default values are only available in functions since C++11
-  template<
-    RestCallback         GetHandler    = Internals::NullRestCallback,
-    ChunkedRestCallback  PostHandler   = Internals::NullChunkedRestCallback,
-    RestCallback         DeleteHandler = Internals::NullRestCallback,
-    ChunkedRestCallback  PutHandler    = Internals::NullChunkedRestCallback
-    >
-  class ChunkedRestRegistration : public boost::noncopyable
-  {
-  public:
-    static void Apply(const std::string& uri)
-    {
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
-      OrthancPluginRegisterChunkedRestCallback(
-        GetGlobalContext(), uri.c_str(),
-        GetHandler == Internals::NullRestCallback         ? NULL : Internals::Protect<GetHandler>,
-        PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
-        DeleteHandler == Internals::NullRestCallback      ? NULL : Internals::Protect<DeleteHandler>,
-        PutHandler == Internals::NullChunkedRestCallback  ? NULL : Internals::ChunkedProtect<PutHandler>,
-        Internals::ChunkedRequestReaderAddChunk,
-        Internals::ChunkedRequestReaderExecute,
-        Internals::ChunkedRequestReaderFinalize);
-#else
-      OrthancPluginRegisterRestCallbackNoLock(
-        GetGlobalContext(), uri.c_str(), 
-        Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
-#endif
-    }
-  };
-
-  
-
-#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
-  class IStorageCommitmentScpHandler : public boost::noncopyable
-  {
-  public:
-    virtual ~IStorageCommitmentScpHandler()
-    {
-    }
-    
-    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
-                                                               const std::string& sopInstanceUid) = 0;
-    
-    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
-                                         void* rawHandler,
-                                         const char* sopClassUid,
-                                         const char* sopInstanceUid);
-
-    static void Destructor(void* rawHandler);
-  };
-#endif
-
-
-  class DicomInstance : public boost::noncopyable
-  {
-  private:
-    bool toFree_;
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
-    const OrthancPluginDicomInstance*  instance_;
-#else
-    OrthancPluginDicomInstance*  instance_;
-#endif
-    
-  public:
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
-    DicomInstance(const OrthancPluginDicomInstance* instance);
-#else
-    DicomInstance(OrthancPluginDicomInstance* instance);
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    DicomInstance(const void* buffer,
-                  size_t size);
-#endif
-
-    ~DicomInstance();
-
-    std::string GetRemoteAet() const;
-
-    const void* GetBuffer() const
-    {
-      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
-    }
-
-    size_t GetSize() const
-    {
-      return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
-    }
-
-    void GetJson(Json::Value& target) const;
-
-    void GetSimplifiedJson(Json::Value& target) const;
-
-    OrthancPluginInstanceOrigin GetOrigin() const
-    {
-      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
-    }
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-    std::string GetTransferSyntaxUid() const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-    bool HasPixelData() const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    unsigned int GetFramesCount() const
-    {
-      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
-    }
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    void GetRawFrame(std::string& target,
-                     unsigned int frameIndex) const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    void Serialize(std::string& target) const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    static DicomInstance* Transcode(const void* buffer,
-                                    size_t size,
-                                    const std::string& transferSyntax);
-#endif
-  };
-}
--- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/**
- * 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.
- * 
- * 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(HAS_ORTHANC_EXCEPTION)
-#  error The macro HAS_ORTHANC_EXCEPTION must be defined
-#endif
-
-
-#if HAS_ORTHANC_EXCEPTION == 1
-#  include <OrthancException.h>
-#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
-#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
-#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
-#else
-#  include <orthanc/OrthancCPlugin.h>
-#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
-#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
-#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
-#endif
-
-
-#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
-  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
-
-
-#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
-  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
-                                                  
-
-#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
-  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
-  {                                                                 \
-    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
-  }
-
-
-namespace OrthancPlugins
-{
-#if HAS_ORTHANC_EXCEPTION == 0
-  class PluginException
-  {
-  private:
-    OrthancPluginErrorCode  code_;
-
-  public:
-    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
-    {
-    }
-
-    OrthancPluginErrorCode GetErrorCode() const
-    {
-      return code_;
-    }
-
-    const char* What(OrthancPluginContext* context) const
-    {
-      const char* description = OrthancPluginGetErrorDescription(context, code_);
-      if (description)
-      {
-        return description;
-      }
-      else
-      {
-        return "No description available";
-      }
-    }
-  };
-#endif
-}
--- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-# 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.
-# 
-# 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/>.
-
-
-# In Orthanc <= 1.7.1, the instructions below were part of
-# "Compiler.cmake", and were protected by the (now unused) option
-# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map")
-elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list")
-endif()
--- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map	Tue Aug 11 12:47:59 2020 +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/OrthancStone/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4740 +0,0 @@
-/**
- * \mainpage
- *
- * This C/C++ SDK allows external developers to create plugins that
- * can be loaded into Orthanc to extend its functionality. Each
- * Orthanc plugin must expose 4 public functions with the following
- * signatures:
- * 
- * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
- *    This function is invoked by Orthanc when it loads the plugin on startup.
- *    The plugin must:
- *    - Check its compatibility with the Orthanc version using
- *      ::OrthancPluginCheckVersion().
- *    - Store the context pointer so that it can use the plugin 
- *      services of Orthanc.
- *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
- *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
- *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
- *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
- *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
- *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
- *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
- * -# <tt>void OrthancPluginFinalize()</tt>:
- *    This function is invoked by Orthanc during its shutdown. The plugin
- *    must free all its memory.
- * -# <tt>const char* OrthancPluginGetName()</tt>:
- *    The plugin must return a short string to identify itself.
- * -# <tt>const char* OrthancPluginGetVersion()</tt>:
- *    The plugin must return a string containing its version number.
- *
- * The name and the version of a plugin is only used to prevent it
- * from being loaded twice. Note that, in C++, it is mandatory to
- * declare these functions within an <tt>extern "C"</tt> section.
- * 
- * To ensure multi-threading safety, the various REST callbacks are
- * guaranteed to be executed in mutual exclusion since Orthanc
- * 0.8.5. If this feature is undesired (notably when developing
- * high-performance plugins handling simultaneous requests), use
- * ::OrthancPluginRegisterRestCallbackNoLock().
- **/
-
-
-
-/**
- * @defgroup Images Images and compression
- * @brief Functions to deal with images and compressed buffers.
- *
- * @defgroup REST REST
- * @brief Functions to answer REST requests in a callback.
- *
- * @defgroup Callbacks Callbacks
- * @brief Functions to register and manage callbacks by the plugins.
- *
- * @defgroup Worklists Worklists
- * @brief Functions to register and manage worklists.
- *
- * @defgroup Orthanc Orthanc
- * @brief Functions to access the content of the Orthanc server.
- **/
-
-
-
-/**
- * @defgroup Toolbox Toolbox
- * @brief Generic functions to help with the creation of plugins.
- **/
-
-
-
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-#pragma once
-
-
-#include <stdio.h>
-#include <string.h>
-
-#ifdef WIN32
-#define ORTHANC_PLUGINS_API __declspec(dllexport)
-#else
-#define ORTHANC_PLUGINS_API
-#endif
-
-#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
-#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 already in use */,
-    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
-    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
-    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
-    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
-    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
-    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
-    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
-    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
-    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
-    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
-    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
-    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
-    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
-    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
-    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
-    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
-    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
-    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
-    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
-    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
-    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
-    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
-    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
-    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
-    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
-    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
-    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
-    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
-    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
-    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
-    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
-    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
-    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
-    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
-    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
-    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
-    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
-
-    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
-  } OrthancPluginErrorCode;
-
-
-  /**
-   * Forward declaration of one of the mandatory functions for Orthanc
-   * plugins.
-   **/
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
-
-
-  /**
-   * The various HTTP methods for a REST call.
-   **/
-  typedef enum
-  {
-    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
-    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
-    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
-    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
-
-    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
-  } OrthancPluginHttpMethod;
-
-
-  /**
-   * @brief The parameters of a REST request.
-   * @ingroup Callbacks
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The HTTP method.
-     **/
-    OrthancPluginHttpMethod method;    
-
-    /**
-     * @brief The number of groups of the regular expression.
-     **/
-    uint32_t                groupsCount;
-
-    /**
-     * @brief The matched values for the groups of the regular expression.
-     **/
-    const char* const*      groups;
-
-    /**
-     * @brief For a GET request, the number of GET parameters.
-     **/
-    uint32_t                getCount;
-
-    /**
-     * @brief For a GET request, the keys of the GET parameters.
-     **/
-    const char* const*      getKeys;
-
-    /**
-     * @brief For a GET request, the values of the GET parameters.
-     **/
-    const char* const*      getValues;
-
-    /**
-     * @brief For a PUT or POST request, the content of the body.
-     **/
-    const char*             body;
-
-    /**
-     * @brief For a PUT or POST request, the number of bytes of the body.
-     **/
-    uint32_t                bodySize;
-
-
-    /* --------------------------------------------------
-       New in version 0.8.1
-       -------------------------------------------------- */
-
-    /**
-     * @brief The number of HTTP headers.
-     **/
-    uint32_t                headersCount;
-
-    /**
-     * @brief The keys of the HTTP headers (always converted to low-case).
-     **/
-    const char* const*      headersKeys;
-
-    /**
-     * @brief The values of the HTTP headers.
-     **/
-    const char* const*      headersValues;
-
-  } OrthancPluginHttpRequest;
-
-
-  typedef enum 
-  {
-    /* Generic services */
-    _OrthancPluginService_LogInfo = 1,
-    _OrthancPluginService_LogWarning = 2,
-    _OrthancPluginService_LogError = 3,
-    _OrthancPluginService_GetOrthancPath = 4,
-    _OrthancPluginService_GetOrthancDirectory = 5,
-    _OrthancPluginService_GetConfigurationPath = 6,
-    _OrthancPluginService_SetPluginProperty = 7,
-    _OrthancPluginService_GetGlobalProperty = 8,
-    _OrthancPluginService_SetGlobalProperty = 9,
-    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
-    _OrthancPluginService_GetCommandLineArgument = 11,
-    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
-    _OrthancPluginService_GetConfiguration = 13,
-    _OrthancPluginService_BufferCompression = 14,
-    _OrthancPluginService_ReadFile = 15,
-    _OrthancPluginService_WriteFile = 16,
-    _OrthancPluginService_GetErrorDescription = 17,
-    _OrthancPluginService_CallHttpClient = 18,
-    _OrthancPluginService_RegisterErrorCode = 19,
-    _OrthancPluginService_RegisterDictionaryTag = 20,
-    _OrthancPluginService_DicomBufferToJson = 21,
-    _OrthancPluginService_DicomInstanceToJson = 22,
-    _OrthancPluginService_CreateDicom = 23,
-    _OrthancPluginService_ComputeMd5 = 24,
-    _OrthancPluginService_ComputeSha1 = 25,
-    _OrthancPluginService_LookupDictionary = 26,
-
-    /* Registration of callbacks */
-    _OrthancPluginService_RegisterRestCallback = 1000,
-    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
-    _OrthancPluginService_RegisterStorageArea = 1002,
-    _OrthancPluginService_RegisterOnChangeCallback = 1003,
-    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
-    _OrthancPluginService_RegisterWorklistCallback = 1005,
-    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
-
-    /* Sending answers to REST calls */
-    _OrthancPluginService_AnswerBuffer = 2000,
-    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
-    _OrthancPluginService_Redirect = 2002,
-    _OrthancPluginService_SendHttpStatusCode = 2003,
-    _OrthancPluginService_SendUnauthorized = 2004,
-    _OrthancPluginService_SendMethodNotAllowed = 2005,
-    _OrthancPluginService_SetCookie = 2006,
-    _OrthancPluginService_SetHttpHeader = 2007,
-    _OrthancPluginService_StartMultipartAnswer = 2008,
-    _OrthancPluginService_SendMultipartItem = 2009,
-    _OrthancPluginService_SendHttpStatus = 2010,
-    _OrthancPluginService_CompressAndAnswerImage = 2011,
-    _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 worklists */
-    _OrthancPluginService_WorklistAddAnswer = 7000,
-    _OrthancPluginService_WorklistMarkIncomplete = 7001,
-    _OrthancPluginService_WorklistIsMatch = 7002,
-    _OrthancPluginService_WorklistGetDicomQuery = 7003,
-
-    _OrthancPluginService_INTERNAL = 0x7fffffff
-  } _OrthancPluginService;
-
-
-  typedef enum
-  {
-    _OrthancPluginProperty_Description = 1,
-    _OrthancPluginProperty_RootUri = 2,
-    _OrthancPluginProperty_OrthancExplorer = 3,
-
-    _OrthancPluginProperty_INTERNAL = 0x7fffffff
-  } _OrthancPluginProperty;
-
-
-
-  /**
-   * The memory layout of the pixels of an image.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    /**
-     * @brief Graylevel 8bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * one byte.
-     **/
-    OrthancPluginPixelFormat_Grayscale8 = 1,
-
-    /**
-     * @brief Graylevel, unsigned 16bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * two bytes.
-     **/
-    OrthancPluginPixelFormat_Grayscale16 = 2,
-
-    /**
-     * @brief Graylevel, signed 16bpp image.
-     *
-     * The image is graylevel. Each pixel is signed and stored in two
-     * bytes.
-     **/
-    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
-
-    /**
-     * @brief Color image in RGB24 format.
-     *
-     * This format describes a color image. The pixels are stored in 3
-     * consecutive bytes. The memory layout is RGB.
-     **/
-    OrthancPluginPixelFormat_RGB24 = 4,
-
-    /**
-     * @brief Color image in RGBA32 format.
-     *
-     * This format describes a color image. The pixels are stored in 4
-     * consecutive bytes. The memory layout is RGBA.
-     **/
-    OrthancPluginPixelFormat_RGBA32 = 5,
-
-    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
-
-    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginPixelFormat;
-
-
-
-  /**
-   * The content types that are supported by Orthanc plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
-    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
-    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
-
-    _OrthancPluginContentType_INTERNAL = 0x7fffffff
-  } OrthancPluginContentType;
-
-
-
-  /**
-   * The supported types of DICOM resources.
-   **/
-  typedef enum
-  {
-    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
-    OrthancPluginResourceType_Study = 1,       /*!< Study */
-    OrthancPluginResourceType_Series = 2,      /*!< Series */
-    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
-    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
-
-    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
-  } OrthancPluginResourceType;
-
-
-
-  /**
-   * The supported types of changes that can happen to DICOM resources.
-   * @ingroup Callbacks
-   **/
-  typedef enum
-  {
-    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
-    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
-    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
-    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
-    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
-    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
-    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
-    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
-    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
-    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
-    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
-    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
-    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
-    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
-
-    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
-  } OrthancPluginChangeType;
-
-
-  /**
-   * The compression algorithms that are supported by the Orthanc core.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
-    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
-    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
-    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
-
-    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
-  } OrthancPluginCompressionType;
-
-
-  /**
-   * The image formats that are supported by the Orthanc core.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
-    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
-    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
-
-    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginImageFormat;
-
-
-  /**
-   * The value representations present in the DICOM standard (version 2013).
-   * @ingroup Toolbox
-   **/
-  typedef enum
-  {
-    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
-    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
-    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
-    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
-    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
-    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
-    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
-    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
-    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
-    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
-    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
-    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
-    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
-    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
-    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
-    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
-    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
-    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
-    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
-    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
-    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
-    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
-    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
-    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
-    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
-    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
-    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
-
-    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
-  } OrthancPluginValueRepresentation;
-
-
-  /**
-   * The possible output formats for a DICOM-to-JSON conversion.
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomToJson()
-   **/
-  typedef enum
-  {
-    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
-    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
-    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
-
-    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginDicomToJsonFormat;
-
-
-  /**
-   * Flags to customize a DICOM-to-JSON conversion. By default, binary
-   * tags are formatted using Data URI scheme.
-   * @ingroup Toolbox
-   **/
-  typedef enum
-  {
-    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
-    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
-    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
-    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
-    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
-    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
-
-    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
-  } OrthancPluginDicomToJsonFlags;
-
-
-  /**
-   * Flags to the creation of a DICOM file.
-   * @ingroup Toolbox
-   * @see OrthancPluginCreateDicom()
-   **/
-  typedef enum
-  {
-    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
-    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
-
-    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
-  } OrthancPluginCreateDicomFlags;
-
-
-  /**
-   * The constraints on the DICOM identifiers that must be supported
-   * by the database plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
-    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
-    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
-    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
-
-    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
-  } OrthancPluginIdentifierConstraint;
-
-
-  /**
-   * The origin of a DICOM instance that has been received by Orthanc.
-   **/
-  typedef enum
-  {
-    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
-    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
-    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
-    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
-    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
-
-    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
-  } OrthancPluginInstanceOrigin;
-
-
-  /**
-   * @brief A memory buffer allocated by the core system of Orthanc.
-   *
-   * A memory buffer allocated by the core system of Orthanc. When the
-   * content of the buffer is not useful anymore, it must be free by a
-   * call to ::OrthancPluginFreeMemoryBuffer().
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The content of the buffer.
-     **/
-    void*      data;
-
-    /**
-     * @brief The number of bytes in the buffer.
-     **/
-    uint32_t   size;
-  } OrthancPluginMemoryBuffer;
-
-
-
-
-  /**
-   * @brief Opaque structure that represents the HTTP connection to the client application.
-   * @ingroup Callback
-   **/
-  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
-
-
-
-  /**
-   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
-   **/
-  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
-
-
-
-  /**
-   * @brief Opaque structure that represents an image that is uncompressed in memory.
-   * @ingroup Images
-   **/
-  typedef struct _OrthancPluginImage_t OrthancPluginImage;
-
-
-
-  /**
-   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
-   * @ingroup Images
-   **/
-  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
-
-
-
-  /**
-   * @brief Opaque structure to an object that represents a C-Find query.
-   * @ingroup Worklists
-   **/
-  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
-
-
-
-  /**
-   * @brief Opaque structure to an object that represents the answers to a C-Find query.
-   * @ingroup Worklists
-   **/
-  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
-
-
-
-  /**
-   * @brief Signature of a callback function that answers to a REST request.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
-    OrthancPluginRestOutput* output,
-    const char* url,
-    const OrthancPluginHttpRequest* request);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
-    OrthancPluginDicomInstance* instance,
-    const char* instanceId);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
-    OrthancPluginChangeType changeType,
-    OrthancPluginResourceType resourceType,
-    const char* resourceId);
-
-
-
-  /**
-   * @brief Signature of a callback function to decode a DICOM instance as an image.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
-    OrthancPluginImage** target,
-    const void* dicom,
-    const uint32_t size,
-    uint32_t frameIndex);
-
-
-
-  /**
-   * @brief Signature of a function to free dynamic memory.
-   **/
-  typedef void (*OrthancPluginFree) (void* buffer);
-
-
-
-  /**
-   * @brief Callback for writing to the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
-   *
-   * @param uuid The UUID of the file.
-   * @param content The content of the file.
-   * @param size The size of the file.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
-    const char* uuid,
-    const void* content,
-    int64_t size,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for reading from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
-   *
-   * @param content The content of the file (output).
-   * @param size The size of the file (output).
-   * @param uuid The UUID of the file of interest.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
-    void** content,
-    int64_t* size,
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for removing a file from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
-   *
-   * @param uuid The UUID of the file to be removed.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
-   *
-   * Signature of a callback function that is triggered when Orthanc
-   * receives a C-Find SCP request against modality worklists.
-   *
-   * @param answers The target structure where answers must be stored.
-   * @param query The worklist query.
-   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
-   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
-    OrthancPluginWorklistAnswers*     answers,
-    const OrthancPluginWorklistQuery* query,
-    const char*                       remoteAet,
-    const char*                       calledAet);
-
-
-
-  /**
-   * @brief Data structure that contains information about the Orthanc core.
-   **/
-  typedef struct _OrthancPluginContext_t
-  {
-    void*                     pluginsManager;
-    const char*               orthancVersion;
-    OrthancPluginFree         Free;
-    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
-                                              _OrthancPluginService service,
-                                              const void* params);
-  } OrthancPluginContext;
-
-
-  
-  /**
-   * @brief An entry in the dictionary of DICOM tags.
-   **/
-  typedef struct
-  {
-    uint16_t                          group;            /*!< The group of the tag */
-    uint16_t                          element;          /*!< The element of the tag */
-    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
-    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
-    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
-  } OrthancPluginDictionaryEntry;
-
-
-
-  /**
-   * @brief Free a string.
-   * 
-   * Free a string that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param str The string to be freed.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
-    OrthancPluginContext* context, 
-    char* str)
-  {
-    if (str != NULL)
-    {
-      context->Free(str);
-    }
-  }
-
-
-  /**
-   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
-   * 
-   * This function checks whether the version of this C header is
-   * compatible with the current version of Orthanc. The result of
-   * this function should always be checked in the
-   * OrthancPluginInitialize() entry point of the plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return 1 if and only if the versions are compatible. If the
-   * result is 0, the initialization of the plugin should fail.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
-    OrthancPluginContext* context)
-  {
-    int major, minor, revision;
-
-    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
-        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
-        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
-        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
-    {
-      /* Mismatch in the size of the enumerations */
-      return 0;
-    }
-
-    /* Assume compatibility with the mainline */
-    if (!strcmp(context->orthancVersion, "mainline"))
-    {
-      return 1;
-    }
-
-    /* Parse the version of the Orthanc core */
-    if ( 
-#ifdef _MSC_VER
-      sscanf_s
-#else
-      sscanf
-#endif
-      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
-    {
-      return 0;
-    }
-
-    /* Check the major number of the version */
-
-    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the minor number of the version */
-
-    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the revision number of the version */
-
-    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
-    {
-      return 1;
-    }
-    else
-    {
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Free a memory buffer.
-   * 
-   * Free a memory buffer that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer to release.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
-    OrthancPluginContext* context, 
-    OrthancPluginMemoryBuffer* buffer)
-  {
-    context->Free(buffer->data);
-  }
-
-
-  /**
-   * @brief Log an error.
-   *
-   * Log an error message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogError, message);
-  }
-
-
-  /**
-   * @brief Log a warning.
-   *
-   * Log a warning message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
-  }
-
-
-  /**
-   * @brief Log an information.
-   *
-   * Log an information message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
-  }
-
-
-
-  typedef struct
-  {
-    const char* pathRegularExpression;
-    OrthancPluginRestCallback callback;
-  } _OrthancPluginRestCallback;
-
-  /**
-   * @brief Register a REST callback.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   *
-   * Each REST callback is guaranteed to run in mutual exclusion.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   * @see OrthancPluginRegisterRestCallbackNoLock()
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
-  }
-
-
-
-  /**
-   * @brief Register a REST callback, without locking.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   *
-   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
-   * will NOT be invoked in mutual exclusion. This can be useful for
-   * high-performance plugins that must handle concurrent requests
-   * (Orthanc uses a pool of threads, one thread being assigned to
-   * each incoming HTTP request). Of course, it is up to the plugin to
-   * implement the required locking mechanisms.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   * @see OrthancPluginRegisterRestCallback()
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnStoredInstanceCallback callback;
-  } _OrthancPluginOnStoredInstanceCallback;
-
-  /**
-   * @brief Register a callback for received instances.
-   *
-   * This function registers a callback function that is called
-   * whenever a new DICOM instance is stored into the Orthanc core.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
-    OrthancPluginContext*                  context,
-    OrthancPluginOnStoredInstanceCallback  callback)
-  {
-    _OrthancPluginOnStoredInstanceCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              answer;
-    uint32_t                 answerSize;
-    const char*              mimeType;
-  } _OrthancPluginAnswerBuffer;
-
-  /**
-   * @brief Answer to a REST request.
-   *
-   * This function answers to a REST request with the content of a memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param answer Pointer to the memory buffer containing the answer.
-   * @param answerSize Number of bytes of the answer.
-   * @param mimeType The MIME type of the answer.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              answer,
-    uint32_t                 answerSize,
-    const char*              mimeType)
-  {
-    _OrthancPluginAnswerBuffer params;
-    params.output = output;
-    params.answer = answer;
-    params.answerSize = answerSize;
-    params.mimeType = mimeType;
-    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginPixelFormat  format;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-  } _OrthancPluginCompressAndAnswerPngImage;
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginImageFormat  imageFormat;
-    OrthancPluginPixelFormat  pixelFormat;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-    uint8_t                   quality;
-  } _OrthancPluginCompressAndAnswerImage;
-
-
-  /**
-   * @brief Answer to a REST request with a PNG image.
-   *
-   * This function answers to a REST request with a PNG image. The
-   * parameters of this function describe a memory buffer that
-   * contains an uncompressed image. The image will be automatically compressed
-   * as a PNG image by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer containing the uncompressed image.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
-    OrthancPluginContext*     context,
-    OrthancPluginRestOutput*  output,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    const void*               buffer)
-  {
-    _OrthancPluginCompressAndAnswerImage params;
-    params.output = output;
-    params.imageFormat = OrthancPluginImageFormat_Png;
-    params.pixelFormat = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    params.quality = 0;  /* No quality for PNG */
-    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 instanceId;
-  } _OrthancPluginGetDicomForInstance;
-
-  /**
-   * @brief Retrieve a DICOM instance using its Orthanc identifier.
-   * 
-   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
-   * file is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 instanceId)
-  {
-    _OrthancPluginGetDicomForInstance params;
-    params.target = target;
-    params.instanceId = instanceId;
-    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-  } _OrthancPluginRestApiGet;
-
-  /**
-   * @brief Make a GET call to the built-in Orthanc REST API.
-   * 
-   * Make a GET call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGetAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
-  }
-
-
-
-  /**
-   * @brief Make a GET call to the REST API, as tainted by the plugins.
-   * 
-   * Make a GET call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    const char*                 body;
-    uint32_t                    bodySize;
-  } _OrthancPluginRestApiPostPut;
-
-  /**
-   * @brief Make a POST call to the built-in Orthanc REST API.
-   * 
-   * Make a POST call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPostAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
-  }
-
-
-  /**
-   * @brief Make a POST call to the REST API, as tainted by the plugins.
-   * 
-   * Make a POST call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPost
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
-  }
-
-
-
-  /**
-   * @brief Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiDeleteAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
-  }
-
-
-  /**
-   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
-   * 
-   * Make a DELETE call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. 
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiDelete
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the built-in Orthanc REST API.
-   * 
-   * Make a PUT call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPutAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the REST API, as tainted by the plugins.
-   * 
-   * Make a PUT call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPut
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              argument;
-  } _OrthancPluginOutputPlusArgument;
-
-  /**
-   * @brief Redirect a REST request.
-   *
-   * This function answers to a REST request by redirecting the user
-   * to another URI using HTTP status 301.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param redirection Where to redirect.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              redirection)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = redirection;
-    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const char*  argument;
-  } _OrthancPluginRetrieveDynamicString;
-
-  /**
-   * @brief Look for a patient.
-   *
-   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored patients).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param patientID The Patient ID of interest.
-   * @return The NULL value if the patient is non-existent, or a string containing the 
-   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
-    OrthancPluginContext*  context,
-    const char*            patientID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = patientID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study.
-   *
-   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param studyUID The Study Instance UID of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
-    OrthancPluginContext*  context,
-    const char*            studyUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = studyUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study, using the accession number.
-   *
-   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param accessionNumber The Accession Number of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
-    OrthancPluginContext*  context,
-    const char*            accessionNumber)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = accessionNumber;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a series.
-   *
-   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored series).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param seriesUID The Series Instance UID of interest.
-   * @return The NULL value if the series is non-existent, or a string containing the 
-   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
-    OrthancPluginContext*  context,
-    const char*            seriesUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = seriesUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for an instance.
-   *
-   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored instances).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param sopInstanceUID The SOP Instance UID of interest.
-   * @return The NULL value if the instance is non-existent, or a string containing the 
-   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
-    OrthancPluginContext*  context,
-    const char*            sopInstanceUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = sopInstanceUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    uint16_t                 status;
-  } _OrthancPluginSendHttpStatusCode;
-
-  /**
-   * @brief Send a HTTP status code.
-   *
-   * This function answers to a REST request by sending a HTTP status
-   * code (such as "400 - Bad Request"). Note that:
-   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
-   * - Redirections (status 301) must use ::OrthancPluginRedirect().
-   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
-   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param status The HTTP status code to be sent.
-   * @ingroup REST
-   * @see OrthancPluginSendHttpStatus()
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    uint16_t                 status)
-  {
-    _OrthancPluginSendHttpStatusCode params;
-    params.output = output;
-    params.status = status;
-    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
-  }
-
-
-  /**
-   * @brief Signal that a REST request is not authorized.
-   *
-   * This function answers to a REST request by signaling that it is
-   * not authorized.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param realm The realm for the authorization process.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              realm)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = realm;
-    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
-  }
-
-
-  /**
-   * @brief Signal that this URI does not support this HTTP method.
-   *
-   * This function answers to a REST request by signaling that the
-   * queried URI does not support this method.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              allowedMethods)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = allowedMethods;
-    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              key;
-    const char*              value;
-  } _OrthancPluginSetHttpHeader;
-
-  /**
-   * @brief Set a cookie.
-   *
-   * This function sets a cookie in the HTTP client.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param cookie The cookie to be set.
-   * @param value The value of the cookie.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              cookie,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = cookie;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
-  }
-
-
-  /**
-   * @brief Set some HTTP header.
-   *
-   * This function sets a HTTP header in the HTTP answer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param key The HTTP header to be set.
-   * @param value The value of the HTTP header.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              key,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = key;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
-  }
-
-
-  typedef struct
-  {
-    char**                       resultStringToFree;
-    const char**                 resultString;
-    int64_t*                     resultInt64;
-    const char*                  key;
-    OrthancPluginDicomInstance*  instance;
-    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
-  } _OrthancPluginAccessDicomInstance;
-
-
-  /**
-   * @brief Get the AET of a DICOM instance.
-   *
-   * This function returns the Application Entity Title (AET) of the
-   * DICOM modality from which a DICOM instance originates.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The AET if success, NULL if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the size of a DICOM file.
-   *
-   * This function returns the number of bytes of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The size of the file, -1 in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    int64_t size;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &size;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return size;
-    }
-  }
-
-
-  /**
-   * @brief Get the data of a DICOM file.
-   *
-   * This function returns a pointer to the content of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The pointer to the DICOM data, NULL in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file.
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance. In contrast with
-   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
-   * its simplified version.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Check whether a DICOM instance is associated with some metadata.
-   *
-   * This function checks whether the DICOM instance of interest is
-   * associated with some metadata. As of Orthanc 0.8.1, in the
-   * callbacks registered by
-   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
-   * possibly available metadata are "ReceptionDate", "RemoteAET" and
-   * "IndexInSeries".
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    int64_t result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return (result != 0);
-    }
-  }
-
-
-  /**
-   * @brief Get the value of some metadata associated with a given DICOM instance.
-   *
-   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
-   * Before calling this function, the existence of the metadata must have been checked with
-   * ::OrthancPluginHasInstanceMetadata().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return The metadata value if success, NULL if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginStorageCreate  create;
-    OrthancPluginStorageRead    read;
-    OrthancPluginStorageRemove  remove;
-    OrthancPluginFree           free;
-  } _OrthancPluginRegisterStorageArea;
-
-  /**
-   * @brief Register a custom storage area.
-   *
-   * This function registers a custom storage area, to replace the
-   * built-in way Orthanc stores its files on the filesystem. This
-   * function must be called during the initialization of the plugin,
-   * i.e. inside the OrthancPluginInitialize() public function.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param create The callback function to store a file on the custom storage area.
-   * @param read The callback function to read a file from the custom storage area.
-   * @param remove The callback function to remove a file from the custom storage area.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
-    OrthancPluginContext*       context,
-    OrthancPluginStorageCreate  create,
-    OrthancPluginStorageRead    read,
-    OrthancPluginStorageRemove  remove)
-  {
-    _OrthancPluginRegisterStorageArea params;
-    params.create = create;
-    params.read = read;
-    params.remove = remove;
-
-#ifdef  __cplusplus
-    params.free = ::free;
-#else
-    params.free = free;
-#endif
-
-    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
-  }
-
-
-
-  /**
-   * @brief Return the path to the Orthanc executable.
-   *
-   * This function returns the path to the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the directory containing the Orthanc.
-   *
-   * This function returns the path to the directory containing the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the path to the configuration file(s).
-   *
-   * This function returns the path to the configuration file(s) that
-   * was specified when starting Orthanc. Since version 0.9.1, this
-   * path can refer to a folder that stores a set of configuration
-   * files. This function is deprecated in favor of
-   * OrthancPluginGetConfiguration().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   * @see OrthancPluginGetConfiguration()
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnChangeCallback callback;
-  } _OrthancPluginOnChangeCallback;
-
-  /**
-   * @brief Register a callback to monitor changes.
-   *
-   * This function registers a callback function that is called
-   * whenever a change happens to some DICOM resource.
-   *
-   * @warning If your change callback has to call the REST API of
-   * Orthanc, you should make these calls in a separate thread (with
-   * the events passing through a message queue). Otherwise, this
-   * could result in deadlocks in the presence of other plugins or Lua
-   * script.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginOnChangeCallback  callback)
-  {
-    _OrthancPluginOnChangeCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    const char* plugin;
-    _OrthancPluginProperty property;
-    const char* value;
-  } _OrthancPluginSetPluginProperty;
-
-
-  /**
-   * @brief Set the URI where the plugin provides its Web interface.
-   *
-   * For plugins that come with a Web interface, this function
-   * declares the entry path where to find this interface. This
-   * information is notably used in the "Plugins" page of Orthanc
-   * Explorer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The root URI for this plugin.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
-    OrthancPluginContext*  context,
-    const char*            uri)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_RootUri;
-    params.value = uri;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  /**
-   * @brief Set a description for this plugin.
-   *
-   * Set a description for this plugin. It is displayed in the
-   * "Plugins" page of Orthanc Explorer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param description The description.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
-    OrthancPluginContext*  context,
-    const char*            description)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_Description;
-    params.value = description;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  /**
-   * @brief Extend the JavaScript code of Orthanc Explorer.
-   *
-   * Add JavaScript code to customize the default behavior of Orthanc
-   * Explorer. This can for instance be used to add new buttons.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param javascript The custom JavaScript code.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
-    OrthancPluginContext*  context,
-    const char*            javascript)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_OrthancExplorer;
-    params.value = javascript;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  typedef struct
-  {
-    char**       result;
-    int32_t      property;
-    const char*  value;
-  } _OrthancPluginGlobalProperty;
-
-
-  /**
-   * @brief Get the value of a global property.
-   *
-   * Get the value of a global property that is stored in the Orthanc database. Global
-   * properties whose index is below 1024 are reserved by Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param property The global property of interest.
-   * @param defaultValue The value to return, if the global property is unset.
-   * @return The value of the global property, or NULL in the case of an error. This
-   * string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
-    OrthancPluginContext*  context,
-    int32_t                property,
-    const char*            defaultValue)
-  {
-    char* result;
-
-    _OrthancPluginGlobalProperty params;
-    params.result = &result;
-    params.property = property;
-    params.value = defaultValue;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Set the value of a global property.
-   *
-   * Set the value of a global property into the Orthanc
-   * database. Setting a global property can be used by plugins to
-   * save their internal parameters. Plugins are only allowed to set
-   * properties whose index are above or equal to 1024 (properties
-   * below 1024 are read-only and reserved by Orthanc).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param property The global property of interest.
-   * @param value The value to be set in the global property.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
-    OrthancPluginContext*  context,
-    int32_t                property,
-    const char*            value)
-  {
-    _OrthancPluginGlobalProperty params;
-    params.result = NULL;
-    params.property = property;
-    params.value = value;
-
-    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
-  }
-
-
-
-  typedef struct
-  {
-    int32_t   *resultInt32;
-    uint32_t  *resultUint32;
-    int64_t   *resultInt64;
-    uint64_t  *resultUint64;
-  } _OrthancPluginReturnSingleValue;
-
-  /**
-   * @brief Get the number of command-line arguments.
-   *
-   * Retrieve the number of command-line arguments that were used to launch Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The number of arguments.
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-  /**
-   * @brief Get the value of a command-line argument.
-   *
-   * Get the value of one of the command-line arguments that were used
-   * to launch Orthanc. The number of available arguments can be
-   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param argument The index of the argument.
-   * @return The value of the argument, or NULL in the case of an error. This
-   * string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
-    OrthancPluginContext*  context,
-    uint32_t               argument)
-  {
-    char* result;
-
-    _OrthancPluginGlobalProperty params;
-    params.result = &result;
-    params.property = (int32_t) argument;
-    params.value = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the expected version of the database schema.
-   *
-   * Retrieve the expected version of the database schema.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The version.
-   * @ingroup Callbacks
-   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the content of the configuration file(s).
-   *
-   * This function returns the content of the configuration that is
-   * used by Orthanc, formatted as a JSON string.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the configuration. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              subType;
-    const char*              contentType;
-  } _OrthancPluginStartMultipartAnswer;
-
-  /**
-   * @brief Start an HTTP multipart answer.
-   *
-   * Initiates a HTTP multipart answer, as the result of a REST request.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param subType The sub-type of the multipart answer ("mixed" or "related").
-   * @param contentType The MIME type of the items in the multipart answer.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginSendMultipartItem(), 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 char*                     buffer;
-    uint32_t                        size;
-    OrthancPluginDicomToJsonFormat  format;
-    OrthancPluginDicomToJsonFlags   flags;
-    uint32_t                        maxStringLength;
-  } _OrthancPluginDicomToJson;
-
-
-  /**
-   * @brief Format a DICOM memory buffer as a JSON string.
-   *
-   * This function takes as input a memory buffer containing a DICOM
-   * file, and outputs a JSON string representing the tags of this
-   * DICOM file.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer containing the DICOM file.
-   * @param size The size of the memory buffer.
-   * @param format The output format.
-   * @param flags Flags governing the output.
-   * @param maxStringLength The maximum length of a field. Too long fields will
-   * be output as "null". The 0 value means no maximum length.
-   * @return The NULL value if the case of an error, or the JSON
-   * string. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
-    OrthancPluginContext*           context,
-    const char*                     buffer,
-    uint32_t                        size,
-    OrthancPluginDicomToJsonFormat  format,
-    OrthancPluginDicomToJsonFlags   flags, 
-    uint32_t                        maxStringLength)
-  {
-    char* result;
-
-    _OrthancPluginDicomToJson params;
-    memset(&params, 0, sizeof(params));
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-    params.format = format;
-    params.flags = flags;
-    params.maxStringLength = maxStringLength;
-
-    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Format a DICOM instance as a JSON string.
-   *
-   * This function formats a DICOM instance that is stored in Orthanc,
-   * and outputs a JSON string representing the tags of this DICOM
-   * instance.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instanceId The Orthanc identifier of the instance.
-   * @param format The output format.
-   * @param flags Flags governing the output.
-   * @param maxStringLength The maximum length of a field. Too long fields will
-   * be output as "null". The 0 value means no maximum length.
-   * @return The NULL value if the case of an error, or the JSON
-   * string. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
-    OrthancPluginContext*           context,
-    const char*                     instanceId,
-    OrthancPluginDicomToJsonFormat  format,
-    OrthancPluginDicomToJsonFlags   flags, 
-    uint32_t                        maxStringLength)
-  {
-    char* result;
-
-    _OrthancPluginDicomToJson params;
-    memset(&params, 0, sizeof(params));
-    params.result = &result;
-    params.instanceId = instanceId;
-    params.format = format;
-    params.flags = flags;
-    params.maxStringLength = maxStringLength;
-
-    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    uint32_t                    headersCount;
-    const char* const*          headersKeys;
-    const char* const*          headersValues;
-    int32_t                     afterPlugins;
-  } _OrthancPluginRestApiGet2;
-
-  /**
-   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
-   * 
-   * Make a GET call to the Orthanc REST API with extended
-   * parameters. The result to the query is stored into a newly
-   * allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param headersCount The number of HTTP headers.
-   * @param headersKeys Array containing the keys of the HTTP headers.
-   * @param headersValues Array containing the values of the HTTP headers.
-   * @param afterPlugins If 0, the built-in API of Orthanc is used.
-   * If 1, the API is tainted by the plugins.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    uint32_t                    headersCount,
-    const char* const*          headersKeys,
-    const char* const*          headersValues,
-    int32_t                     afterPlugins)
-  {
-    _OrthancPluginRestApiGet2 params;
-    params.target = target;
-    params.uri = uri;
-    params.headersCount = headersCount;
-    params.headersKeys = headersKeys;
-    params.headersValues = headersValues;
-    params.afterPlugins = afterPlugins;
-
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginWorklistCallback callback;
-  } _OrthancPluginWorklistCallback;
-
-  /**
-   * @brief Register a callback to handle modality worklists requests.
-   *
-   * This function registers a callback to handle C-Find SCP requests
-   * on modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginWorklistCallback  callback)
-  {
-    _OrthancPluginWorklistCallback params;
-    params.callback = callback;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
-  }
-
-
-  
-  typedef struct
-  {
-    OrthancPluginWorklistAnswers*      answers;
-    const OrthancPluginWorklistQuery*  query;
-    const void*                        dicom;
-    uint32_t                           size;
-  } _OrthancPluginWorklistAnswersOperation;
-
-  /**
-   * @brief Add one answer to some modality worklist request.
-   *
-   * This function adds one worklist (encoded as a DICOM file) to the
-   * set of answers corresponding to some C-Find SCP request against
-   * modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param answers The set of answers.
-   * @param query The worklist query, as received by the callback.
-   * @param dicom The worklist to answer, encoded as a DICOM file.
-   * @param size The size of the DICOM file.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
-    OrthancPluginContext*             context,
-    OrthancPluginWorklistAnswers*     answers,
-    const OrthancPluginWorklistQuery* query,
-    const void*                       dicom,
-    uint32_t                          size)
-  {
-    _OrthancPluginWorklistAnswersOperation params;
-    params.answers = answers;
-    params.query = query;
-    params.dicom = dicom;
-    params.size = size;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
-  }
-
-
-  /**
-   * @brief Mark the set of worklist answers as incomplete.
-   *
-   * This function marks as incomplete the set of answers
-   * corresponding to some C-Find SCP request against modality
-   * worklists. This must be used if canceling the handling of a
-   * request when too many answers are to be returned.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param answers The set of answers.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
-    OrthancPluginContext*          context,
-    OrthancPluginWorklistAnswers*  answers)
-  {
-    _OrthancPluginWorklistAnswersOperation params;
-    params.answers = answers;
-    params.query = NULL;
-    params.dicom = NULL;
-    params.size = 0;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
-  }
-
-
-  typedef struct
-  {
-    const OrthancPluginWorklistQuery*  query;
-    const void*                        dicom;
-    uint32_t                           size;
-    int32_t*                           isMatch;
-    OrthancPluginMemoryBuffer*         target;
-  } _OrthancPluginWorklistQueryOperation;
-
-  /**
-   * @brief Test whether a worklist matches the query.
-   *
-   * This function checks whether one worklist (encoded as a DICOM
-   * file) matches the C-Find SCP query against modality
-   * worklists. This function must be called before adding the
-   * worklist as an answer through OrthancPluginWorklistAddAnswer().
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param query The worklist query, as received by the callback.
-   * @param dicom The worklist to answer, encoded as a DICOM file.
-   * @param size The size of the DICOM file.
-   * @return 1 if the worklist matches the query, 0 otherwise.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
-    OrthancPluginContext*              context,
-    const OrthancPluginWorklistQuery*  query,
-    const void*                        dicom,
-    uint32_t                           size)
-  {
-    int32_t isMatch = 0;
-
-    _OrthancPluginWorklistQueryOperation params;
-    params.query = query;
-    params.dicom = dicom;
-    params.size = size;
-    params.isMatch = &isMatch;
-    params.target = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
-    {
-      return isMatch;
-    }
-    else
-    {
-      /* Error: Assume non-match */
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Retrieve the worklist query as a DICOM file.
-   *
-   * This function retrieves the DICOM file that underlies a C-Find
-   * SCP query against modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param query The worklist query, as received by the callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
-    OrthancPluginContext*              context,
-    OrthancPluginMemoryBuffer*         target,
-    const OrthancPluginWorklistQuery*  query)
-  {
-    _OrthancPluginWorklistQueryOperation params;
-    params.query = query;
-    params.dicom = NULL;
-    params.size = 0;
-    params.isMatch = NULL;
-    params.target = target;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
-  }
-
-
-  /**
-   * @brief Get the origin of a DICOM file.
-   *
-   * This function returns the origin of a DICOM instance that has been received by Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The origin of the instance.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    OrthancPluginInstanceOrigin origin;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultOrigin = &origin;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return OrthancPluginInstanceOrigin_Unknown;
-    }
-    else
-    {
-      return origin;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*     target;
-    const char*                    json;
-    const OrthancPluginImage*      pixelData;
-    OrthancPluginCreateDicomFlags  flags;
-  } _OrthancPluginCreateDicom;
-
-  /**
-   * @brief Create a DICOM instance from a JSON string and an image.
-   *
-   * This function takes as input a string containing a JSON file
-   * describing the content of a DICOM instance. As an output, it
-   * writes the corresponding DICOM instance to a newly allocated
-   * memory buffer. Additionally, an image to be encoded within the
-   * DICOM instance can also be provided.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param json The input JSON file.
-   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
-   * @param flags Flags governing the output.
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomBufferToJson
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
-    OrthancPluginContext*          context,
-    OrthancPluginMemoryBuffer*     target,
-    const char*                    json,
-    const OrthancPluginImage*      pixelData,
-    OrthancPluginCreateDicomFlags  flags)
-  {
-    _OrthancPluginCreateDicom params;
-    params.target = target;
-    params.json = json;
-    params.pixelData = pixelData;
-    params.flags = flags;
-
-    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginDecodeImageCallback callback;
-  } _OrthancPluginDecodeImageCallback;
-
-  /**
-   * @brief Register a callback to handle the decoding of DICOM images.
-   *
-   * This function registers a custom callback to the decoding of
-   * DICOM images, replacing the built-in decoder of Orthanc.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
-    OrthancPluginContext*             context,
-    OrthancPluginDecodeImageCallback  callback)
-  {
-    _OrthancPluginDecodeImageCallback params;
-    params.callback = callback;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
-  }
-  
-
-
-  typedef struct
-  {
-    OrthancPluginImage**       target;
-    OrthancPluginPixelFormat   format;
-    uint32_t                   width;
-    uint32_t                   height;
-    uint32_t                   pitch;
-    void*                      buffer;
-    const void*                constBuffer;
-    uint32_t                   bufferSize;
-    uint32_t                   frameIndex;
-  } _OrthancPluginCreateImage;
-
-
-  /**
-   * @brief Create an image.
-   *
-   * This function creates an image of given size and format.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param format The format of the pixels.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
-    OrthancPluginContext*     context,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-
-    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-  /**
-   * @brief Create an image pointing to a memory buffer.
-   *
-   * This function creates an image whose content points to a memory
-   * buffer managed by the plugin. Note that the buffer is directly
-   * accessed, no memory is allocated and no data is copied.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param format The format of the pixels.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer.
-   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
-    OrthancPluginContext*     context,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    void*                     buffer)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-
-    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  /**
-   * @brief Decode one frame from a DICOM instance.
-   *
-   * This function decodes one frame of a DICOM image that is stored
-   * in a memory buffer. This function will give the same result as
-   * OrthancPluginUncompressImage() for single-frame DICOM images.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer Pointer to a memory buffer containing the DICOM image.
-   * @param bufferSize Size of the memory buffer containing the DICOM image.
-   * @param frameIndex The index of the frame of interest in a multi-frame image.
-   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               bufferSize,
-    uint32_t               frameIndex)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.constBuffer = buffer;
-    params.bufferSize = bufferSize;
-    params.frameIndex = frameIndex;
-
-    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const void*  buffer;
-    uint32_t     size;
-  } _OrthancPluginComputeHash;
-
-  /**
-   * @brief Compute an MD5 hash.
-   *
-   * This functions computes the MD5 cryptographic hash of the given memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The source memory buffer.
-   * @param size The size in bytes of the source buffer.
-   * @return The NULL value in case of error, or a string containing the cryptographic hash.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               size)
-  {
-    char* result;
-
-    _OrthancPluginComputeHash params;
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-
-    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Compute a SHA-1 hash.
-   *
-   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The source memory buffer.
-   * @param size The size in bytes of the source buffer.
-   * @return The NULL value in case of error, or a string containing the cryptographic hash.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               size)
-  {
-    char* result;
-
-    _OrthancPluginComputeHash params;
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-
-    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginDictionaryEntry* target;
-    const char*                   name;
-  } _OrthancPluginLookupDictionary;
-
-  /**
-   * @brief Get information about the given DICOM tag.
-   *
-   * This functions makes a lookup in the dictionary of DICOM tags
-   * that are known to Orthanc, and returns information about this
-   * tag. The tag can be specified using its human-readable name
-   * (e.g. "PatientName") or a set of two hexadecimal numbers
-   * (e.g. "0010-0020").
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target Where to store the information about the tag.
-   * @param name The name of the DICOM tag.
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
-    OrthancPluginContext*          context,
-    OrthancPluginDictionaryEntry*  target,
-    const char*                    name)
-  {
-    _OrthancPluginLookupDictionary params;
-    params.target = target;
-    params.name = name;
-    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
-  }
-
-
-
-  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);
-  }
-
-
-#ifdef  __cplusplus
-}
-#endif
-
-
-/** @} */
-
--- a/OrthancStone/Samples/Sdl/BoostExtendedConfiguration.cmake	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-# Stone of Orthanc
-# 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/>.
-
-
-if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
-  set(BOOST_EXTENDED_SOURCES
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp
-    ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp
-    ${BOOST_SOURCES_DIR}/libs/chrono/src/thread_clock.cpp
-    ${BOOST_SOURCES_DIR}/libs/chrono/src/chrono.cpp
-    ${BOOST_SOURCES_DIR}/libs/chrono/src/process_cpu_clocks.cpp
-    #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp
-    )
-  add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB)
-else()
-  link_libraries(boost_program_options)
-endif()
--- a/OrthancStone/Samples/Sdl/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-cmake_minimum_required(VERSION 2.8.10)
-
-project(OrthancStone)
-
-include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake)
-
-if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  set(ORTHANC_BOOST_COMPONENTS program_options)
-
-  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
-  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
-  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
-  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake)
-  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
-
-else()
-  set(ENABLE_GOOGLE_TEST ON)
-  set(ENABLE_LOCALE ON)  # Necessary for text rendering
-  set(ENABLE_OPENGL ON)  #  <==
-  set(ENABLE_WEB_CLIENT ON)
-endif()
-
-set(ENABLE_DCMTK ON)  # <==
-set(ENABLE_SDL ON)
-
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/Utilities.cmake)
-
-if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  # This include must be after "OrthancStoneConfiguration.cmake" to
-  # have "BOOST_SOURCES_DIR" defined
-  include(${CMAKE_SOURCE_DIR}/BoostExtendedConfiguration.cmake)
-endif()
-
-
-DownloadPackage(
-  "a24b8136b8f3bb93f166baf97d9328de"
-  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
-  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
-
-EmbedResources(
-  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut
-  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
-  )
-
-SortFilesInSourceGroups()
-
-add_library(OrthancStone STATIC
-  ${ORTHANC_STONE_SOURCES}
-  ${AUTOGENERATED_SOURCES}
-  ${BOOST_EXTENDED_SOURCES}
-  )
-
-message(${AUTOGENERATED_SOURCES})
-
-
-
-#############################
-project(RtViewerSdl)
-
-add_executable(RtViewerSdl
-  RtViewer/RtViewerSdl.cpp
-  SdlHelpers.h
-  ../Common/RtViewerApp.cpp
-  ../Common/RtViewerApp.h
-  ../Common/RtViewerView.cpp
-  ../Common/RtViewerView.h
-  ../Common/SampleHelpers.h
-  )
-
-target_link_libraries(RtViewerSdl OrthancStone ${DCMTK_LIBRARIES})
-
-#############################
-project(SdlSimpleViewer)
-
-add_executable(SdlSimpleViewer
-  SdlHelpers.h
-  ../Common/SampleHelpers.h
-  SingleFrameViewer/SdlSimpleViewerApplication.h
-  SingleFrameViewer/SdlSimpleViewer.cpp
-  )
-
-target_link_libraries(SdlSimpleViewer OrthancStone ${DCMTK_LIBRARIES})
-
-#############################
-project(UnitTests)
-
-add_executable(UnitTests
-  ${GOOGLE_TEST_SOURCES}
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/SortedFramesTests.cpp
-  ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
-  )
-
-target_link_libraries(UnitTests OrthancStone)
-
-add_custom_command(
-  TARGET UnitTests
-  POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy
-    "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" 
-    "$<TARGET_FILE_DIR:UnitTests>/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json"
-)
-
-target_link_libraries(UnitTests OrthancStone ${DCMTK_LIBRARIES})
--- a/OrthancStone/Samples/Sdl/RtViewer/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-cmake_minimum_required(VERSION 2.8.10)
-
-project(RtViewerSdl)
-
-include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
-
-if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  set(ORTHANC_BOOST_COMPONENTS program_options)
-
-  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
-  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
-  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
-  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake)
-  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
-
-else()
-  set(ENABLE_GOOGLE_TEST ON)
-  set(ENABLE_LOCALE ON)  # Necessary for text rendering
-  set(ENABLE_OPENGL ON)  #  <==
-  set(ENABLE_WEB_CLIENT ON)
-endif()
-
-set(ENABLE_DCMTK ON)  # <==
-set(ENABLE_SDL ON)
-
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/../Utilities.cmake)
-
-if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  # This include must be after "OrthancStoneConfiguration.cmake" to
-  # have "BOOST_SOURCES_DIR" defined
-  include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake)
-endif()
-
-DownloadPackage(
-  "a24b8136b8f3bb93f166baf97d9328de"
-  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
-  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
-
-EmbedResources(
-  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut
-  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
-  )
-
-SortFilesInSourceGroups()
-
-add_executable(RtViewerSdl
-  RtViewerSdl.cpp
-  ../SdlHelpers.h
-  ../../Common/RtViewerApp.cpp
-  ../../Common/RtViewerApp.h
-  ../../Common/RtViewerView.cpp
-  ../../Common/RtViewerView.h
-  ../../Common/SampleHelpers.h
-  ${ORTHANC_STONE_SOURCES}
-  ${AUTOGENERATED_SOURCES}
-  ${BOOST_EXTENDED_SOURCES}
-  )
-
-target_link_libraries(RtViewerSdl ${DCMTK_LIBRARIES})
--- a/OrthancStone/Samples/Sdl/RtViewer/CMakeSettings.json	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-{
-  // this file is meant to be used with Visual Studio CMake support
-  // tested with VS 16.5.4+ 
-  "configurations": [
-    {
-      "name": "x64-Debug",
-      "generator": "Ninja",
-      "configurationType": "Debug",
-      "inheritEnvironments": [ "msvc_x64_x64" ],
-      "buildRoot": "${projectDir}/../../../../out/build-stone-sdl-RtViewer-ninja-msvc16-x64-Debug",
-      "installRoot": "${projectDir}/../../../../out/install-stone-sdl-RtViewer-ninja-msvc16-x64-Debug",
-      "cmakeCommandArgs": "",
-      "buildCommandArgs": "-v",
-      "ctestCommandArgs": "",
-      "variables": [
-        {
-          "name": "ALLOW_DOWNLOADS",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "MSVC_MULTIPLE_PROCESSES",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "STATIC_BUILD",
-          "value": "True",
-          "type": "BOOL"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
--- a/OrthancStone/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,461 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../Common/RtViewerApp.h"
-#include "../../Common/RtViewerView.h"
-#include "../SdlHelpers.h"
-
-#include <EmbeddedResources.h>
-
-// Stone of Orthanc includes
-#include "../../../Sources/Loaders/GenericLoadersContext.h"
-#include "../../../Sources/OpenGL/OpenGLIncludes.h"
-#include "../../../Sources/OpenGL/SdlOpenGLContext.h"
-#include "../../../Sources/StoneException.h"
-#include "../../../Sources/StoneInitialization.h"
-
-// Orthanc (a.o. for screenshot capture)
-#include <Compatibility.h>  // For std::unique_ptr<>
-#include <Images/Image.h>
-#include <Images/ImageProcessing.h>
-#include <Images/PngWriter.h>
-
-
-#include <boost/program_options.hpp>
-#include <boost/shared_ptr.hpp>
-
-// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions
-
-#include <SDL.h>
-
-#include <string>
-
-
-#if !defined(__APPLE__)
-/**
- * OpenGL: "OS X does not seem to support debug output functionality
- * (as gathered online)."
- * https://learnopengl.com/In-Practice/Debugging
- **/
-static void GLAPIENTRY
-OpenGLMessageCallback(GLenum source,
-                      GLenum type,
-                      GLuint id,
-                      GLenum severity,
-                      GLsizei length,
-                      const GLchar* message,
-                      const void* userParam)
-{
-  if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
-  {
-    fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
-            (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
-            type, severity, message);
-  }
-}
-#endif
-
-namespace OrthancStone
-{
-  void RtViewerView::EnableGLDebugOutput()
-  {
-#if !defined(__APPLE__)
-    glEnable(GL_DEBUG_OUTPUT);
-    glDebugMessageCallback(OpenGLMessageCallback, 0);
-#endif
-  }
-
-  boost::shared_ptr<IViewport> RtViewerView::CreateViewport(const std::string& canvasId)
-  {
-    // False means we do NOT let Windows treat this as a legacy application that needs to be scaled
-    return SdlOpenGLViewport::Create(canvasId, 1024, 1024, false);
-  }
-
-  void RtViewerApp::ProcessOptions(int argc, char* argv[])
-  {
-    namespace po = boost::program_options;
-    po::options_description desc("Usage");
-
-    desc.add_options()
-      ("loglevel", po::value<std::string>()->default_value("WARNING"),
-       "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)")
-
-      ("orthanc", po::value<std::string>()->default_value("http://localhost:8042"),
-       "Base URL of the Orthanc instance")
-
-      ("ctseries", po::value<std::string>()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"),
-       "Orthanc ID of the CT series to load. This must be supplied.")
-
-      ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"),
-       "Orthanc ID of the RTDOSE instance to load. This may be an empty string.")
-
-      ("rtstruct", po::value<std::string>()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"),
-       "Orthanc ID of the RTSTRUCT instance to load. This may be an empty string.")
-      ;
-
-    std::cout << desc << std::endl;
-
-    po::variables_map vm;
-    try
-    {
-      po::store(po::parse_command_line(argc, argv, desc), vm);
-      po::notify(vm);
-    }
-    catch (std::exception& e)
-    {
-      std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl;
-    }
-
-    for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it)
-    {
-      std::string key = it->first;
-      const po::variable_value& value = it->second;
-      const std::string& strValue = value.as<std::string>();
-      SetArgument(key, strValue);
-    }
-  }
-
-  void RtViewerApp::RunSdl(int argc, char* argv[])
-  {
-    ProcessOptions(argc, argv);
-
-    /**
-    Create the shared loaders context
-    */
-    loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
-
-    // we are in SDL --> downcast to concrete type
-    boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_);
-
-    /**
-      Url of the Orthanc instance
-      Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In 
-      wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative
-      URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders
-      plugin that serves the main web application from an URL like "http://localhost:8042/stone-rtviewer" (with ".." 
-      leading to the main Orthanc root URL)
-    */
-    std::string orthancUrl = arguments_["orthanc"];
-
-    {
-      Orthanc::WebServiceParameters p;
-      if (HasArgument("orthanc"))
-      {
-        p.SetUrl(orthancUrl);
-      }
-      if (HasArgument("user"))
-      {
-        ORTHANC_ASSERT(HasArgument("password"));
-        p.SetCredentials(GetArgument("user"), GetArgument("password"));
-      } 
-      else
-      {
-        ORTHANC_ASSERT(!HasArgument("password"));
-      }
-      loadersContext->SetOrthancParameters(p);
-    }
-
-    loadersContext->StartOracle();
-
-    CreateLoaders();
-
-    /**
-    Create viewports
-    */
-    CreateView("RtViewer Axial", VolumeProjection_Axial);
-    CreateView("RtViewer Coronal", VolumeProjection_Coronal);
-    CreateView("RtViewer Sagittal", VolumeProjection_Sagittal);
-
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->PrepareViewport();
-      views_[i]->EnableGLDebugOutput();
-    }
-
-    DefaultViewportInteractor interactor;
-
-    /**
-    It is very important that the Oracle (responsible for network I/O) be started before creating and firing the 
-    loaders, for any command scheduled by the loader before the oracle is started will be lost.
-    */
-    StartLoaders();
-
-
-    SdlRunLoop(views_, interactor);
-    loadersContext->StopOracle();
-  }
-
-  void RtViewerView::TakeScreenshot(const std::string& target,
-                                   unsigned int canvasWidth,
-                                   unsigned int canvasHeight)
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-
-    std::string ttf;
-    Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT);
-    
-    CairoCompositor compositor(canvasWidth, canvasHeight);
-    compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1);
-    compositor.Refresh(scene);
-
-    Orthanc::ImageAccessor canvas;
-    compositor.GetCanvas().GetReadOnlyAccessor(canvas);
-
-    Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
-    Orthanc::ImageProcessing::Convert(png, canvas);
-
-    Orthanc::PngWriter writer;
-    writer.WriteToFile(target, png);
-  }
-
-  static boost::shared_ptr<OrthancStone::RtViewerView> GetViewFromWindowId(
-    const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
-    Uint32 windowID)
-  {
-    using namespace OrthancStone;
-    for (size_t i = 0; i < views.size(); ++i)
-    {
-      boost::shared_ptr<OrthancStone::RtViewerView> view = views[i];
-      boost::shared_ptr<IViewport> viewport = view->GetViewport();
-      boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport);
-      Uint32 curWindowID = sdlViewport->GetSdlWindowId();
-      if (windowID == curWindowID)
-        return view;
-    }
-    return boost::shared_ptr<OrthancStone::RtViewerView>();
-  }
-
-  void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
-                               OrthancStone::DefaultViewportInteractor& interactor)
-  {
-    using namespace OrthancStone;
-
-    // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views
-    std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports;
-    for (size_t i = 0; i < views.size(); ++i)
-    {
-      boost::shared_ptr<RtViewerView> view = views[i];
-      boost::shared_ptr<IViewport> viewport = view->GetViewport();
-      boost::shared_ptr<SdlViewport> sdlViewport =
-        boost::dynamic_pointer_cast<SdlViewport>(viewport);
-      viewports.push_back(sdlViewport);
-    }
-
-    {
-      int scancodeCount = 0;
-      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-
-      bool stop = false;
-      while (!stop)
-      {
-        std::vector<SDL_Event> sdlEvents;
-        std::map<Uint32,SDL_Event> userEventsMap;
-        SDL_Event sdlEvent;
-
-        // FIRST: collect all pending events
-        while (SDL_PollEvent(&sdlEvent) != 0)
-        {
-          if ( (sdlEvent.type >= SDL_USEREVENT) && 
-               (sdlEvent.type < SDL_LASTEVENT) )
-          {
-            // we don't want to have multiple refresh events ,
-            // and since every refresh event is a user event with a special type,
-            // we use a map
-            userEventsMap[sdlEvent.type] = sdlEvent;
-          }
-          else
-          {
-            sdlEvents.push_back(sdlEvent);
-          }
-        }
-
-        // SECOND: add all user events to sdlEvents
-        for (std::map<Uint32,SDL_Event>::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it)
-          sdlEvents.push_back(it->second);
-
-        // now process the events
-        for (std::vector<SDL_Event>::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it)
-        {
-          const SDL_Event& sdlEvent = *it;
-
-          if (sdlEvent.type == SDL_QUIT)
-          {
-            stop = true;
-            break;
-          }
-          else if (sdlEvent.type == SDL_WINDOWEVENT &&
-                   (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED ||
-                    sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
-          {
-            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
-              views, sdlEvent.window.windowID);
-
-            boost::shared_ptr<SdlViewport> sdlViewport =
-              boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
-
-            sdlViewport->UpdateSize(sdlEvent.window.data1, sdlEvent.window.data2);
-          }
-          else if (sdlEvent.type == SDL_WINDOWEVENT &&
-                   (sdlEvent.window.event == SDL_WINDOWEVENT_SHOWN ||
-                    sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED))
-          {
-            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
-              views, sdlEvent.window.windowID);
-            boost::shared_ptr<SdlViewport> sdlViewport =
-              boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
-            sdlViewport->Paint();
-          }
-          else if (sdlEvent.type == SDL_KEYDOWN &&
-                   sdlEvent.key.repeat == 0 /* Ignore key bounce */)
-          {
-            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
-              views, sdlEvent.window.windowID);
-
-            switch (sdlEvent.key.keysym.sym)
-            {
-            case SDLK_f:
-            {
-              boost::shared_ptr<SdlViewport> sdlViewport =
-                boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
-              sdlViewport->ToggleMaximize();
-            }
-            break;
-
-            case SDLK_s:
-            {
-              std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
-              lock->GetCompositor().FitContent(lock->GetController().GetScene());
-              lock->Invalidate();
-            }
-            break;
-
-            case SDLK_q:
-              stop = true;
-              break;
-
-            default:
-              break;
-            }
-          }
-          else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
-                   sdlEvent.type == SDL_MOUSEMOTION ||
-                   sdlEvent.type == SDL_MOUSEBUTTONUP)
-          {
-            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
-              views, sdlEvent.window.windowID);
-
-            std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
-            if (lock->HasCompositor())
-            {
-              OrthancStone::PointerEvent p;
-              OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
-                                                   sdlEvent, keyboardState, scancodeCount);
-
-              switch (sdlEvent.type)
-              {
-              case SDL_MOUSEBUTTONDOWN:
-                interactor.SetWindowingLayer(view->GetCtLayerIndex());
-                lock->GetController().HandleMousePress(interactor, p,
-                                                       lock->GetCompositor().GetCanvasWidth(),
-                                                       lock->GetCompositor().GetCanvasHeight());
-                lock->Invalidate();
-                break;
-
-              case SDL_MOUSEMOTION:
-                if (lock->GetController().HandleMouseMove(p))
-                {
-                  lock->Invalidate();
-                }
-                break;
-
-              case SDL_MOUSEBUTTONUP:
-                lock->GetController().HandleMouseRelease(p);
-                lock->Invalidate();
-                break;
-
-              default:
-                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-              }
-            }
-          }
-          else if (sdlEvent.type == SDL_MOUSEWHEEL)
-          {
-            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
-              views, sdlEvent.window.windowID);
-
-            int delta = 0;
-            if (sdlEvent.wheel.y < 0)
-              delta = -1;
-            if (sdlEvent.wheel.y > 0)
-              delta = 1;
-
-            view->Scroll(delta);
-          }
-          else
-          {
-            for (size_t i = 0; i < views.size(); ++i)
-            {
-              boost::shared_ptr<SdlViewport> sdlViewport =
-                boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport());
-              if (sdlViewport->IsRefreshEvent(sdlEvent))
-              {
-                sdlViewport->Paint();
-              }
-            }
-          }
-        }
-        // Small delay to avoid using 100% of CPU
-        SDL_Delay(1);
-      }
-    }
-  }
-}
-
-boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
-
-/**
- * IMPORTANT: The full arguments to "main()" are needed for SDL on
- * Windows. Otherwise, one gets the linking error "undefined reference
- * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
- **/
-int main(int argc, char* argv[])
-{
-  using namespace OrthancStone;
-
-  StoneInitialize();
-
-  try
-  {
-    boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
-    g_app = app;
-    app->RunSdl(argc,argv);
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "EXCEPTION: " << e.What();
-  }
-
-  StoneFinalize();
-
-  return 0;
-}
-
--- a/OrthancStone/Samples/Sdl/SdlHelpers.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-/**
- * Stone of Orthanc
- * 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
-
-#if ORTHANC_ENABLE_SDL != 1
-# error This file cannot be used if ORTHANC_ENABLE_SDL != 1
-#endif
-
-#include "../../Sources/Viewport/SdlViewport.h"
-
-#include <boost/shared_ptr.hpp>
-
-#include <SDL.h>
-
-#include <map>
-#include <string>
-
-namespace OrthancStoneHelpers
-{
-
-  inline OrthancStone::KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState,
-                                                              const int scancodeCount)
-  {
-    using namespace OrthancStone;
-    int result = KeyboardModifiers_None;
-
-    if (keyboardState != NULL)
-    {
-      if (SDL_SCANCODE_LSHIFT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LSHIFT])
-      {
-        result |= KeyboardModifiers_Shift;
-      }
-
-      if (SDL_SCANCODE_RSHIFT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_RSHIFT])
-      {
-        result |= KeyboardModifiers_Shift;
-      }
-
-      if (SDL_SCANCODE_LCTRL < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LCTRL])
-      {
-        result |= KeyboardModifiers_Control;
-      }
-
-      if (SDL_SCANCODE_RCTRL < scancodeCount &&
-          keyboardState[SDL_SCANCODE_RCTRL])
-      {
-        result |= KeyboardModifiers_Control;
-      }
-
-      if (SDL_SCANCODE_LALT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LALT])
-      {
-        result |= KeyboardModifiers_Alt;
-      }
-
-      if (SDL_SCANCODE_RALT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_RALT])
-      {
-        result |= KeyboardModifiers_Alt;
-      }
-    }
-
-    return static_cast<KeyboardModifiers>(result);
-  }
-
-
-  inline void GetPointerEvent(OrthancStone::PointerEvent& p,
-                              const OrthancStone::ICompositor& compositor,
-                              SDL_Event event,
-                              const uint8_t* keyboardState,
-                              const int scancodeCount)
-  {
-    using namespace OrthancStone;
-    KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
-
-    switch (event.button.button)
-    {
-    case SDL_BUTTON_LEFT:
-      p.SetMouseButton(OrthancStone::MouseButton_Left);
-      break;
-
-    case SDL_BUTTON_RIGHT:
-      p.SetMouseButton(OrthancStone::MouseButton_Right);
-      break;
-
-    case SDL_BUTTON_MIDDLE:
-      p.SetMouseButton(OrthancStone::MouseButton_Middle);
-      break;
-
-    default:
-      p.SetMouseButton(OrthancStone::MouseButton_None);
-      break;
-    }
-
-    p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-    p.SetAltModifier( (modifiers & KeyboardModifiers_Alt) != 0);
-    p.SetControlModifier( (modifiers & KeyboardModifiers_Control) != 0);
-    p.SetShiftModifier( (modifiers & KeyboardModifiers_Shift) != 0);
-  }
-
-  
-  inline boost::shared_ptr<OrthancStone::SdlViewport> GetSdlViewportFromWindowId(
-    const std::vector<boost::shared_ptr<OrthancStone::SdlViewport> >& viewports,
-    Uint32 windowID)
-  {
-    using namespace OrthancStone;
-    for (size_t i = 0; i < viewports.size(); ++i)
-    {
-      boost::shared_ptr<IViewport> viewport = viewports[i];
-      boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport);
-      Uint32 curWindowID = sdlViewport->GetSdlWindowId();
-      if (windowID == curWindowID)
-        return sdlViewport;
-    }
-    
-    return boost::shared_ptr<OrthancStone::SdlViewport>();
-  }
-}
-
-
--- a/OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-cmake_minimum_required(VERSION 2.8.10)
-
-project(SdlSimpleViewer)
-
-include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
-
-if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  set(ORTHANC_BOOST_COMPONENTS program_options)
-
-  set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
-  set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
-  mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
-  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake)
-  include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake)
-
-else()
-  set(ENABLE_GOOGLE_TEST ON)
-  set(ENABLE_LOCALE ON)  # Necessary for text rendering
-  set(ENABLE_OPENGL ON)  #  <==
-  set(ENABLE_WEB_CLIENT ON)
-endif()
-
-set(ENABLE_DCMTK ON)  # <==
-set(ENABLE_SDL ON)
-
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/../Utilities.cmake)
-
-if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  # This include must be after "OrthancStoneConfiguration.cmake" to
-  # have "BOOST_SOURCES_DIR" defined
-  include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake)
-endif()
-
-SortFilesInSourceGroups()
-
-add_executable(SdlSimpleViewer
-  ../SdlHelpers.h
-  ../../Common/SampleHelpers.h
-  SdlSimpleViewerApplication.h
-  SdlSimpleViewer.cpp
-  ${ORTHANC_STONE_SOURCES}
-  )
-
-target_link_libraries(SdlSimpleViewer ${DCMTK_LIBRARIES})
-
--- a/OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeSettings.json	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-{
-  "configurations": [
-    {
-      "name": "x64-Debug",
-      "generator": "Ninja",
-      "configurationType": "Debug",
-      "inheritEnvironments": [ "msvc_x64_x64" ],
-      "buildRoot": "${projectDir}\\out\\build\\${name}",
-      "installRoot": "${projectDir}\\out\\install\\${name}",
-      "cmakeCommandArgs": "",
-      "buildCommandArgs": "-v",
-      "ctestCommandArgs": "",
-      "variables": [
-        {
-          "name": "MSVC_MULTIPLE_PROCESSES",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "ALLOW_DOWNLOADS",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "STATIC_BUILD",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "OPENSSL_NO_CAPIENG",
-          "value": "True",
-          "type": "BOOL"
-        },
-      ]
-    }
-  ]
-}
\ No newline at end of file
--- a/OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "SdlSimpleViewerApplication.h"
-#include "../SdlHelpers.h"
-#include "../../Common/SampleHelpers.h"
-
-#include "../../../Sources/Loaders/GenericLoadersContext.h"
-#include "../../../Sources/StoneException.h"
-#include "../../../Sources/StoneEnumerations.h"
-#include "../../../Sources/StoneInitialization.h"
-#include "../../../Sources/Viewport/SdlViewport.h"
-
-#include <Compatibility.h>  // For std::unique_ptr<>
-#include <OrthancException.h>
-
-#include <boost/program_options.hpp>
-#include <SDL.h>
-
-#include <string>
-
-
-std::string orthancUrl;
-std::string instanceId;
-int frameIndex = 0;
-
-
-static void ProcessOptions(int argc, char* argv[])
-{
-  namespace po = boost::program_options;
-  po::options_description desc("Usage");
-
-  desc.add_options()
-    ("loglevel", po::value<std::string>()->default_value("WARNING"),
-     "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)")
-
-    ("orthanc", po::value<std::string>()->default_value("http://localhost:8042"),
-     "Base URL of the Orthanc instance")
-
-    ("instance", po::value<std::string>()->default_value("285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"),
-     "Orthanc ID of the instance to display")
-
-    ("frame_index", po::value<int>()->default_value(0),
-     "The zero-based index of the frame (for multi-frame instances)")
-    ;
-
-  std::cout << desc << std::endl;
-
-  po::variables_map vm;
-  try
-  {
-    po::store(po::parse_command_line(argc, argv, desc), vm);
-    po::notify(vm);
-  }
-  catch (std::exception& e)
-  {
-    std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl;
-  }
-
-  if (vm.count("loglevel") > 0)
-  {
-    std::string logLevel = vm["loglevel"].as<std::string>();
-    OrthancStoneHelpers::SetLogLevel(logLevel);
-  }
-
-  if (vm.count("orthanc") > 0)
-  {
-    // maybe check URL validity here
-    orthancUrl = vm["orthanc"].as<std::string>();
-  }
-
-  if (vm.count("instance") > 0)
-  {
-    instanceId = vm["instance"].as<std::string>();
-  }
-
-  if (vm.count("frame_index") > 0)
-  {
-    frameIndex = vm["frame_index"].as<int>();
-  }
-
-}
-
-/**
- * IMPORTANT: The full arguments to "main()" are needed for SDL on
- * Windows. Otherwise, one gets the linking error "undefined reference
- * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
- **/
-int main(int argc, char* argv[])
-{
-  try
-  {
-    OrthancStone::StoneInitialize();
-
-    ProcessOptions(argc, argv);
-
-    //Orthanc::Logging::EnableInfoLevel(true);
-    //Orthanc::Logging::EnableTraceLevel(true);
-
-    {
-
-#if 1
-      boost::shared_ptr<OrthancStone::SdlViewport> viewport =
-        OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600);
-#else
-      boost::shared_ptr<OrthancStone::SdlViewport> viewport =
-        OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600);
-#endif
-
-      OrthancStone::GenericLoadersContext context(1, 4, 1);
-
-      Orthanc::WebServiceParameters orthancWebService;
-      orthancWebService.SetUrl(orthancUrl);
-      context.SetOrthancParameters(orthancWebService);
-
-      context.StartOracle();
-
-      {
-
-        boost::shared_ptr<SdlSimpleViewerApplication> application(
-          SdlSimpleViewerApplication::Create(context, viewport));
-
-        OrthancStone::DicomSource source;
-
-        application->LoadOrthancFrame(source, instanceId, frameIndex);
-
-        OrthancStone::DefaultViewportInteractor interactor;
-        interactor.SetWindowingLayer(0);
-
-        {
-          int scancodeCount = 0;
-          const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-
-          bool stop = false;
-          while (!stop)
-          {
-            bool paint = false;
-            SDL_Event event;
-            while (SDL_PollEvent(&event))
-            {
-              if (event.type == SDL_QUIT)
-              {
-                stop = true;
-                break;
-              }
-              else if (viewport->IsRefreshEvent(event))
-              {
-                paint = true;
-              }
-              else if (event.type == SDL_WINDOWEVENT &&
-                       (event.window.event == SDL_WINDOWEVENT_RESIZED ||
-                        event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
-              {
-                viewport->UpdateSize(event.window.data1, event.window.data2);
-              }
-              else if (event.type == SDL_WINDOWEVENT &&
-                       (event.window.event == SDL_WINDOWEVENT_SHOWN ||
-                        event.window.event == SDL_WINDOWEVENT_EXPOSED))
-              {
-                paint = true;
-              }
-              else if (event.type == SDL_KEYDOWN &&
-                       event.key.repeat == 0 /* Ignore key bounce */)
-              {
-                switch (event.key.keysym.sym)
-                {
-                case SDLK_f:
-                  viewport->ToggleMaximize();
-                  break;
-
-                case SDLK_s:
-                  application->FitContent();
-                  break;
-
-                case SDLK_q:
-                  stop = true;
-                  break;
-
-                default:
-                  break;
-                }
-              }
-              else if (event.type == SDL_MOUSEBUTTONDOWN ||
-                       event.type == SDL_MOUSEMOTION ||
-                       event.type == SDL_MOUSEBUTTONUP)
-              {
-                std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport->Lock());
-                if (lock->HasCompositor())
-                {
-                  OrthancStone::PointerEvent p;
-                  OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
-                                                       event, keyboardState, scancodeCount);
-
-                  switch (event.type)
-                  {
-                  case SDL_MOUSEBUTTONDOWN:
-                    lock->GetController().HandleMousePress(interactor, p,
-                                                           lock->GetCompositor().GetCanvasWidth(),
-                                                           lock->GetCompositor().GetCanvasHeight());
-                    lock->Invalidate();
-                    break;
-
-                  case SDL_MOUSEMOTION:
-                    if (lock->GetController().HandleMouseMove(p))
-                    {
-                      lock->Invalidate();
-                    }
-                    break;
-
-                  case SDL_MOUSEBUTTONUP:
-                    lock->GetController().HandleMouseRelease(p);
-                    lock->Invalidate();
-                    break;
-
-                  default:
-                    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-                  }
-                }
-              }
-            }
-
-            if (paint)
-            {
-              viewport->Paint();
-            }
-
-            // Small delay to avoid using 100% of CPU
-            SDL_Delay(1);
-          }
-        }
-        context.StopOracle();
-      }
-    }
-
-    OrthancStone::StoneFinalize();
-    return 0;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "OrthancException: " << e.What();
-    return -1;
-  }
-  catch (OrthancStone::StoneException& e)
-  {
-    LOG(ERROR) << "StoneException: " << e.What();
-    return -1;
-  }
-  catch (std::runtime_error& e)
-  {
-    LOG(ERROR) << "Runtime error: " << e.what();
-    return -1;
-  }
-  catch (...)
-  {
-    LOG(ERROR) << "Native exception";
-    return -1;
-  }
-}
--- a/OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../../Sources/Viewport/IViewport.h"
-#include "../../../Sources/Loaders/DicomResourcesLoader.h"
-#include "../../../Sources/Loaders/ILoadersContext.h"
-#include "../../../Sources/Loaders/SeriesFramesLoader.h"
-#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h"
-
-#include <Compatibility.h>  // For std::unique_ptr<>
-
-#include <boost/make_shared.hpp>
-
-
-using OrthancStone::ILoadersContext;
-using OrthancStone::ObserverBase;
-using OrthancStone::IViewport;
-using OrthancStone::DicomResourcesLoader;
-using OrthancStone::SeriesFramesLoader;
-using OrthancStone::TextureBaseSceneLayer;
-using OrthancStone::DicomSource;
-using OrthancStone::SeriesThumbnailsLoader;
-using OrthancStone::LoadedDicomResources;
-using OrthancStone::SeriesThumbnailType;
-using OrthancStone::OracleScheduler;
-using OrthancStone::OrthancRestApiCommand;
-using OrthancStone::OracleScheduler;
-using OrthancStone::OracleScheduler;
-using OrthancStone::OracleScheduler;
-
-
-class SdlSimpleViewerApplication : public ObserverBase<SdlSimpleViewerApplication>
-{
-
-public:
-  static boost::shared_ptr<SdlSimpleViewerApplication> Create(ILoadersContext& context, boost::shared_ptr<IViewport> viewport)
-  {
-    boost::shared_ptr<SdlSimpleViewerApplication> application(new SdlSimpleViewerApplication(context, viewport));
-
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
-      application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
-    }
-
-    application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle);
-
-    return application;
-  }
-
-  void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame)
-  {
-    std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-
-    dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
-                                              0, source, Orthanc::ResourceType_Instance, instanceId,
-                                              new Orthanc::SingleValueObject<unsigned int>(frame));
-  }
-
-#if 0
-  void LoadDicomWebFrame(const DicomSource& source,
-                         const std::string& studyInstanceUid,
-                         const std::string& seriesInstanceUid,
-                         const std::string& sopInstanceUid,
-                         unsigned int frame)
-  {
-    std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-
-    // We first must load the "/metadata" to know the number of frames
-    dicomLoader_->ScheduleGetDicomWeb(
-      boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source,
-      "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata",
-      new Orthanc::SingleValueObject<unsigned int>(frame));
-  }
-#endif 
-
-  void FitContent()
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    lock->GetCompositor().FitContent(lock->GetController().GetScene());
-    lock->Invalidate();
-  }
-
-private:
-  ILoadersContext& context_;
-  boost::shared_ptr<IViewport>             viewport_;
-  boost::shared_ptr<DicomResourcesLoader>  dicomLoader_;
-  boost::shared_ptr<SeriesFramesLoader>    framesLoader_;
-
-  SdlSimpleViewerApplication(ILoadersContext& context,
-                             boost::shared_ptr<IViewport> viewport) :
-    context_(context),
-    viewport_(viewport)
-  {
-  }
-
-  void Handle(const SeriesFramesLoader::FrameLoadedMessage& message)
-  {
-    LOG(INFO) << "Frame decoded! "
-      << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight()
-      << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat());
-
-    std::unique_ptr<TextureBaseSceneLayer> layer(
-      message.GetInstanceParameters().CreateTexture(message.GetImage()));
-    layer->SetLinearInterpolation(true);
-
-    {
-      std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-      lock->GetController().GetScene().SetLayer(0, layer.release());
-      lock->GetCompositor().FitContent(lock->GetController().GetScene());
-      lock->Invalidate();
-    }
-  }
-
-  void Handle(const DicomResourcesLoader::SuccessMessage& message)
-  {
-    if (message.GetResources()->GetSize() != 1)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    //message.GetResources()->GetResource(0).Print(stdout);
-
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-      SeriesFramesLoader::Factory f(*message.GetResources());
-
-      framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(
-        f.Create(*lock));
-      
-      Register<SeriesFramesLoader::FrameLoadedMessage>(
-        *framesLoader_, &SdlSimpleViewerApplication::Handle);
-
-      assert(message.HasUserPayload());
-
-      const Orthanc::SingleValueObject<unsigned int>& payload =
-        dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(
-          message.GetUserPayload());
-
-      LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue();
-      framesLoader_->ScheduleLoadFrame(
-        0, message.GetDicomSource(), payload.GetValue(),
-        message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */,
-        NULL);
-    }
-  }
-
-};
-
--- a/OrthancStone/Samples/Sdl/Utilities.cmake	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-
-
-macro(GetFilenameFromPath TargetVariable Path)
-#message(STATUS "GetFilenameFromPath (1): Path = ${Path} TargetVariable = ${${TargetVariable}}")
-string(REPLACE "\\" "/" PathWithFwdSlashes "${Path}")
-string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${PathWithFwdSlashes}")
-#message(STATUS "GetFilenameFromPath (2): Path = ${Path} Path = ${PathWithFwdSlashes} TargetVariable = ${TargetVariable}")
-endmacro()
-
-macro(GetFilePathWithoutLastExtension TargetVariable FilePath)
-string(REGEX REPLACE "(^.*)\\.([^\\.]+)" "\\1" ${TargetVariable} "${FilePath}")
-#message(STATUS "GetFileNameWithoutLastExtension: FilePath = ${FilePath} TargetVariable = ${${TargetVariable}}")
-endmacro()
-
-macro(Test_GetFilePathWithoutLastExtension)
-set(tmp "/prout/zi/goui.goui.cpp")
-GetFilePathWithoutLastExtension(TargetVariable "${tmp}")
-if(NOT ("${TargetVariable}" STREQUAL "/prout/zi/goui.goui"))
-  message(FATAL_ERROR "Test_GetFilePathWithoutLastExtension failed (1)")
-else()
-  #message(STATUS "Test_GetFilePathWithoutLastExtension: <<OK>>")
-endif()
-endmacro()
-
-Test_GetFilePathWithoutLastExtension()
-
-macro(Test_GetFilenameFromPath)
-set(tmp "/prout/../../dada/zi/goui.goui.cpp")
-GetFilenameFromPath(TargetVariable "${tmp}")
-if(NOT ("${TargetVariable}" STREQUAL "goui.goui.cpp"))
-  message(FATAL_ERROR "Test_GetFilenameFromPath failed")
-else()
-  #message(STATUS "Test_GetFilenameFromPath: <<OK>>")
-endif()
-endmacro()
-
-Test_GetFilenameFromPath()
-
-macro(SortFilesInSourceGroups)
-  if(FALSE)
-    foreach(source IN LISTS ORTHANC_STONE_SOURCES)
-        # if("${source}" MATCHES ".*/pixman.*\\.c")
-        #   message("pixman source: ${source}")
-        # elseif("${source}" MATCHES ".*/pixman.*\\.c")
-        #   message("pixman header: ${source}")
-        # endif()
-        
-        if("${source}" MATCHES ".*\\.\\./.*")
-          message("source raw: ${source}")
-          #file(TO_CMAKE_PATH ${source} sourceCMakePath)
-          get_filename_component(sourceCMakePath ${source} ABSOLUTE)
-          message("source CMake: ${sourceCMakePath}")
-        endif()
-
-        # returns the containing directory with forward slashes
-        # get_filename_component(source_path "${source}" PATH) 
-
-        # converts / to \
-        # string(REPLACE "/" "\\" source_path_msvc "${source_path}")
-        #source_group("Stone ${source_path_msvc}" FILES "${source}")
-    endforeach()
-  endif()
-
-  source_group("Orthanc Framework\\Sources" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*cpp")
-  source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*hpp")
-  source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*h")
-
-  source_group("Stone Library\\Sources" REGULAR_EXPRESSION ".*/orthanc-stone/.*cpp")
-  source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*hpp")
-  source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*h")
-
-  source_group("Stone Samples\\Source" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.cpp")
-  source_group("Stone Samples\\Headers" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.h")
-
-  source_group("ThirdParty\\cairo" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/cairo[^/]*/.*")
-  source_group("ThirdParty\\pixman" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/pixman[^/]*/.*")
-  source_group("ThirdParty\\base64" REGULAR_EXPRESSION ".*ThirdParty/base64.*")
-  source_group("ThirdParty\\SDL2" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/SDL2.*")
-  source_group("ThirdParty\\glew" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/glew.*")
-  source_group("AUTOGENERATED" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/AUTOGENERATED/.*")
-  source_group("ThirdParty\\minizip" REGULAR_EXPRESSION ".*ThirdParty/minizip/.*")
-  source_group("ThirdParty\\md5" REGULAR_EXPRESSION ".*ThirdParty/md5/.*")
-endmacro()
-
--- a/OrthancStone/Samples/WebAssembly/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-cmake_minimum_required(VERSION 2.8.3)
-
-project(OrthancStone)
-
-# Configuration of the Emscripten compiler for WebAssembly target
-# ---------------------------------------------------------------
-set(USE_WASM ON CACHE BOOL "")
-
-set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
-
-set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
-endif()
-
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
-
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
-add_definitions(
-  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
-)
-
-# Stone of Orthanc configuration
-# ---------------------------------------------------------------
-set(ALLOW_DOWNLOADS ON)
-
-include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake)
-
-SET(ENABLE_DCMTK OFF)  # Not necessary
-SET(ENABLE_GOOGLE_TEST OFF)
-SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
-SET(ORTHANC_SANDBOXED ON)
-
-# this will set up the build system for Stone of Orthanc and will
-# populate the ORTHANC_STONE_SOURCES CMake variable
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-
-
-# We embed a font to be used for on-screen overlays
-# ---------------------------------------------------------------
-
-DownloadPackage(
-  "a24b8136b8f3bb93f166baf97d9328de"
-  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
-  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
-
-EmbedResources(
-  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut  
-  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
-  )
-
-add_library(OrthancStone STATIC
-  ${ORTHANC_STONE_SOURCES}
-  ${AUTOGENERATED_SOURCES}
-  )
-
-################################################################################
-
-# Define the WASM module
-# ---------------------------------------------------------------
-
-project(RtViewerWasm)
-
-add_executable(RtViewerWasm
-  RtViewer/RtViewerWasm.cpp
-  ../Common/RtViewerApp.cpp
-  ../Common/RtViewerApp.h
-  ../Common/RtViewerView.cpp
-  ../Common/RtViewerView.h
-  )
-
-target_link_libraries(RtViewerWasm OrthancStone)
-
-# Declare installation files for the module
-# ---------------------------------------------------------------
-install(
-  TARGETS RtViewerWasm
-  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/
-  )
-
-# Declare installation files for the companion files (web scaffolding)
-# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
-# (the generated JS loader for the WASM module) is handled by the `install1`
-# section above: it is considered to be the binary output of 
-# the linker.
-# ---------------------------------------------------------------
-install(
-  FILES
-  ${CMAKE_SOURCE_DIR}/RtViewer/RtViewerWasmApp.js
-  ${CMAKE_SOURCE_DIR}/RtViewer/index.html
-  ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm
-  DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/
-  )
-
-################################################################################
-
-# Define the WASM module
-# ---------------------------------------------------------------
-
-project(SingleFrameViewerWasm)
-
-add_executable(SingleFrameViewerWasm
-  SingleFrameViewer/SingleFrameViewer.cpp
-  )
-
-target_link_libraries(SingleFrameViewerWasm OrthancStone)
-
-# Declare installation files for the module
-# ---------------------------------------------------------------
-install(
-  TARGETS SingleFrameViewerWasm
-  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/
-  )
-
-# Declare installation files for the companion files (web scaffolding)
-# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
-# (the generated JS loader for the WASM module) is handled by the `install1`
-# section above: it is considered to be the binary output of 
-# the linker.
-# ---------------------------------------------------------------
-install(
-  FILES
-  ${CMAKE_SOURCE_DIR}/SingleFrameViewer/SingleFrameViewerApp.js
-  ${CMAKE_SOURCE_DIR}/SingleFrameViewer/index.html
-  ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm
-  DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/
-  )
--- a/OrthancStone/Samples/WebAssembly/NOTES.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-
-Building WebAssembly samples using Docker
-=========================================
-
-The script "./docker-build.sh" can be used to quickly build the
-WebAssembly samples on any GNU/Linux distribution equipped with
-Docker. This avoids newcomers to install Emscripten and learn the
-CMake options. Just type:
-
-$ ./docker-build.sh Release
-
-After successful build, the binaries will be installed in the
-following folder (i.e. in the folder "wasm-binaries" at the root of
-the source distribution):
-
-$ ls -l ../../wasm-binaries
-
-
-NB: The source code of the Docker build environment can be found at
-the following location:
-https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder
-
--- a/OrthancStone/Samples/WebAssembly/RtViewer/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-cmake_minimum_required(VERSION 2.8.3)
-
-project(RtViewerWasm)
-
-# Configuration of the Emscripten compiler for WebAssembly target
-# ---------------------------------------------------------------
-set(USE_WASM ON CACHE BOOL "")
-
-set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
-
-set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
-endif()
-
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
-
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
-add_definitions(
-  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
-)
-
-# Stone of Orthanc configuration
-# ---------------------------------------------------------------
-set(ALLOW_DOWNLOADS ON)
-
-include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
-
-SET(ENABLE_DCMTK OFF)  # Not necessary
-SET(ENABLE_GOOGLE_TEST OFF)
-SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
-SET(ORTHANC_SANDBOXED ON)
-
-# this will set up the build system for Stone of Orthanc and will
-# populate the ORTHANC_STONE_SOURCES CMake variable
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-
-
-# We embed a font to be used for on-screen overlays
-# ---------------------------------------------------------------
-
-DownloadPackage(
-  "a24b8136b8f3bb93f166baf97d9328de"
-  "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip"
-  "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
-
-EmbedResources(
-  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut  
-  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
-  )
-
-# Define the WASM module
-# ---------------------------------------------------------------
-add_executable(RtViewerWasm
-  RtViewerWasm.cpp
-  ../../Common/RtViewerApp.cpp
-  ../../Common/RtViewerApp.h
-  ../../Common/RtViewerView.cpp
-  ../../Common/RtViewerView.h
-  ${ORTHANC_STONE_SOURCES}
-  ${AUTOGENERATED_SOURCES}
-  )
-
-# Declare installation files for the module
-# ---------------------------------------------------------------
-install(
-  TARGETS RtViewerWasm
-  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
-  )
-
-# Declare installation files for the companion files (web scaffolding)
-# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
-# (the generated JS loader for the WASM module) is handled by the `install1`
-# section above: it is considered to be the binary output of 
-# the linker.
-# ---------------------------------------------------------------
-install(
-  FILES
-  ${CMAKE_SOURCE_DIR}/RtViewerWasmApp.js
-  ${CMAKE_SOURCE_DIR}/index.html
-  ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm
-  DESTINATION ${CMAKE_INSTALL_PREFIX}
-  )
--- a/OrthancStone/Samples/WebAssembly/RtViewer/OBSOLETE.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,559 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
-#include "../../../Framework/Oracle/SleepOracleCommand.h"
-#include "../../../Framework/Oracle/WebAssemblyOracle.h"
-#include "../../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
-#include "../../../Framework/Scene2D/OpenGLCompositor.h"
-#include "../../../Framework/Scene2D/PanSceneTracker.h"
-#include "../../../Framework/Scene2D/RotateSceneTracker.h"
-#include "../../../Framework/Scene2D/ZoomSceneTracker.h"
-#include "../../../Framework/Scene2DViewport/UndoStack.h"
-#include "../../../Framework/Scene2DViewport/ViewportController.h"
-#include "../../../Framework/StoneInitialization.h"
-#include "../../../Framework/Viewport/WebAssemblyViewport.h"
-#include "../../../Framework/Volumes/VolumeSceneLayerSource.h"
-
-#include <OrthancException.h>
-
-#include <emscripten/html5.h>
-#include <emscripten.h>
-
-#include <boost/make_shared.hpp>
-
-
-class ViewportManager;
-
-static const unsigned int FONT_SIZE = 32;
-
-boost::shared_ptr<OrthancStone::DicomVolumeImage>                     ct_(new OrthancStone::DicomVolumeImage);
-boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_;
-std::unique_ptr<ViewportManager>                        widget1_;
-std::unique_ptr<ViewportManager>                        widget2_;
-std::unique_ptr<ViewportManager>                        widget3_;
-//OrthancStone::MessageBroker                                         broker_;
-//OrthancStone::WebAssemblyOracle                                     oracle_(broker_);
-std::unique_ptr<OrthancStone::IFlexiblePointerTracker>                tracker_;
-static std::map<std::string, std::string>                             arguments_;
-static bool                                                           ctrlDown_ = false;
-
-
-#if 0
-
-// use the one from WebAssemblyViewport
-static OrthancStone::PointerEvent* ConvertMouseEvent(
-  const EmscriptenMouseEvent&        source,
-  OrthancStone::IViewport& viewport)
-{
-
-  std::unique_ptr<OrthancStone::PointerEvent> target(
-    new OrthancStone::PointerEvent);
-
-  target->AddPosition(viewport.GetPixelCenterCoordinates(
-                        source.targetX, source.targetY));
-  target->SetAltModifier(source.altKey);
-  target->SetControlModifier(source.ctrlKey);
-  target->SetShiftModifier(source.shiftKey);
-
-  return target.release();
-}
-#endif
-
-
-EM_BOOL OnMouseEvent(int eventType, 
-                     const EmscriptenMouseEvent *mouseEvent, 
-                     void *userData)
-{
-  if (mouseEvent != NULL &&
-      userData != NULL)
-  {
-    boost::shared_ptr<OrthancStone::WebGLViewport>& viewport = 
-      *reinterpret_cast<boost::shared_ptr<OrthancStone::WebGLViewport>*>(userData);
-
-    std::unique_ptr<OrthancStone::IViewport::ILock> lock = (*viewport)->Lock();
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-    
-    switch (eventType)
-    {
-      case EMSCRIPTEN_EVENT_CLICK:
-      {
-        static unsigned int count = 0;
-        char buf[64];
-        sprintf(buf, "click %d", count++);
-
-        std::unique_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
-        layer->SetText(buf);
-        scene.SetLayer(100, layer.release());
-        lock->Invalidate();
-        break;
-      }
-
-      case EMSCRIPTEN_EVENT_MOUSEDOWN:
-      {
-        boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t;
-
-        {
-          std::unique_ptr<OrthancStone::PointerEvent> event(
-            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
-
-          switch (mouseEvent->button)
-          {
-            case 0:  // Left button
-              emscripten_console_log("Creating RotateSceneTracker");
-              t.reset(new OrthancStone::RotateSceneTracker(
-                        viewport, *event));
-              break;
-
-            case 1:  // Middle button
-              emscripten_console_log("Creating PanSceneTracker");
-              LOG(INFO) << "Creating PanSceneTracker" ;
-              t.reset(new OrthancStone::PanSceneTracker(
-                        viewport, *event));
-              break;
-
-            case 2:  // Right button
-              emscripten_console_log("Creating ZoomSceneTracker");
-              t.reset(new OrthancStone::ZoomSceneTracker(
-                        viewport, *event, controller->GetViewport().GetCanvasWidth()));
-              break;
-
-            default:
-              break;
-          }
-        }
-
-        if (t.get() != NULL)
-        {
-          tracker_.reset(
-            new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier()));
-          controller->GetViewport().Refresh();
-        }
-
-        break;
-      }
-
-      case EMSCRIPTEN_EVENT_MOUSEMOVE:
-        if (tracker_.get() != NULL)
-        {
-          std::unique_ptr<OrthancStone::PointerEvent> event(
-            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
-          tracker_->PointerMove(*event);
-          controller->GetViewport().Refresh();
-        }
-        break;
-
-      case EMSCRIPTEN_EVENT_MOUSEUP:
-        if (tracker_.get() != NULL)
-        {
-          std::unique_ptr<OrthancStone::PointerEvent> event(
-            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
-          tracker_->PointerUp(*event);
-          controller->GetViewport().Refresh();
-          if (!tracker_->IsAlive())
-            tracker_.reset();
-        }
-        break;
-
-      default:
-        break;
-    }
-  }
-
-  return true;
-}
-
-
-void SetupEvents(const std::string& canvas,
-                 boost::shared_ptr<OrthancStone::WebGLViewport>& viewport)
-{
-  emscripten_set_mousedown_callback(canvas.c_str(), &viewport, false, OnMouseEvent);
-  emscripten_set_mousemove_callback(canvas.c_str(), &viewport, false, OnMouseEvent);
-  emscripten_set_mouseup_callback(canvas.c_str(), &viewport, false, OnMouseEvent);
-}
-
-  class ViewportManager : public OrthanStone::ObserverBase<ViewportManager>
-  {
-  private:
-    OrthancStone::WebAssemblyViewport         viewport_;
-    std::unique_ptr<VolumeSceneLayerSource>   source_;
-    VolumeProjection                          projection_;
-    std::vector<CoordinateSystem3D>           planes_;
-    size_t                                    currentPlane_;
-
-    void Handle(const DicomVolumeImage::GeometryReadyMessage& message)
-    {
-      LOG(INFO) << "Geometry is available";
-
-      const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry();
-
-      const unsigned int depth = geometry.GetProjectionDepth(projection_);
-
-      // select an initial cutting plane halfway through the volume
-      currentPlane_ = depth / 2;
-
-      planes_.resize(depth);
-
-      for (unsigned int z = 0; z < depth; z++)
-      {
-        planes_[z] = geometry.GetProjectionSlice(projection_, z);
-      }
-
-      Refresh();
-
-      viewport_.FitContent();
-    }
-    
-  public:
-    ViewportManager(const std::string& canvas,
-                    VolumeProjection projection) :
-      projection_(projection),
-      currentPlane_(0)
-    {
-      viewport_ = OrthancStone::WebGLViewport::Create(canvas);
-    }
-
-    void UpdateSize()
-    {
-      viewport_.UpdateSize();
-    }
-
-    void SetSlicer(int layerDepth,
-                   const boost::shared_ptr<IVolumeSlicer>& slicer,
-                   IObservable& loader,
-                   ILayerStyleConfigurator* configurator)
-    {
-      if (source_.get() != NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
-                                        "Only one slicer can be registered");
-      }
-      
-      loader.RegisterObserverCallback(
-        new Callable<ViewportManager, DicomVolumeImage::GeometryReadyMessage>
-        (*this, &ViewportManager::Handle));
-
-      source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer));
-
-      if (configurator != NULL)
-      {
-        source_->SetConfigurator(configurator);
-      }
-    }    
-
-    void Refresh()
-    {
-      if (source_.get() != NULL &&
-          currentPlane_ < planes_.size())
-      {
-        source_->Update(planes_[currentPlane_]);
-        viewport_.Refresh();
-      }
-    }
-
-    size_t GetSlicesCount() const
-    {
-      return planes_.size();
-    }
-
-    void Scroll(int delta)
-    {
-      if (!planes_.empty())
-      {
-        int tmp = static_cast<int>(currentPlane_) + delta;
-        unsigned int next;
-
-        if (tmp < 0)
-        {
-          next = 0;
-        }
-        else if (tmp >= static_cast<int>(planes_.size()))
-        {
-          next = planes_.size() - 1;
-        }
-        else
-        {
-          next = static_cast<size_t>(tmp);
-        }
-
-        if (next != currentPlane_)
-        {
-          currentPlane_ = next;
-          Refresh();
-        }
-      }
-    }
-  };
-}
-
-
-EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
-{
-  try
-  {
-    if (widget1_.get() != NULL)
-    {
-      widget1_->UpdateSize();
-    }
-  
-    if (widget2_.get() != NULL)
-    {
-      widget2_->UpdateSize();
-    }
-  
-    if (widget3_.get() != NULL)
-    {
-      widget3_->UpdateSize();
-    }
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "Exception while updating canvas size: " << e.What();
-  }
-  
-  return true;
-}
-
-EM_BOOL OnAnimationFrame(double time, void *userData)
-{
-  try
-  {
-    if (widget1_.get() != NULL)
-    {
-      widget1_->Refresh();
-    }
-  
-    if (widget2_.get() != NULL)
-    {
-      widget2_->Refresh();
-    }
-  
-    if (widget3_.get() != NULL)
-    {
-      widget3_->Refresh();
-    }
-
-    return true;
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What();
-    return false;
-  }  
-}
-
-EM_BOOL OnMouseWheel(int eventType,
-                     const EmscriptenWheelEvent *wheelEvent,
-                     void *userData)
-{
-  try
-  {
-    if (userData != NULL)
-    {
-      int delta = 0;
-
-      if (wheelEvent->deltaY < 0)
-      {
-        delta = -1;
-      }
-           
-      if (wheelEvent->deltaY > 0)
-      {
-        delta = 1;
-      }
-
-      OrthancStone::ViewportManager& widget =
-        *reinterpret_cast<OrthancStone::ViewportManager*>(userData);
-      
-      if (ctrlDown_)
-      {
-        delta *= static_cast<int>(widget.GetSlicesCount() / 10);
-      }
-
-      widget.Scroll(delta);
-    }
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "Exception in the wheel event: " << e.What();
-  }
-  
-  return true;
-}
-
-
-EM_BOOL OnKeyDown(int eventType,
-                  const EmscriptenKeyboardEvent *keyEvent,
-                  void *userData)
-{
-  ctrlDown_ = keyEvent->ctrlKey;
-  return false;
-}
-
-
-EM_BOOL OnKeyUp(int eventType,
-                const EmscriptenKeyboardEvent *keyEvent,
-                void *userData)
-{
-  ctrlDown_ = false;
-  return false;
-}
-
-
-
-#if 0
-namespace OrthancStone
-{
-  class TestSleep : public IObserver
-  {
-  private:
-    WebAssemblyOracle&  oracle_;
-
-    void Schedule()
-    {
-      oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000));
-    }
-    
-    void Handle(const SleepOracleCommand::TimeoutMessage& message)
-    {
-      LOG(INFO) << "TIMEOUT";
-      Schedule();
-    }
-    
-  public:
-    TestSleep(MessageBroker& broker,
-              WebAssemblyOracle& oracle) :
-      IObserver(broker),
-      oracle_(oracle)
-    {
-      oracle.RegisterObserverCallback(
-        new Callable<TestSleep, SleepOracleCommand::TimeoutMessage>
-        (*this, &TestSleep::Handle));
-
-      LOG(INFO) << "STARTING";
-      Schedule();
-    }
-  };
-
-  //static TestSleep testSleep(broker_, oracle_);
-}
-#endif
-
-static bool GetArgument(std::string& value,
-                        const std::string& key)
-{
-  std::map<std::string, std::string>::const_iterator found = arguments_.find(key);
-
-  if (found == arguments_.end())
-  {
-    return false;
-  }
-  else
-  {
-    value = found->second;
-    return true;
-  }
-}
-
-
-extern "C"
-{
-  int main(int argc, char const *argv[]) 
-  {
-    OrthancStone::StoneInitialize();
-    Orthanc::Logging::EnableInfoLevel(true);
-    // Orthanc::Logging::EnableTraceLevel(true);
-    EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded")););
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetArgument(const char* key, const char* value)
-  {
-    // This is called for each GET argument (cf. "app.js")
-    LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]";
-    arguments_[key] = value;
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  void Initialize()
-  {
-    try
-    {
-      oracle_.SetOrthancRoot("..");
-      
-      loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_));
-    
-      widget1_.reset(new OrthancStone::ViewportManager("mycanvas1", OrthancStone::VolumeProjection_Axial));
-      {
-        std::unique_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
-        style->SetLinearInterpolation(true);
-        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
-        widget1_->SetSlicer(0, loader_, *loader_, style.release());
-      }
-      widget1_->UpdateSize();
-
-      widget2_.reset(new OrthancStone::ViewportManager("mycanvas2", OrthancStone::VolumeProjection_Coronal));
-      {
-        std::unique_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
-        style->SetLinearInterpolation(true);
-        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
-        widget2_->SetSlicer(0, loader_, *loader_, style.release());
-      }
-      widget2_->UpdateSize();
-
-      widget3_.reset(new OrthancStone::ViewportManager("mycanvas3", OrthancStone::VolumeProjection_Sagittal));
-      {
-        std::unique_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
-        style->SetLinearInterpolation(true);
-        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
-        widget3_->SetSlicer(0, loader_, *loader_, style.release());
-      }
-      widget3_->UpdateSize();
-
-      emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnWindowResize); // DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 !!
-
-      emscripten_set_wheel_callback("#mycanvas1", widget1_.get(), false, OnMouseWheel);
-      emscripten_set_wheel_callback("#mycanvas2", widget2_.get(), false, OnMouseWheel);
-      emscripten_set_wheel_callback("#mycanvas3", widget3_.get(), false, OnMouseWheel);
-
-      emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyDown);
-      emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyUp);
-    
-      //emscripten_request_animation_frame_loop(OnAnimationFrame, NULL);
-
-      std::string ct;
-      if (GetArgument(ct, "ct"))
-      {
-        //loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");
-        loader_->LoadSeries(ct);
-      }
-      else
-      {
-        LOG(ERROR) << "No Orthanc identifier for the CT series was provided";
-      }
-    }
-    catch (Orthanc::OrthancException& e)
-    {
-      LOG(ERROR) << "Exception during Initialize(): " << e.What();
-    }
-  }
-}
-
--- a/OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../Common/RtViewerApp.h"
-#include "../../Common/RtViewerView.h"
-#include "../../Common/SampleHelpers.h"
-
-// Stone of Orthanc includes
-#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h"
-#include "../../../Sources/StoneException.h"
-#include "../../../Sources/StoneInitialization.h"
-#include "../../../Sources/Viewport/WebGLViewport.h"
-//#include "../../../Sources/OpenGL/WebAssemblyOpenGLContext.h"
-
-#include <Toolbox.h>
-
-#include <boost/program_options.hpp>
-#include <boost/shared_ptr.hpp>
-// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions
-
-#include <emscripten.h>
-#include <emscripten/html5.h>
-
-
-#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
-  EM_ASM(                                                       \
-    const customEvent = document.createEvent("CustomEvent");    \
-    customEvent.initCustomEvent(name, false, false, undefined); \
-    window.dispatchEvent(customEvent);                          \
-    );
-
-#define EXTERN_CATCH_EXCEPTIONS                         \
-  catch (Orthanc::OrthancException& e)                  \
-  {                                                     \
-    LOG(ERROR) << "OrthancException: " << e.What();     \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (OrthancStone::StoneException& e)               \
-  {                                                     \
-    LOG(ERROR) << "StoneException: " << e.What();       \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (std::exception& e)                             \
-  {                                                     \
-    LOG(ERROR) << "Runtime error: " << e.what();        \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (...)                                           \
-  {                                                     \
-    LOG(ERROR) << "Native exception";                   \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }
-
-namespace OrthancStone
-{
-  //   typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData);
-
-  EM_BOOL RtViewerView_Scroll(int eventType, 
-                              const EmscriptenWheelEvent* wheelEvent, 
-                              void* userData)
-  {
-    RtViewerView* that = reinterpret_cast<RtViewerView*>(userData);
-
-    int delta = 0;
-    if (wheelEvent->deltaY < 0)
-      delta = -1;
-    if (wheelEvent->deltaY > 0)
-      delta = 1;
-
-    that->Scroll(delta);
-
-    return 1;
-  }
-  
-  boost::shared_ptr<IViewport> RtViewerView::CreateViewport(
-    const std::string& canvasId)
-  {
-    boost::shared_ptr<IViewport> viewport = WebGLViewport::Create(canvasId);
-
-    void* userData = reinterpret_cast<void*>(this);
-
-    // manually add the mouse wheel handler
-
-    std::string selector = "#" + canvasId;
-
-    emscripten_set_wheel_callback_on_thread(
-      selector.c_str(),
-      userData,
-      false,
-      &RtViewerView_Scroll,
-      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
-
-    return viewport;
-  }
-
-  void RtViewerView::TakeScreenshot(const std::string& target,
-                                   unsigned int canvasWidth,
-                                   unsigned int canvasHeight)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-
-
-  void RtViewerApp::RunWasm()
-  {
-    loadersContext_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
-
-    // we are in WASM --> downcast to concrete type
-    boost::shared_ptr<WebAssemblyLoadersContext> loadersContext = 
-      boost::dynamic_pointer_cast<WebAssemblyLoadersContext>(loadersContext_);
-
-    if (HasArgument("orthanc"))
-      loadersContext->SetLocalOrthanc(GetArgument("orthanc"));
-    else 
-      loadersContext->SetLocalOrthanc("..");
-
-    loadersContext->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
-
-    CreateLoaders();
-    
-    CreateView("RtViewer_Axial", VolumeProjection_Axial);
-    CreateView("RtViewer_Coronal", VolumeProjection_Coronal);
-    CreateView("RtViewer_Sagittal", VolumeProjection_Sagittal);
-
-    for (size_t i = 0; i < views_.size(); ++i)
-    {
-      views_[i]->PrepareViewport();
-    }
-
-    StartLoaders();
-  }
-}
-
-extern "C"
-{
-  boost::shared_ptr<OrthancStone::RtViewerApp> g_app;
-
-  int main(int argc, char const *argv[]) 
-  {
-    try
-    {
-      OrthancStone::StoneInitialize();
-      Orthanc::Logging::Initialize();
-      Orthanc::Logging::EnableTraceLevel(true);
-
-      LOG(WARNING) << "Initializing native Stone";
-
-      LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__
-                   << "." << __EMSCRIPTEN_minor__
-                   << "." << __EMSCRIPTEN_tiny__;
-
-      LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness());
-
-      g_app = OrthancStone::RtViewerApp::Create();
-  
-      DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized");
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  void Initialize(const char* canvasId)
-  {
-    try
-    {
-      g_app->RunWasm();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetArgument(const char* key, const char* value)
-  {
-    // This is called for each GET argument (cf. "app.js")
-    LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]";
-    g_app->SetArgument(key, value);
-  }
-
-}
--- a/OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-
-// This object wraps the functions exposed by the wasm module
-
-const WasmModuleWrapper = function() {
-  this._InitializeViewport = undefined;
-};
-
-WasmModuleWrapper.prototype.Setup = function(Module) {
-  this._SetArgument = Module.cwrap('SetArgument', null, [ 'string', 'string' ]);
-  this._Initialize = Module.cwrap('Initialize', null, [ 'string' ]);
-};
-
-WasmModuleWrapper.prototype.SetArgument = function(key, value) {
-  this._SetArgument(key, value);
-};
-
-WasmModuleWrapper.prototype.Initialize = function(canvasId) {
-  this._Initialize(canvasId);
-};
-
-var wasmModuleWrapper = new WasmModuleWrapper();
-
-$(document).ready(function() {
-
-  window.addEventListener('WasmModuleInitialized', function() {
-
-    // bind the C++ global functions
-    wasmModuleWrapper.Setup(Module);
-
-    console.warn('Native C++ module initialized');
-
-    // Loop over the GET arguments
-    var parameters = window.location.search.substr(1);
-    if (parameters != null && parameters != '') {
-      var tokens = parameters.split('&');
-      for (var i = 0; i < tokens.length; i++) {
-        var arg = tokens[i].split('=');
-        if (arg.length == 2) {
-          // Send each GET argument to WebAssembly
-          wasmModuleWrapper.SetArgument(arg[0], decodeURIComponent(arg[1]));
-        }
-      }
-    }
-    wasmModuleWrapper.Initialize();
-  });
-
-  window.addEventListener('StoneException', function() {
-    alert('Exception caught in C++ code');
-  });    
-
-  var scriptSource;
-
-  if ('WebAssembly' in window) {
-    console.warn('Loading WebAssembly');
-    scriptSource = 'RtViewerWasm.js';
-  } else {
-    console.error('Your browser does not support WebAssembly!');
-  }
-
-  // Option 1: Loading script using plain HTML
-  
-  /*
-    var script = document.createElement('script');
-    script.src = scriptSource;
-    script.type = 'text/javascript';
-    document.body.appendChild(script);
-  */
-
-  // Option 2: Loading script using AJAX (gives the opportunity to
-  // report explicit errors)
-  
-  axios.get(scriptSource)
-    .then(function (response) {
-      var script = document.createElement('script');
-      script.innerHTML = response.data;
-      script.type = 'text/javascript';
-      document.body.appendChild(script);
-    })
-    .catch(function (error) {
-      alert('Cannot load the WebAssembly framework');
-    });
-});
-
-// http://localhost:9979/stone-rtviewer/index.html?loglevel=trace&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
-
--- a/OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-
-const Stone = function() {
-  this._InitializeViewport = undefined;
-  this._LoadOrthanc = undefined;
-  this._LoadDicomWeb = undefined;
-};
-
-Stone.prototype.Setup = function(Module) {
-  this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]);
-  this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]);
-  this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]);
-};
-
-Stone.prototype.InitializeViewport = function(canvasId) {
-  this._InitializeViewport(canvasId);
-};
-
-Stone.prototype.LoadOrthanc = function(instance, frame) {
-  this._LoadOrthanc(instance, frame);
-};
-
-Stone.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) {
-  this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame);
-};
-
-var stone = new Stone();
-
--- a/OrthancStone/Samples/WebAssembly/RtViewer/index.html	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-<!doctype html>
-<html lang="en-us">
-  <head>
-    <title>Stone of Orthanc Single Frame Viewer </title>
-    <meta charset="utf-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
-    <meta name="apple-mobile-web-app-capable" content="yes" />
-    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
-    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
-
-    <!-- Disable pinch zoom on mobile devices -->
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
-    <meta name="HandheldFriendly" content="true" />
-    
-    <style>
-      html, body {
-      width: 100%;
-      height: 100%;
-      margin: 0px;
-      border: 0;
-      overflow: hidden; /*  Disable scrollbars */
-      display: block;  /* No floating content on sides */
-      }
-
-      #RtViewer_Axial {
-        position: absolute;
-        left: 0%;
-        top: 0%;
-        background-color: red;
-        width: 50%;
-        height: 100%;
-      }
-
-      #RtViewer_Coronal {
-        position: absolute;
-        left: 50%;
-        top: 0%;
-        background-color: green;
-        width: 50%;
-        height: 50%;
-      }
-
-      #RtViewer_Sagittal {
-        position: absolute;
-        left: 50%;
-        top: 50%;
-        background-color: blue;
-        width: 50%;
-        height: 50%;
-      }
-    </style>
-  </head>
-  <body>
-    <canvas id="RtViewer_Axial" oncontextmenu="return false;"></canvas>
-    <canvas id="RtViewer_Coronal" oncontextmenu="return false;"></canvas>
-    <canvas id="RtViewer_Sagittal" oncontextmenu="return false;"></canvas>
-
-    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
-
-    <script src="RtViewerWasmApp.js"></script>
-  </body>
-</html>
--- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-cmake_minimum_required(VERSION 2.8.3)
-
-project(SingleFrameViewer)
-
-# Configuration of the Emscripten compiler for WebAssembly target
-# ---------------------------------------------------------------
-set(USE_WASM ON CACHE BOOL "")
-
-set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
-
-set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
-endif()
-
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
-
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
-add_definitions(
-  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
-)
-
-# Stone of Orthanc configuration
-# ---------------------------------------------------------------
-set(ALLOW_DOWNLOADS ON)
-
-include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake)
-
-SET(ENABLE_DCMTK OFF)  # Not necessary
-SET(ENABLE_GOOGLE_TEST OFF)
-SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
-SET(ORTHANC_SANDBOXED ON)
-
-# this will set up the build system for Stone of Orthanc and will
-# populate the ORTHANC_STONE_SOURCES CMake variable
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-
-# Define the WASM module
-# ---------------------------------------------------------------
-add_executable(SingleFrameViewerWasm
-  SingleFrameViewer.cpp
-  ${ORTHANC_STONE_SOURCES}
-  )
-
-# Declare installation files for the module
-# ---------------------------------------------------------------
-install(
-  TARGETS SingleFrameViewerWasm
-  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
-  )
-
-# Declare installation files for the companion files (web scaffolding)
-# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js
-# (the generated JS loader for the WASM module) is handled by the `install1`
-# section above: it is considered to be the binary output of 
-# the linker.
-# ---------------------------------------------------------------
-install(
-  FILES
-  ${CMAKE_SOURCE_DIR}/SingleFrameViewerApp.js
-  ${CMAKE_SOURCE_DIR}/index.html
-  ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm
-  DESTINATION ${CMAKE_INSTALL_PREFIX}
-  )
--- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-{
-  "configurations": [
-    {
-      "name": "wasm32-RelWithDebInfo",
-      "generator": "Ninja",
-      "configurationType": "RelWithDebInfo",
-      //"inheritEnvironments": [ "msvc_x64_x64" ],
-      "buildRoot": "${projectDir}\\out\\build\\${name}",
-      "installRoot": "${projectDir}\\out\\install\\${name}",
-      "cmakeCommandArgs": "",
-      "buildCommandArgs": "-v",
-      "ctestCommandArgs": "",
-      "cmakeToolchain": "C:/osi/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
-      "intelliSenseMode": "windows-clang-x64",
-      "variables": [
-        {
-          "name": "CMAKE_BUILD_TYPE",
-          "value": "RelWithDebInfo",
-          "type": "STRING"
-        },
-        {
-          "name": "ALLOW_DOWNLOADS",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "STATIC_BUILD",
-          "value": "True",
-          "type": "BOOL"
-        },
-        {
-          "name": "OPENSSL_NO_CAPIENG",
-          "value": "True",
-          "type": "BOOL"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
--- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "SingleFrameViewerApplication.h"
-
-#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h"
-#include "../../../Sources/StoneException.h"
-#include "../../../Sources/StoneInitialization.h"
-
-#include <Compatibility.h>  // For std::unique_ptr<>
-#include <Toolbox.h>
-
-#include <emscripten.h>
-#include <emscripten/html5.h>
-
-
-#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
-  EM_ASM(                                                       \
-    const customEvent = document.createEvent("CustomEvent");    \
-    customEvent.initCustomEvent(name, false, false, undefined); \
-    window.dispatchEvent(customEvent);                          \
-    );
-
-#define EXTERN_CATCH_EXCEPTIONS                         \
-  catch (Orthanc::OrthancException& e)                  \
-  {                                                     \
-    LOG(ERROR) << "OrthancException: " << e.What();     \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (OrthancStone::StoneException& e)               \
-  {                                                     \
-    LOG(ERROR) << "StoneException: " << e.What();       \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (std::exception& e)                             \
-  {                                                     \
-    LOG(ERROR) << "Runtime error: " << e.what();        \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (...)                                           \
-  {                                                     \
-    LOG(ERROR) << "Native exception";                   \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }
-
-
-
-namespace OrthancStone
-{
-}
-
-static std::unique_ptr<OrthancStone::WebAssemblyLoadersContext>  context_;
-static boost::shared_ptr<OrthancStone::Application>  application_;
-
-extern "C"
-{
-  int main(int argc, char const *argv[]) 
-  {
-    try
-    {
-      Orthanc::Logging::Initialize();
-      Orthanc::Logging::EnableInfoLevel(true);
-      //Orthanc::Logging::EnableTraceLevel(true);
-      LOG(WARNING) << "Initializing native Stone";
-
-      LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__
-                   << "." << __EMSCRIPTEN_minor__
-                   << "." << __EMSCRIPTEN_tiny__;
-
-      LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness());
-      context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
-      context_->SetLocalOrthanc("..");
-      context_->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
-  
-      DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized");
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-
-    return 0;
-  }
-  
-  EMSCRIPTEN_KEEPALIVE
-  void InitializeViewport(const char* canvasId)
-  {
-    try
-    {
-      if (context_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
-                                        "The loaders context is not available yet");
-      }
-      
-      if (application_.get() != NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
-                                        "Only one single viewport is available for this application");
-      }
-
-      boost::shared_ptr<OrthancStone::WebGLViewport> viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId));
-      application_ = OrthancStone::Application::Create(*context_, viewport);
-
-      {
-        OrthancStone::WebGLViewportsRegistry::Accessor accessor(
-          OrthancStone::GetWebGLViewportsRegistry(), canvasId);
-
-        if (accessor.IsValid())
-        {
-          accessor.GetViewport().Invalidate();
-        }
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-  
-  EMSCRIPTEN_KEEPALIVE
-  void LoadFromOrthanc(const char* instance,
-                       int frame)
-  {
-    try
-    {
-      if (application_.get() != NULL)
-      {
-        OrthancStone::DicomSource source;
-        application_->LoadOrthancFrame(source, instance, frame);
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-  
-  EMSCRIPTEN_KEEPALIVE
-  void LoadFromDicomWeb(const char* server,
-                        const char* studyInstanceUid,
-                        const char* seriesInstanceUid,
-                        const char* sopInstanceUid,
-                        int frame)
-  {
-    try
-    {
-      if (application_.get() != NULL)
-      {
-        OrthancStone::DicomSource source;
-        source.SetDicomWebThroughOrthancSource(server);
-        application_->LoadDicomWebFrame(source, studyInstanceUid, seriesInstanceUid,
-                                        sopInstanceUid, frame);
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-}
--- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-
-// This object wraps the functions exposed by the wasm module
-
-const WasmModuleWrapper = function() {
-  this._InitializeViewport = undefined;
-  this._LoadFromOrthanc = undefined;
-};
-
-WasmModuleWrapper.prototype.Setup = function(Module) {
-  this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]);
-  this._LoadFromOrthanc = Module.cwrap('LoadFromOrthanc', null, [ 'string', 'int' ]);
-};
-
-WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) {
-  this._InitializeViewport(canvasId);
-};
-
-WasmModuleWrapper.prototype.LoadFromOrthanc = function(instance, frame) {
-  this._LoadFromOrthanc(instance, frame);
-};
-
-var wasmModuleWrapper = new WasmModuleWrapper();
-
-$(document).ready(function() {
-
-  window.addEventListener('WasmModuleInitialized', function() {
-    wasmModuleWrapper.Setup(Module);
-    console.warn('Native C++ module initialized');
-
-    wasmModuleWrapper.InitializeViewport('viewport');
-  });
-
-  window.addEventListener('StoneException', function() {
-    alert('Exception caught in C++ code');
-  });    
-
-  var scriptSource;
-
-  if ('WebAssembly' in window) {
-    console.warn('Loading WebAssembly');
-    scriptSource = 'SingleFrameViewerWasm.js';
-  } else {
-    console.error('Your browser does not support WebAssembly!');
-  }
-
-  // Option 1: Loading script using plain HTML
-  
-  /*
-    var script = document.createElement('script');
-    script.src = scriptSource;
-    script.type = 'text/javascript';
-    document.body.appendChild(script);
-  */
-
-  // Option 2: Loading script using AJAX (gives the opportunity to
-  // report explicit errors)
-  
-  axios.get(scriptSource)
-    .then(function (response) {
-      var script = document.createElement('script');
-      script.innerHTML = response.data;
-      script.type = 'text/javascript';
-      document.body.appendChild(script);
-    })
-    .catch(function (error) {
-      alert('Cannot load the WebAssembly framework');
-    });
-});
-
-
-$('#orthancLoad').click(function() {
-  wasmModuleWrapper.LoadFromOrthanc($('#orthancInstance').val(),
-                    $('#orthancFrame').val());
-});
--- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,502 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../../../Sources/Loaders/DicomResourcesLoader.h"
-#include "../../../Sources/Loaders/ILoadersContext.h"
-#include "../../../Sources/Loaders/SeriesFramesLoader.h"
-#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h"
-#include "../../../Sources/Viewport/IViewport.h"
-
-#include <Compatibility.h>  // For std::unique_ptr<>
-
-#include <boost/make_shared.hpp>
-
-
-namespace OrthancStone
-{
-  class Application : public ObserverBase<Application>
-  {
-  private:
-    ILoadersContext&                         context_;
-    boost::shared_ptr<IViewport>             viewport_;
-    boost::shared_ptr<DicomResourcesLoader>  dicomLoader_;
-    boost::shared_ptr<SeriesFramesLoader>    framesLoader_;
-
-    Application(ILoadersContext& context,
-                boost::shared_ptr<IViewport> viewport) : 
-      context_(context),
-      viewport_(viewport)
-    {
-    }
-
-    void Handle(const SeriesFramesLoader::FrameLoadedMessage& message)
-    {
-      LOG(INFO) << "Frame decoded! "
-                << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight()
-                << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat());
-
-      std::unique_ptr<TextureBaseSceneLayer> layer(
-        message.GetInstanceParameters().CreateTexture(message.GetImage()));
-      layer->SetLinearInterpolation(true);
-
-      {
-        std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-        lock->GetController().GetScene().SetLayer(0, layer.release());
-        lock->GetCompositor().FitContent(lock->GetController().GetScene());
-        lock->Invalidate();
-      }
-    }
-
-    void Handle(const DicomResourcesLoader::SuccessMessage& message)
-    {
-      if (message.GetResources()->GetSize() != 1)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-   
-      //message.GetResources()->GetResource(0).Print(stdout);
-
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-        SeriesFramesLoader::Factory f(*message.GetResources());
-
-        framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock));
-        Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle);
-
-        assert(message.HasUserPayload());
-        const Orthanc::SingleValueObject<unsigned int>& payload =
-          dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(message.GetUserPayload());
-
-        LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue();
-        framesLoader_->ScheduleLoadFrame(
-          0, message.GetDicomSource(), payload.GetValue(),
-          message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */,
-          NULL);
-      }
-    }
-
-  public:
-    static boost::shared_ptr<Application> Create(ILoadersContext& context,
-                                                 boost::shared_ptr<IViewport> viewport)
-    {
-      boost::shared_ptr<Application> application(new Application(context, viewport));
-
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
-        application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
-      }
-
-      application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle);
-
-      return application;
-    }
-
-    void LoadOrthancFrame(const DicomSource& source,
-                          const std::string& instanceId,
-                          unsigned int frame)
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-
-      dicomLoader_->ScheduleLoadOrthancResource(
-        boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 
-        0, source, Orthanc::ResourceType_Instance, instanceId,
-        new Orthanc::SingleValueObject<unsigned int>(frame));
-    }
-
-    void LoadDicomWebFrame(const DicomSource& source,
-                           const std::string& studyInstanceUid,
-                           const std::string& seriesInstanceUid,
-                           const std::string& sopInstanceUid,
-                           unsigned int frame)
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-
-      // We first must load the "/metadata" to know the number of frames
-      dicomLoader_->ScheduleGetDicomWeb(
-        boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source,
-        "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata",
-        new Orthanc::SingleValueObject<unsigned int>(frame));
-    }
-
-    void FitContent()
-    {
-      std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-      lock->GetCompositor().FitContent(lock->GetController().GetScene());
-      lock->Invalidate();
-    }
-  };
-
-
-
-  class IWebViewerLoadersObserver : public boost::noncopyable
-  {
-  public:
-    virtual ~IWebViewerLoadersObserver()
-    {
-    }
-
-    virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0;
-
-    virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid,
-                                       const std::string& seriesInstanceUid,
-                                       SeriesThumbnailType type) = 0;
-  };
-  
-
-  class WebViewerLoaders : public ObserverBase<WebViewerLoaders>
-  {
-  private:
-    static const int PRIORITY_ADD_RESOURCES = 0;
-    static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100;
-
-    enum Type
-    {
-      Type_Orthanc = 1,
-      Type_DicomWeb = 2
-    };
-
-    ILoadersContext&                           context_;
-    std::unique_ptr<IWebViewerLoadersObserver>   observer_;
-    bool                                       loadThumbnails_;
-    DicomSource                                source_;
-    std::set<std::string>                      scheduledSeries_;
-    std::set<std::string>                      scheduledThumbnails_;
-    std::set<std::string>                      scheduledStudies_;
-    boost::shared_ptr<LoadedDicomResources>    loadedSeries_;
-    boost::shared_ptr<LoadedDicomResources>    loadedStudies_;
-    boost::shared_ptr<DicomResourcesLoader>    resourcesLoader_;
-    boost::shared_ptr<SeriesThumbnailsLoader>  thumbnailsLoader_;
-
-    WebViewerLoaders(ILoadersContext& context,
-                     IWebViewerLoadersObserver* observer) :
-      context_(context),
-      observer_(observer),
-      loadThumbnails_(false)
-    {
-      loadedSeries_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
-      loadedStudies_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID);
-    }
-
-    static Orthanc::IDynamicObject* CreatePayload(Type type)
-    {
-      return new Orthanc::SingleValueObject<Type>(type);
-    }
-    
-    void HandleThumbnail(const SeriesThumbnailsLoader::SuccessMessage& message)
-    {
-      if (observer_.get() != NULL)
-      {
-        observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(),
-                                         message.GetSeriesInstanceUid(),
-                                         message.GetType());
-      }
-    }
-    
-    void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message)
-    {
-      LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
-
-      switch (dynamic_cast<const Orthanc::SingleValueObject<Type>&>(message.GetUserPayload()).GetValue())
-      {
-        case Type_DicomWeb:
-        {          
-          for (size_t i = 0; i < loadedSeries_->GetSize(); i++)
-          {
-            std::string study;
-            if (loadedSeries_->GetResource(i).LookupStringValue(
-                  study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
-                loadedStudies_->HasResource(study))
-            {
-              Orthanc::DicomMap m;
-              m.Assign(loadedSeries_->GetResource(i));
-              loadedStudies_->MergeResource(m, study);
-              series.AddResource(m);
-            }
-          }
-
-          break;
-        }
-
-        case Type_Orthanc:
-        {          
-          for (size_t i = 0; i < message.GetResources()->GetSize(); i++)
-          {
-            series.AddResource(message.GetResources()->GetResource(i));
-          }
-
-          break;
-        }
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      if (loadThumbnails_ &&
-          (!source_.IsDicomWeb() ||
-           source_.HasDicomWebRendered()))
-      {
-        for (size_t i = 0; i < series.GetSize(); i++)
-        {
-          std::string patientId, studyInstanceUid, seriesInstanceUid;
-          if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) &&
-              series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
-              series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) &&
-              scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end())
-          {
-            scheduledThumbnails_.insert(seriesInstanceUid);
-            thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid);
-          }
-        }
-      }
-
-      if (observer_.get() != NULL &&
-          series.GetSize() > 0)
-      {
-        observer_->SignalSeriesUpdated(series);
-      }
-    }
-
-    void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      Json::Value body;
-      message.ParseJsonBody(body);
-
-      if (body.type() != Json::arrayValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-      else
-      {
-        for (Json::Value::ArrayIndex i = 0; i < body.size(); i++)
-        {
-          if (body[i].type() == Json::stringValue)
-          {
-            AddOrthancSeries(body[i].asString());
-          }
-          else
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-          }
-        }
-      }
-    }
-
-  public:
-    static boost::shared_ptr<WebViewerLoaders> Create(ILoadersContext& context,
-                                                      const DicomSource& source,
-                                                      bool loadThumbnails,
-                                                      IWebViewerLoadersObserver* observer)
-    {
-      boost::shared_ptr<WebViewerLoaders> application(new WebViewerLoaders(context, observer));
-      application->source_ = source;
-      application->loadThumbnails_ = loadThumbnails;
-
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
-
-        application->resourcesLoader_ = DicomResourcesLoader::Create(*lock);
-
-        {
-          SeriesThumbnailsLoader::Factory f;
-          f.SetPriority(PRIORITY_THUMBNAILS);
-          application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock));
-        }
-
-        application->Register<OrthancRestApiCommand::SuccessMessage>(
-          lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi);
-
-        application->Register<DicomResourcesLoader::SuccessMessage>(
-          *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources);
-
-        application->Register<SeriesThumbnailsLoader::SuccessMessage>(
-          *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail);
-
-        lock->AddLoader(application);
-      }
-
-      return application;
-    }
-    
-    void AddDicomAllSeries()
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-
-      if (source_.IsDicomWeb())
-      {
-        resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
-                                              "/series", CreatePayload(Type_DicomWeb));
-        resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
-                                              "/studies", CreatePayload(Type_DicomWeb));
-      }
-      else if (source_.IsOrthanc())
-      {
-        std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-        command->SetMethod(Orthanc::HttpMethod_Get);
-        command->SetUri("/series");
-        lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-    }
-    
-    void AddDicomStudy(const std::string& studyInstanceUid)
-    {
-      // Avoid adding twice the same study
-      if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
-      {
-        scheduledStudies_.insert(studyInstanceUid);
-
-        if (source_.IsDicomWeb())
-        {
-          Orthanc::DicomMap filter;
-          filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
-          
-          std::set<Orthanc::DicomTag> tags;
-          
-          {
-            std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-            
-            resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
-                                           Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb));
-            
-            resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
-                                           Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb));
-          }
-        }
-        else if (source_.IsOrthanc())
-        {
-          std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-          command->SetMethod(Orthanc::HttpMethod_Post);
-          command->SetUri("/tools/find");
-
-          Json::Value body;
-          body["Level"] = "Series";
-          body["Query"] = Json::objectValue;
-          body["Query"]["StudyInstanceUID"] = studyInstanceUid;
-          command->SetBody(body);
-
-          {
-            std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-            lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
-          }
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }        
-      }
-    }
-    
-    void AddDicomSeries(const std::string& studyInstanceUid,
-                        const std::string& seriesInstanceUid)
-    {
-      std::set<Orthanc::DicomTag> tags;
-
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-
-      if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
-      {
-        scheduledStudies_.insert(studyInstanceUid);
-          
-        if (source_.IsDicomWeb())
-        {
-          Orthanc::DicomMap filter;
-          filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
-          
-          resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
-                                         Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb));
-        }
-      }
-
-      if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end())
-      {
-        scheduledSeries_.insert(seriesInstanceUid);
-
-        if (source_.IsDicomWeb())
-        {
-          Orthanc::DicomMap filter;
-          filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
-          filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false);
-          
-          resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
-                                         Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb));
-        }
-        else if (source_.IsOrthanc())
-        {
-          std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-          command->SetMethod(Orthanc::HttpMethod_Post);
-          command->SetUri("/tools/find");
-
-          Json::Value body;
-          body["Level"] = "Series";
-          body["Query"] = Json::objectValue;
-          body["Query"]["StudyInstanceUID"] = studyInstanceUid;
-          body["Query"]["SeriesInstanceUID"] = seriesInstanceUid;
-          command->SetBody(body);
-
-          lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-      }
-    }
-
-    void AddOrthancStudy(const std::string& orthancId)
-    {
-      if (source_.IsOrthanc())
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-        resourcesLoader_->ScheduleLoadOrthancResources(
-          loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
-          Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series,
-          CreatePayload(Type_Orthanc));
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
-                                        "Only applicable to Orthanc DICOM sources");
-      }
-    }
-
-    void AddOrthancSeries(const std::string& orthancId)
-    {
-      if (source_.IsOrthanc())
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-        resourcesLoader_->ScheduleLoadOrthancResource(
-          loadedSeries_, PRIORITY_ADD_RESOURCES,
-          source_, Orthanc::ResourceType_Series, orthancId,
-          CreatePayload(Type_Orthanc));
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
-                                        "Only applicable to Orthanc DICOM sources");
-      }
-    }
-  };
-}
--- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/index.html	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <title>Stone of Orthanc Single Frame Viewer </title>
-    <meta charset="utf-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
-    <meta name="apple-mobile-web-app-capable" content="yes" />
-    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
-    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
-
-    <style>
-      canvas {
-      background-color: green;
-      width : 100%;
-      height : 512px;
-      }
-    </style>
-  </head>
-  <body>
-    <h1>Viewport</h1>
-
-    <canvas id="viewport" >
-    </canvas>
-
-    <h1>Load from Orthanc</h1>
-    <p>
-      Orthanc instance: <input type="text" id="orthancInstance" size="80"
-                               value="5eb2dd5f-3fca21a8-fa7565fd-63e112ae-344830a4">
-    </p>
-    <p>
-      Frame number: <input type="text" id="orthancFrame" value="0">
-    </p>
-    <p>
-      <button id="orthancLoad">Load</button>
-    </p>
-
-    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
-    
-    <script src="SingleFrameViewerApp.js"></script>
-  </body>
-</html>
--- a/OrthancStone/Samples/WebAssembly/docker-build.sh	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-set -ex
-
-IMAGE=jodogne/wasm-builder:1.39.17-upstream
-
-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}/wasm-binaries
-
-docker run -t ${DOCKER_FLAGS} --rm \
-    --user $(id -u):$(id -g) \
-    -v ${ROOT_DIR}:/source:ro \
-    -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \
-    bash /source/OrthancStone/Samples/WebAssembly/docker-internal.sh $1
-
-ls -lR ${ROOT_DIR}/wasm-binaries/
--- a/OrthancStone/Samples/WebAssembly/docker-internal.sh	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#!/bin/bash
-set -ex
-
-source /opt/emsdk/emsdk_env.sh
-
-# Use a folder that is writeable by non-root users for the Emscripten cache
-export EM_CACHE=/tmp/emscripten-cache
-
-# Get the Orthanc framework
-cd /tmp/
-hg clone https://hg.orthanc-server.com/orthanc/
-
-# Make a copy of the read-only folder containing the source code into
-# a writeable folder, because of "DownloadPackage.cmake" that writes
-# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt"
-cd /source
-hg clone /source /tmp/source-writeable
-
-mkdir /tmp/build
-cd /tmp/build
-
-cmake /tmp/source-writeable/OrthancStone/Samples/WebAssembly \
-      -DCMAKE_BUILD_TYPE=$1 \
-      -DCMAKE_INSTALL_PREFIX=/target \
-      -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-      -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \
-      -DSTATIC_BUILD=ON \
-      -G Ninja
-
-ninja -j2 install
--- a/OrthancStone/Samples/build-wasm-samples.sh	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-#!/bin/bash
-#
-# usage:
-# to build the samples in RelWithDebInfo:
-# ./build-wasm-samples.sh
-#
-# to build the samples in Release:
-# ./build-wasm-samples.sh Release
-
-set -e
-
-if [ ! -d "WebAssembly" ]; then
-  echo "This script must be run from the Samples folder one level below orthanc-stone"
-  exit 1
-fi
-
-
-currentDir=$(pwd)
-samplesRootDir=$(pwd)
-devrootDir=$(pwd)/../../
-
-buildType=${1:-RelWithDebInfo}
-buildFolderName="$devrootDir/out/build-stone-wasm-samples-$buildType"
-installFolderName="$devrootDir/out/install-stone-wasm-samples-$buildType"
-
-mkdir -p $buildFolderName
-# change current folder to the build folder
-pushd $buildFolderName
-
-# configure the environment to use Emscripten
-source ~/apps/emsdk/emsdk_env.sh
-
-emcmake cmake -G "Ninja" \
-  -DCMAKE_BUILD_TYPE=$buildType \
-  -DCMAKE_INSTALL_PREFIX=$installFolderName \
-  -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \
-  $samplesRootDir/WebAssembly
-
-# perform build + installation
-ninja
-ninja install
-
-# restore the original working folder
-popd
-
-echo "If all went well, the output files can be found in $installFolderName:"
-
-ls $installFolderName
\ No newline at end of file
--- a/StoneWebViewer/COPYING	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,661 +0,0 @@
-                    GNU AFFERO GENERAL PUBLIC LICENSE
-                       Version 3, 19 November 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The GNU Affero General Public License is a free, copyleft license for
-software and other kinds of works, specifically designed to ensure
-cooperation with the community in the case of network server software.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-our General Public Licenses are intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  Developers that use our General Public Licenses protect your rights
-with two steps: (1) assert copyright on the software, and (2) offer
-you this License which gives you legal permission to copy, distribute
-and/or modify the software.
-
-  A secondary benefit of defending all users' freedom is that
-improvements made in alternate versions of the program, if they
-receive widespread use, become available for other developers to
-incorporate.  Many developers of free software are heartened and
-encouraged by the resulting cooperation.  However, in the case of
-software used on network servers, this result may fail to come about.
-The GNU General Public License permits making a modified version and
-letting the public access it on a server without ever releasing its
-source code to the public.
-
-  The GNU Affero General Public License is designed specifically to
-ensure that, in such cases, the modified source code becomes available
-to the community.  It requires the operator of a network server to
-provide the source code of the modified version running there to the
-users of that server.  Therefore, public use of a modified version, on
-a publicly accessible server, gives the public access to the source
-code of the modified version.
-
-  An older license, called the Affero General Public License and
-published by Affero, was designed to accomplish similar goals.  This is
-a different license, not a version of the Affero GPL, but Affero has
-released a new version of the Affero GPL which permits relicensing under
-this license.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU Affero General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Remote Network Interaction; Use with the GNU General Public License.
-
-  Notwithstanding any other provision of this License, if you modify the
-Program, your modified version must prominently offer all users
-interacting with it remotely through a computer network (if your version
-supports such interaction) an opportunity to receive the Corresponding
-Source of your version by providing access to the Corresponding Source
-from a network server at no charge, through some standard or customary
-means of facilitating copying of software.  This Corresponding Source
-shall include the Corresponding Source for any work covered by version 3
-of the GNU General Public License that is incorporated pursuant to the
-following paragraph.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the work with which it is combined will remain governed by version
-3 of the GNU General Public License.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU Affero General Public License from time to time.  Such new versions
-will be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU Affero General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU Affero General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU Affero General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    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/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If your software can interact with users remotely through a computer
-network, you should also make sure that it provides a way for users to
-get its source.  For example, if your program is a web application, its
-interface could display a "Source" link that leads users to an archive
-of the code.  There are many ways you could offer source, and different
-solutions will be better for different programs; see section 13 for the
-specific requirements.
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU AGPL, see
-<http://www.gnu.org/licenses/>.
--- a/StoneWebViewer/Plugin/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-cmake_minimum_required(VERSION 2.8.3)
-
-project(StoneWebViewerPlugin)
-
-set(ORTHANC_PLUGIN_VERSION "mainline")
-
-if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
-  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
-else()
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2")
-  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
-endif()
-
-
-
-set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../wasm-binaries/StoneWebViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder")
-
-# 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 \"system\", \"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_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
-set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked")
-mark_as_advanced(ORTHANC_FRAMEWORK_STATIC)
-
-
-# Download and setup the Orthanc framework
-include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
-
-include_directories(${ORTHANC_FRAMEWORK_ROOT})
-
-if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
-  link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES})
-
-else()
-  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
-  set(ENABLE_MODULE_IMAGES OFF)
-  set(ENABLE_MODULE_JOBS OFF)
-  set(ENABLE_MODULE_DICOM OFF)
-  include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
-endif()
-
-include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginsExports.cmake)
-
-
-if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
-  include_directories(${CMAKE_SOURCE_DIR}/../Resources/OrthancSdk-1.0.0)
-else ()
-  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
-  if (NOT HAVE_ORTHANC_H)
-    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
-  endif()
-endif()
-
-
-add_definitions(
-  -DHAS_ORTHANC_EXCEPTION=1
-  -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
-  -DPLUGIN_NAME="stone-webviewer"
-  )
-
-
-EmbedResources(
-  # Folders
-  IMAGES                 ${STONE_BINARIES}/img/
-  WEB_APPLICATION        ${CMAKE_SOURCE_DIR}/../WebApplication
-
-  # Individual files
-  ORTHANC_EXPLORER       ${CMAKE_SOURCE_DIR}/OrthancExplorer.js
-  STONE_WEB_VIEWER_JS    ${STONE_BINARIES}/StoneWebViewer.js
-  STONE_WEB_VIEWER_WASM  ${STONE_BINARIES}/StoneWebViewer.wasm
-  STONE_WRAPPER          ${STONE_BINARIES}/stone.js
-  )
-
-add_library(StoneWebViewer SHARED
-  Plugin.cpp
-  ${AUTOGENERATED_SOURCES}
-  ${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
-  ${ORTHANC_CORE_SOURCES}
-  )
-
-set_target_properties(StoneWebViewer PROPERTIES 
-  VERSION ${ORTHANC_PLUGIN_VERSION} 
-  SOVERSION ${ORTHANC_PLUGIN_VERSION})
-
-install(
-  TARGETS StoneWebViewer
-  RUNTIME DESTINATION lib    # Destination for Windows
-  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
-  )
--- a/StoneWebViewer/Plugin/OrthancExplorer.js	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-$('#study').live('pagebeforecreate', function() {
-  var b = $('<a>')
-      .attr('data-role', 'button')
-      .attr('href', '#')
-      .attr('data-icon', 'search')
-      .attr('data-theme', 'e')
-      .text('Stone Web Viewer');
-
-  b.insertBefore($('#study-delete').parent().parent());
-  b.click(function() {
-    if ($.mobile.pageData) {
-      $.ajax({
-        url: '../studies/' + $.mobile.pageData.uuid,
-        dataType: 'json',
-        cache: false,
-        success: function(study) {
-          var studyInstanceUid = study.MainDicomTags.StudyInstanceUID;
-          window.open('../stone-webviewer/index.html?study=' + studyInstanceUid);
-        }
-      });      
-    }
-  });
-});
-
-
-$('#series').live('pagebeforecreate', function() {
-  var b = $('<a>')
-      .attr('data-role', 'button')
-      .attr('href', '#')
-      .attr('data-icon', 'search')
-      .attr('data-theme', 'e')
-      .text('Stone Web Viewer');
-
-  b.insertBefore($('#series-delete').parent().parent());
-  b.click(function() {
-    if ($.mobile.pageData) {
-      $.ajax({
-        url: '../series/' + $.mobile.pageData.uuid,
-        dataType: 'json',
-        cache: false,
-        success: function(series) {
-          $.ajax({
-            url: '../studies/' + series.ParentStudy,
-            dataType: 'json',
-            cache: false,
-            success: function(study) {
-              var studyInstanceUid = study.MainDicomTags.StudyInstanceUID;
-              var seriesInstanceUid = series.MainDicomTags.SeriesInstanceUID;
-              window.open('../stone-webviewer/index.html?study=' + studyInstanceUid +
-                          '&series=' + seriesInstanceUid);
-            }
-          });      
-        }
-      });      
-    }
-  });
-});
--- a/StoneWebViewer/Plugin/Plugin.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
-
-#include <EmbeddedResources.h>
-
-#include <SystemToolbox.h>
-#include <Toolbox.h>
-
-OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
-                                        OrthancPluginResourceType resourceType,
-                                        const char* resourceId)
-{
-  try
-  {
-    if (changeType == OrthancPluginChangeType_OrthancStarted)
-    {
-      Json::Value info;
-      if (!OrthancPlugins::RestApiGet(info, "/plugins/dicom-web", false))
-      {
-        throw Orthanc::OrthancException(
-          Orthanc::ErrorCode_InternalError,
-          "The Stone Web viewer requires the DICOMweb plugin to be installed");
-      }
-
-      if (info.type() != Json::objectValue ||
-          !info.isMember("ID") ||
-          !info.isMember("Version") ||
-          info["ID"].type() != Json::stringValue ||
-          info["Version"].type() != Json::stringValue ||
-          info["ID"].asString() != "dicom-web")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                        "The DICOMweb plugin is not properly installed");
-      }
-
-      std::string version = info["Version"].asString();
-      if (version != "mainline")
-      {
-        std::vector<std::string> tokens;
-        Orthanc::Toolbox::TokenizeString(tokens, version, '.');
-        if (tokens.size() != 2)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                          "Bad version of the DICOMweb plugin: " + version);
-        }
-
-        int major, minor;
-        
-        try
-        {
-          major = boost::lexical_cast<int>(tokens[0]);
-          minor = boost::lexical_cast<int>(tokens[1]);
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                          "Bad version of the DICOMweb plugin: " + version);
-        }
-
-        if (major <= 0 ||
-            (major == 1 && minor <= 1))
-        {
-          throw Orthanc::OrthancException(
-            Orthanc::ErrorCode_InternalError,
-            "The Stone Web viewer requires DICOMweb plugin with version >= 1.2, found: " + version);
-        }
-
-        if (major <= 0 ||
-            (major == 1 && minor == 2))
-        {
-          /**
-           * DICOMweb 1.3 is better than 1.2 for 2 reasons: (1)
-           * MONOCHROME1 images are not properly rendered in DICOMweb
-           * 1.2, and (2) DICOMweb 1.2 cannot transcode images (this
-           * causes issues on JPEG2k images).
-           **/
-          LOG(WARNING) << "The Stone Web viewer has some incompatibilities "
-                       << "with DICOMweb plugin 1.2, consider upgrading the DICOMweb plugin";
-        }
-      }
-    }
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "Exception: " << e.What();
-    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-  }
-
-  return OrthancPluginErrorCode_Success;
-}
-    
-
-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);
-  }
-}
-
-
-template <enum Orthanc::EmbeddedResources::FileResourceId file>
-void ServeEmbeddedFile(OrthancPluginRestOutput* output,
-                       const char* url,
-                       const OrthancPluginHttpRequest* request)
-{
-  OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
-
-  if (request->method != OrthancPluginHttpMethod_Get)
-  {
-    OrthancPluginSendMethodNotAllowed(context, output, "GET");
-  }
-  else
-  {
-    const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url));
-
-    std::string s;
-    Orthanc::EmbeddedResources::GetFileResource(s, file);
-
-    const char* resource = s.size() ? s.c_str() : NULL;
-    OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime);
-  }
-}
-
-
-extern "C"
-{
-  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
-  {
-    OrthancPlugins::SetGlobalContext(context);
-
-#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2)
-    Orthanc::Logging::InitializePluginContext(context);
-#else
-    Orthanc::Logging::Initialize(context);
-#endif
-
-    /* Check the version of the Orthanc core */
-    if (OrthancPluginCheckVersion(context) == 0)
-    {
-      char info[1024];
-      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
-              context->orthancVersion,
-              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
-      OrthancPluginLogError(context, info);
-      return -1;
-    }
-
-    try
-    {
-      std::string explorer;
-      Orthanc::EmbeddedResources::GetFileResource(
-        explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
-      OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str());
-      
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_WASM> >
-        ("/stone-webviewer/StoneWebViewer.wasm", true);
-      
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_JS> >
-        ("/stone-webviewer/StoneWebViewer.js", true);
-      
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WRAPPER> >
-        ("/stone-webviewer/stone.js", true);
-      
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFolder<Orthanc::EmbeddedResources::IMAGES> >
-        ("/stone-webviewer/img/(.*)", true);
-
-      OrthancPlugins::RegisterRestCallback
-        <ServeEmbeddedFolder<Orthanc::EmbeddedResources::WEB_APPLICATION> >
-        ("/stone-webviewer/(.*)", true);
-
-      OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
-    }
-    catch (...)
-    {
-      OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin");
-      return -1;
-    }
-
-    return 0;
-  }
-
-
-  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
-  {
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
-  {
-    return PLUGIN_NAME;
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
-  {
-    return PLUGIN_VERSION;
-  }
-}
--- a/StoneWebViewer/Resources/GenerateImages.py	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-#!/usr/bin/env python
-
-# Stone of Orthanc
-# 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/>.
-
-
-import os
-from PIL import Image
-
-SOURCE = os.path.dirname(os.path.abspath(__file__))
-TARGET = os.path.join(SOURCE, '..', 'WebApplication', 'img')
-
-try:
-    os.makedirs(TARGET)
-except:  # Directory already exists
-    pass
-    
-color = (217, 217, 217, 255)
-border = 3
-width = 32
-height = 32
-
-
-
-image = Image.new('RGBA', (width, height))
-
-for x in range(0, width):
-    for y in range(0, height):
-        image.putpixel((x, y), color)
-
-image.save(os.path.join(TARGET, 'grid1x1.png'), 'PNG')
-
-
-
-image = Image.new('RGBA', (width, height))
-
-for x in range(0, width / 2 - border):
-    for y in range(0, height / 2 - border):
-        image.putpixel((x, y), color)
-    for y in range(height / 2 + border, height):
-        image.putpixel((x, y), color)
-
-for x in range(width / 2 + border, width):
-    for y in range(0, height / 2 - border):
-        image.putpixel((x, y), color)
-    for y in range(height / 2 + border, height):
-        image.putpixel((x, y), color)
-
-image.save(os.path.join(TARGET, 'grid2x2.png'), 'PNG')
-
-
-
-image = Image.new('RGBA', (width, height))
-
-for y in range(0, height):
-    for x in range(0, width / 2 - border):
-        image.putpixel((x, y), color)
-    for x in range(width / 2 + border, width):
-        image.putpixel((x, y), color)
-
-image.save(os.path.join(TARGET, 'grid2x1.png'), 'PNG')
-
-
-
-image = Image.new('RGBA', (width, height))
-
-for x in range(0, width):
-    for y in range(0, height / 2 - border):
-        image.putpixel((x, y), color)
-    for y in range(height / 2 + border, height):
-        image.putpixel((x, y), color)
-
-image.save(os.path.join(TARGET, 'grid1x2.png'), 'PNG')
--- a/StoneWebViewer/Resources/NOTES.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-
-Origin of SCSS
-==============
-
-The "Styles" folder is a copy of:
-https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/styles/
-
-
-
-Generation of CSS from the SCSS
-===============================
-
-$ npm install node-sass
-$ ./node_modules/node-sass/bin/node-sass ./Styles/styles.scss > ../WebApplication/app.css
-$ ./GenerateImages.py
--- a/StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list	Tue Aug 11 12:47:59 2020 +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/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3383 +0,0 @@
-/**
- * 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.
- * 
- * 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 <boost/algorithm/string/predicate.hpp>
-#include <boost/move/unique_ptr.hpp>
-#include <boost/thread.hpp>
-#include <json/reader.h>
-#include <json/writer.h>
-
-
-#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
-static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
-#endif
-
-
-namespace OrthancPlugins
-{
-  static OrthancPluginContext* globalContext_ = NULL;
-
-
-  void SetGlobalContext(OrthancPluginContext* context)
-  {
-    if (context == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
-    }
-    else if (globalContext_ == NULL)
-    {
-      globalContext_ = context;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
-    }
-  }
-
-
-  bool HasGlobalContext()
-  {
-    return globalContext_ != NULL;
-  }
-
-
-  OrthancPluginContext* GetGlobalContext()
-  {
-    if (globalContext_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
-    }
-    else
-    {
-      return globalContext_;
-    }
-  }
-
-
-  void MemoryBuffer::Check(OrthancPluginErrorCode code)
-  {
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      // Prevent using garbage information
-      buffer_.data = NULL;
-      buffer_.size = 0;
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-
-
-  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
-  {
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      // Prevent using garbage information
-      buffer_.data = NULL;
-      buffer_.size = 0;
-    }
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (code == OrthancPluginErrorCode_UnknownResource ||
-             code == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-
-
-  MemoryBuffer::MemoryBuffer()
-  {
-    buffer_.data = NULL;
-    buffer_.size = 0;
-  }
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  MemoryBuffer::MemoryBuffer(const void* buffer,
-                             size_t size)
-  {
-    uint32_t s = static_cast<uint32_t>(size);
-    if (static_cast<size_t>(s) != size)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
-    }
-    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
-             OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
-    }
-    else
-    {
-      memcpy(buffer_.data, buffer, size);
-    }
-  }
-#endif
-
-
-  void MemoryBuffer::Clear()
-  {
-    if (buffer_.data != NULL)
-    {
-      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &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::Swap(MemoryBuffer& other)
-  {
-    std::swap(buffer_.data, other.buffer_.data);
-    std::swap(buffer_.size, other.buffer_.size);
-  }
-
-
-  OrthancPluginMemoryBuffer MemoryBuffer::Release()
-  {
-    OrthancPluginMemoryBuffer result = buffer_;
-
-    buffer_.data = NULL;
-    buffer_.size = 0;
-
-    return result;
-  }
-
-
-  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)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
-
-    Json::Reader reader;
-    if (!reader.parse(tmp, tmp + buffer_.size, target))
-    {
-      LogError("Cannot convert some memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
-    }
-  }
-
-  bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                const std::map<std::string, std::string>& httpHeaders,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    std::vector<const char*> headersKeys;
-    std::vector<const char*> headersValues;
-    
-    for (std::map<std::string, std::string>::const_iterator
-           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
-    {
-      headersKeys.push_back(it->first.c_str());
-      headersValues.push_back(it->second.c_str());
-    }
-
-    return CheckHttp(OrthancPluginRestApiGet2(
-                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
-                       (headersKeys.empty() ? NULL : &headersKeys[0]),
-                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
-  }
-
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const void* body,
-                                 size_t bodySize,
-                                 bool applyPlugins)
-  {
-    Clear();
-    
-    // Cast for compatibility with Orthanc SDK <= 1.5.6
-    const char* b = reinterpret_cast<const char*>(body);
-
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const void* body,
-                                size_t bodySize,
-                                bool applyPlugins)
-  {
-    Clear();
-
-    // Cast for compatibility with Orthanc SDK <= 1.5.6
-    const char* b = reinterpret_cast<const char*>(body);
-
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
-    }
-  }
-
-
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const Json::Value& body,
-                                 bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPost(uri, writer.write(body), applyPlugins);
-  }
-
-
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const Json::Value& body,
-                                bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPut(uri, writer.write(body), applyPlugins);
-  }
-
-
-  void MemoryBuffer::CreateDicom(const Json::Value& tags,
-                                 OrthancPluginCreateDicomFlags flags)
-  {
-    Clear();
-
-    Json::FastWriter writer;
-    std::string s = writer.write(tags);
-
-    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
-  }
-
-  void MemoryBuffer::CreateDicom(const Json::Value& tags,
-                                 const OrthancImage& pixelData,
-                                 OrthancPluginCreateDicomFlags flags)
-  {
-    Clear();
-
-    Json::FastWriter writer;
-    std::string s = writer.write(tags);
-
-    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
-  }
-
-
-  void MemoryBuffer::ReadFile(const std::string& path)
-  {
-    Clear();
-    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
-  }
-
-
-  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
-  {
-    Clear();
-    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
-  }
-
-
-  void OrthancString::Assign(char* str)
-  {
-    Clear();
-
-    if (str != NULL)
-    {
-      str_ = str;
-    }
-  }
-
-
-  void OrthancString::Clear()
-  {
-    if (str_ != NULL)
-    {
-      OrthancPluginFreeString(GetGlobalContext(), 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)
-    {
-      LogError("Cannot convert an empty memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Reader reader;
-    if (!reader.parse(str_, target))
-    {
-      LogError("Cannot convert some memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  void MemoryBuffer::DicomToJson(Json::Value& target,
-                                 OrthancPluginDicomToJsonFormat format,
-                                 OrthancPluginDicomToJsonFlags flags,
-                                 uint32_t maxStringLength)
-  {
-    OrthancString str;
-    str.Assign(OrthancPluginDicomBufferToJson
-               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
-    str.ToJson(target);
-  }
-
-
-  bool MemoryBuffer::HttpGet(const std::string& url,
-                             const std::string& username,
-                             const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
-                                          username.empty() ? NULL : username.c_str(),
-                                          password.empty() ? NULL : password.c_str()));
-  }
-
-
-  bool MemoryBuffer::HttpPost(const std::string& url,
-                              const std::string& body,
-                              const std::string& username,
-                              const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
-                                           body.c_str(), body.size(),
-                                           username.empty() ? NULL : username.c_str(),
-                                           password.empty() ? NULL : password.c_str()));
-  }
-
-
-  bool MemoryBuffer::HttpPut(const std::string& url,
-                             const std::string& body,
-                             const std::string& username,
-                             const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
-                                          body.empty() ? NULL : body.c_str(),
-                                          body.size(),
-                                          username.empty() ? NULL : username.c_str(),
-                                          password.empty() ? NULL : password.c_str()));
-  }
-
-
-  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
-  {
-    Clear();
-    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
-  }
-
-
-  bool HttpDelete(const std::string& url,
-                  const std::string& username,
-                  const std::string& password)
-  {
-    OrthancPluginErrorCode error = OrthancPluginHttpDelete
-      (GetGlobalContext(), url.c_str(),
-       username.empty() ? NULL : username.c_str(),
-       password.empty() ? NULL : password.c_str());
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-
-
-  void LogError(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogError(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void LogWarning(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void LogInfo(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void OrthancConfiguration::LoadConfiguration()
-  {
-    OrthancString str;
-    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
-
-    if (str.GetContent() == NULL)
-    {
-      LogError("Cannot access the Orthanc configuration");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    str.ToJson(configuration_);
-
-    if (configuration_.type() != Json::objectValue)
-    {
-      LogError("Unable to read the Orthanc configuration");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-    
-
-  OrthancConfiguration::OrthancConfiguration()
-  {
-    LoadConfiguration();
-  }
-
-
-  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
-  {
-    if (loadConfiguration)
-    {
-      LoadConfiguration();
-    }
-    else
-    {
-      configuration_ = Json::objectValue;
-    }
-  }
-
-
-  std::string OrthancConfiguration::GetPath(const std::string& key) const
-  {
-    if (path_.empty())
-    {
-      return key;
-    }
-    else
-    {
-      return path_ + "." + key;
-    }
-  }
-
-
-  bool OrthancConfiguration::IsSection(const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    return (configuration_.isMember(key) &&
-            configuration_[key].type() == Json::objectValue);
-  }
-
-
-  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
-                                        const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.path_ = GetPath(key);
-
-    if (!configuration_.isMember(key))
-    {
-      target.configuration_ = Json::objectValue;
-    }
-    else
-    {
-      if (configuration_[key].type() != Json::objectValue)
-      {
-        LogError("The configuration section \"" + target.path_ +
-                 "\" is not an associative array as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(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)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(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:
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not an integer as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
-                                                        const std::string& key) const
-  {
-    int tmp;
-    if (!LookupIntegerValue(tmp, key))
-    {
-      return false;
-    }
-
-    if (tmp < 0)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a positive integer as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(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)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a Boolean as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(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 = static_cast<float>(configuration_[key].asInt());
-        return true;
-
-      case Json::uintValue:
-        target = static_cast<float>(configuration_[key].asUInt());
-        return true;
-
-      default:
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not an integer as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
-                                                 const std::string& key,
-                                                 bool allowSingleString) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.clear();
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-      case Json::arrayValue:
-      {
-        bool ok = true;
-
-        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
-        {
-          if (configuration_[key][i].type() == Json::stringValue)
-          {
-            target.push_back(configuration_[key][i].asString());
-          }
-          else
-          {
-            ok = false;
-          }
-        }
-
-        if (ok)
-        {
-          return true;
-        }
-
-        break;
-      }
-
-      case Json::stringValue:
-        if (allowSingleString)
-        {
-          target.push_back(configuration_[key].asString());
-          return true;
-        }
-
-        break;
-
-      default:
-        break;
-    }
-
-    LogError("The configuration option \"" + GetPath(key) +
-             "\" is not a list of strings as expected");
-
-    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-  }
-
-
-  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
-                                                const std::string& key,
-                                                bool allowSingleString) const
-  {
-    std::list<std::string> lst;
-
-    if (LookupListOfStrings(lst, key, allowSingleString))
-    {
-      target.clear();
-
-      for (std::list<std::string>::const_iterator
-             it = lst.begin(); it != lst.end(); ++it)
-      {
-        target.insert(*it);
-      }
-
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  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 OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
-                                           const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.clear();
-
-    if (!configuration_.isMember(key))
-    {
-      return;
-    }
-
-    if (configuration_[key].type() != Json::objectValue)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    Json::Value::Members members = configuration_[key].getMemberNames();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& value = configuration_[key][members[i]];
-
-      if (value.type() == Json::stringValue)
-      {
-        target[members[i]] = value.asString();
-      }
-      else
-      {
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not a dictionary mapping strings to strings");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-      }
-    }
-  }
-
-
-  void OrthancImage::Clear()
-  {
-    if (image_ != NULL)
-    {
-      OrthancPluginFreeImage(GetGlobalContext(), image_);
-      image_ = NULL;
-    }
-  }
-
-
-  void OrthancImage::CheckImageAvailable() const
-  {
-    if (image_ == NULL)
-    {
-      LogError("Trying to access a NULL image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancImage::OrthancImage() :
-    image_(NULL)
-  {
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
-    image_(image)
-  {
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height)
-  {
-    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot create an image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height,
-                             uint32_t                  pitch,
-                             void*                     buffer)
-  {
-    image_ = OrthancPluginCreateImageAccessor
-      (GetGlobalContext(), format, width, height, pitch, buffer);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot create an image accessor");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-  void OrthancImage::UncompressPngImage(const void* data,
-                                        size_t size)
-  {
-    Clear();
-
-    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a PNG image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::UncompressJpegImage(const void* data,
-                                         size_t size)
-  {
-    Clear();
-    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a JPEG image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::DecodeDicomImage(const void* data,
-                                      size_t size,
-                                      unsigned int frame)
-  {
-    Clear();
-    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a DICOM image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetWidth() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetHeight() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetPitch() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
-  }
-
-
-  void* OrthancImage::GetBuffer() const
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
-  }
-
-
-  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
-  {
-    CheckImageAvailable();
-
-    OrthancPlugins::MemoryBuffer answer;
-    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
-                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-
-    target.Swap(answer);
-  }
-
-
-  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
-                                       uint8_t quality) const
-  {
-    CheckImageAvailable();
-
-    OrthancPlugins::MemoryBuffer answer;
-    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
-                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-
-    target.Swap(answer);
-  }
-
-
-  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
-                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-  }
-
-
-  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
-                                     uint8_t quality) const
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
-                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-  }
-
-
-  OrthancPluginImage* OrthancImage::Release()
-  {
-    CheckImageAvailable();
-    OrthancPluginImage* tmp = image_;
-    image_ = NULL;
-    return tmp;
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
-  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
-    matcher_(NULL),
-    worklist_(worklist)
-  {
-    if (worklist_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void FindMatcher::SetupDicom(const void*  query,
-                               uint32_t     size)
-  {
-    worklist_ = NULL;
-
-    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
-    if (matcher_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-
-  FindMatcher::~FindMatcher()
-  {
-    // The "worklist_" field
-
-    if (matcher_ != NULL)
-    {
-      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
-    }
-  }
-
-
-
-  bool FindMatcher::IsMatch(const void*  dicom,
-                            uint32_t     size) const
-  {
-    int32_t result;
-
-    if (matcher_ != NULL)
-    {
-      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
-    }
-    else if (worklist_ != NULL)
-    {
-      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    if (result == 0)
-    {
-      return false;
-    }
-    else if (result == 1)
-    {
-      return true;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
-
-  void AnswerJson(const Json::Value& value,
-                  OrthancPluginRestOutput* output
-    )
-  {
-    Json::StyledWriter writer;
-    std::string bodyString = writer.write(value);
-
-    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
-  }
-
-  void AnswerString(const std::string& answer,
-                    const char* mimeType,
-                    OrthancPluginRestOutput* output
-    )
-  {
-    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
-  }
-
-  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
-  {
-    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
-  }
-
-  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
-  {
-    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
-  }
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToString(result);
-      return true;
-    }
-  }
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        const std::map<std::string, std::string>& httpHeaders,
-                        bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToString(result);
-      return true;
-    }
-  }
-
-
-
-  bool RestApiGet(Json::Value& result,
-                  const std::string& uri,
-                  bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(std::string& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        result.assign(answer.GetData(), answer.GetSize());
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const Json::Value& body,
-                   bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPost(result, uri, writer.write(body), applyPlugins);
-  }
-
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const void* body,
-                  size_t bodySize,
-                  bool applyPlugins)
-  {
-    MemoryBuffer answer;
-
-    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const Json::Value& body,
-                  bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPut(result, uri, writer.write(body), applyPlugins);
-  }
-
-
-  bool RestApiDelete(const std::string& uri,
-                     bool applyPlugins)
-  {
-    OrthancPluginErrorCode error;
-
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
-    }
-    else
-    {
-      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-
-
-  void ReportMinimalOrthancVersion(unsigned int major,
-                                   unsigned int minor,
-                                   unsigned int revision)
-  {
-    LogError("Your version of the Orthanc core (" +
-             std::string(GetGlobalContext()->orthancVersion) +
-             ") is too old to run this plugin (version " +
-             boost::lexical_cast<std::string>(major) + "." +
-             boost::lexical_cast<std::string>(minor) + "." +
-             boost::lexical_cast<std::string>(revision) +
-             " is required)");
-  }
-
-
-  bool CheckMinimalOrthancVersion(unsigned int major,
-                                  unsigned int minor,
-                                  unsigned int revision)
-  {
-    if (!HasGlobalContext())
-    {
-      LogError("Bad Orthanc context in the plugin");
-      return false;
-    }
-
-    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
-    {
-      // Assume compatibility with the mainline
-      return true;
-    }
-
-    // Parse the version of the Orthanc core
-    int aa, bb, cc;
-    if (
-#ifdef _MSC_VER
-      sscanf_s
-#else
-      sscanf
-#endif
-      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
-      aa < 0 ||
-      bb < 0 ||
-      cc < 0)
-    {
-      return false;
-    }
-
-    unsigned int a = static_cast<unsigned int>(aa);
-    unsigned int b = static_cast<unsigned int>(bb);
-    unsigned int c = static_cast<unsigned int>(cc);
-
-    // Check the major version number
-
-    if (a > major)
-    {
-      return true;
-    }
-
-    if (a < major)
-    {
-      return false;
-    }
-
-
-    // Check the minor version number
-    assert(a == major);
-
-    if (b > minor)
-    {
-      return true;
-    }
-
-    if (b < minor)
-    {
-      return false;
-    }
-
-    // Check the patch level version number
-    assert(a == major && b == minor);
-
-    if (c >= revision)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-  const char* AutodetectMimeType(const std::string& path)
-  {
-    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
-
-    if (mime == NULL)
-    {
-      // Should never happen, just for safety
-      return "application/octet-stream";
-    }
-    else
-    {
-      return mime;
-    }
-  }
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_PEERS == 1
-  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
-  {
-    size_t index;
-    if (LookupName(index, name))
-    {
-      return index;
-    }
-    else
-    {
-      LogError("Inexistent peer: " + name);
-      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
-    }
-  }
-
-
-  OrthancPeers::OrthancPeers() :
-    peers_(NULL),
-    timeout_(0)
-  {
-    peers_ = OrthancPluginGetPeers(GetGlobalContext());
-
-    if (peers_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-
-    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
-
-    for (uint32_t i = 0; i < count; i++)
-    {
-      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
-      if (name == NULL)
-      {
-        OrthancPluginFreePeers(GetGlobalContext(), peers_);
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-
-      index_[name] = i;
-    }
-  }
-
-
-  OrthancPeers::~OrthancPeers()
-  {
-    if (peers_ != NULL)
-    {
-      OrthancPluginFreePeers(GetGlobalContext(), peers_);
-    }
-  }
-
-
-  bool OrthancPeers::LookupName(size_t& target,
-                                const std::string& name) const
-  {
-    Index::const_iterator found = index_.find(name);
-
-    if (found == index_.end())
-    {
-      return false;
-    }
-    else
-    {
-      target = found->second;
-      return true;
-    }
-  }
-
-
-  std::string OrthancPeers::GetPeerName(size_t index) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
-      if (s == NULL)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-      else
-      {
-        return s;
-      }
-    }
-  }
-
-
-  std::string OrthancPeers::GetPeerUrl(size_t index) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
-      if (s == NULL)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-      else
-      {
-        return s;
-      }
-    }
-  }
-
-
-  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
-  {
-    return GetPeerUrl(GetPeerIndex(name));
-  }
-
-
-  bool OrthancPeers::LookupUserProperty(std::string& value,
-                                        size_t index,
-                                        const std::string& key) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
-      if (s == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        value.assign(s);
-        return true;
-      }
-    }
-  }
-
-
-  bool OrthancPeers::LookupUserProperty(std::string& value,
-                                        const std::string& peer,
-                                        const std::string& key) const
-  {
-    return LookupUserProperty(value, GetPeerIndex(peer), key);
-  }
-
-
-  bool OrthancPeers::DoGet(MemoryBuffer& target,
-                           size_t index,
-                           const std::string& uri) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      target.Swap(answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoGet(MemoryBuffer& target,
-                           const std::string& name,
-                           const std::string& uri) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoGet(target, index, uri));
-  }
-
-
-  bool OrthancPeers::DoGet(Json::Value& target,
-                           size_t index,
-                           const std::string& uri) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoGet(buffer, index, uri))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoGet(Json::Value& target,
-                           const std::string& name,
-                           const std::string& uri) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoGet(buffer, name, uri))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPost(MemoryBuffer& target,
-                            const std::string& name,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoPost(target, index, uri, body));
-  }
-
-
-  bool OrthancPeers::DoPost(Json::Value& target,
-                            size_t index,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoPost(buffer, index, uri, body))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPost(Json::Value& target,
-                            const std::string& name,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoPost(buffer, name, uri, body))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPost(MemoryBuffer& target,
-                            size_t index,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      target.Swap(answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPut(size_t index,
-                           const std::string& uri,
-                           const std::string& body) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoPut(const std::string& name,
-                           const std::string& uri,
-                           const std::string& body) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoPut(index, uri, body));
-  }
-
-
-  bool OrthancPeers::DoDelete(size_t index,
-                              const std::string& uri) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPlugins::MemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
-       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool OrthancPeers::DoDelete(const std::string& name,
-                              const std::string& uri) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoDelete(index, uri));
-  }
-#endif
-
-
-
-
-
-  /******************************************************************
-   ** JOBS
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_JOB == 1
-  void OrthancJob::CallbackFinalize(void* job)
-  {
-    if (job != NULL)
-    {
-      delete reinterpret_cast<OrthancJob*>(job);
-    }
-  }
-
-
-  float OrthancJob::CallbackGetProgress(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->progress_;
-    }
-    catch (...)
-    {
-      return 0;
-    }
-  }
-
-
-  const char* OrthancJob::CallbackGetContent(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
-    }
-    catch (...)
-    {
-      return 0;
-    }
-  }
-
-
-  const char* OrthancJob::CallbackGetSerialized(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
-
-      if (tmp.hasSerialized_)
-      {
-        return tmp.serialized_.c_str();
-      }
-      else
-      {
-        return NULL;
-      }
-    }
-    catch (...)
-    {
-      return 0;
-    }
-  }
-
-
-  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->Step();
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
-    {
-      return OrthancPluginJobStepStatus_Failure;
-    }
-    catch (...)
-    {
-      return OrthancPluginJobStepStatus_Failure;
-    }
-  }
-
-
-  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
-                                                  OrthancPluginJobStopReason reason)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-
-
-  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      reinterpret_cast<OrthancJob*>(job)->Reset();
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-
-
-  void OrthancJob::ClearContent()
-  {
-    Json::Value empty = Json::objectValue;
-    UpdateContent(empty);
-  }
-
-
-  void OrthancJob::UpdateContent(const Json::Value& content)
-  {
-    if (content.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      Json::FastWriter writer;
-      content_ = writer.write(content);
-    }
-  }
-
-
-  void OrthancJob::ClearSerialized()
-  {
-    hasSerialized_ = false;
-    serialized_.clear();
-  }
-
-
-  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
-  {
-    if (serialized.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      Json::FastWriter writer;
-      serialized_ = writer.write(serialized);
-      hasSerialized_ = true;
-    }
-  }
-
-
-  void OrthancJob::UpdateProgress(float progress)
-  {
-    if (progress < 0 ||
-        progress > 1)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    progress_ = progress;
-  }
-
-
-  OrthancJob::OrthancJob(const std::string& jobType) :
-    jobType_(jobType),
-    progress_(0)
-  {
-    ClearContent();
-    ClearSerialized();
-  }
-
-
-  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
-  {
-    if (job == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
-    }
-
-    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
-      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
-      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
-      CallbackStep, CallbackStop, CallbackReset);
-
-    if (orthanc == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      return orthanc;
-    }
-  }
-
-
-  std::string OrthancJob::Submit(OrthancJob* job,
-                                 int priority)
-  {
-    if (job == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
-    }
-
-    OrthancPluginJob* orthanc = Create(job);
-
-    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
-
-    if (id == NULL)
-    {
-      LogError("Plugin cannot submit job");
-      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      std::string tmp(id);
-      tmp.assign(id);
-      OrthancPluginFreeString(GetGlobalContext(), id);
-
-      return tmp;
-    }
-  }
-
-
-  void OrthancJob::SubmitAndWait(Json::Value& result,
-                                 OrthancJob* job /* takes ownership */,
-                                 int priority)
-  {
-    std::string id = Submit(job, priority);
-
-    for (;;)
-    {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
-      Json::Value status;
-      if (!RestApiGet(status, "/jobs/" + id, false) ||
-          !status.isMember("State") ||
-          status["State"].type() != Json::stringValue)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
-      }
-
-      const std::string state = status["State"].asString();
-      if (state == "Success")
-      {
-        if (status.isMember("Content"))
-        {
-          result = status["Content"];
-        }
-        else
-        {
-          result = Json::objectValue;
-        }
-
-        return;
-      }
-      else if (state == "Running")
-      {
-        continue;
-      }
-      else if (!status.isMember("ErrorCode") ||
-               status["ErrorCode"].type() != Json::intValue)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
-      }
-      else
-      {
-        if (!status.isMember("ErrorDescription") ||
-            status["ErrorDescription"].type() != Json::stringValue)
-        {
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
-        }
-        else
-        {
-#if HAS_ORTHANC_EXCEPTION == 1
-          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
-                                          status["ErrorDescription"].asString());
-#else
-          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
-#endif
-        }
-      }
-    }
-  }
-
-
-  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
-                                         const Json::Value& body,
-                                         OrthancJob* job)
-  {
-    static const char* KEY_SYNCHRONOUS = "Synchronous";
-    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
-    static const char* KEY_PRIORITY = "Priority";
-
-    boost::movelib::unique_ptr<OrthancJob> protection(job);
-  
-    if (body.type() != Json::objectValue)
-    {
-#if HAS_ORTHANC_EXCEPTION == 1
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                      "Expected a JSON object in the body");
-#else
-      LogError("Expected a JSON object in the body");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-    }
-
-    bool synchronous = true;
-  
-    if (body.isMember(KEY_SYNCHRONOUS))
-    {
-      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
-                                        "\" must be Boolean");
-#else
-        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-      }
-      else
-      {
-        synchronous = body[KEY_SYNCHRONOUS].asBool();
-      }
-    }
-
-    if (body.isMember(KEY_ASYNCHRONOUS))
-    {
-      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
-                                        "\" must be Boolean");
-#else
-        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-      }
-      else
-      {
-        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
-      }
-    }
-
-    int priority = 0;
-
-    if (body.isMember(KEY_PRIORITY))
-    {
-      if (body[KEY_PRIORITY].type() != Json::booleanValue)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                        "Option \"" + std::string(KEY_PRIORITY) +
-                                        "\" must be an integer");
-#else
-        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-#endif
-      }
-      else
-      {
-        priority = !body[KEY_PRIORITY].asInt();
-      }
-    }
-  
-    Json::Value result;
-
-    if (synchronous)
-    {
-      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
-    }
-    else
-    {
-      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
-
-      result = Json::objectValue;
-      result["ID"] = id;
-      result["Path"] = "/jobs/" + id;
-    }
-
-    std::string s = result.toStyledString();
-    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
-                              s.size(), "application/json");
-  }
-
-#endif
-
-
-
-
-  /******************************************************************
-   ** METRICS
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_METRICS == 1
-  MetricsTimer::MetricsTimer(const char* name) :
-    name_(name)
-  {
-    start_ = boost::posix_time::microsec_clock::universal_time();
-  }
-  
-  MetricsTimer::~MetricsTimer()
-  {
-    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
-    const boost::posix_time::time_duration diff = stop - start_;
-    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
-                                 OrthancPluginMetricsType_Timer);
-  }
-#endif
-
-
-
-
-  /******************************************************************
-   ** HTTP CLIENT
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
-  class HttpClient::RequestBodyWrapper : public boost::noncopyable
-  {
-  private:
-    static RequestBodyWrapper& GetObject(void* body)
-    {
-      assert(body != NULL);
-      return *reinterpret_cast<RequestBodyWrapper*>(body);
-    }
-
-    IRequestBody&  body_;
-    bool           done_;
-    std::string    chunk_;
-
-  public:
-    RequestBodyWrapper(IRequestBody& body) :
-      body_(body),
-      done_(false)
-    {
-    }      
-
-    static uint8_t IsDone(void* body)
-    {
-      return GetObject(body).done_;
-    }
-    
-    static const void* GetChunkData(void* body)
-    {
-      return GetObject(body).chunk_.c_str();
-    }
-    
-    static uint32_t GetChunkSize(void* body)
-    {
-      return static_cast<uint32_t>(GetObject(body).chunk_.size());
-    }
-
-    static OrthancPluginErrorCode Next(void* body)
-    {
-      RequestBodyWrapper& that = GetObject(body);
-        
-      if (that.done_)
-      {
-        return OrthancPluginErrorCode_BadSequenceOfCalls;
-      }
-      else
-      {
-        try
-        {
-          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
-          return OrthancPluginErrorCode_Success;
-        }
-        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-        {
-          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-        }
-        catch (...)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-      }
-    }    
-  };
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
-                                                        const char* key,
-                                                        const char* value)
-  {
-    assert(answer != NULL && key != NULL && value != NULL);
-
-    try
-    {
-      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
-                                                       const void* data,
-                                                       uint32_t size)
-  {
-    assert(answer != NULL);
-
-    try
-    {
-      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-#endif
-
-
-  HttpClient::HttpClient() :
-    httpStatus_(0),
-    method_(OrthancPluginHttpMethod_Get),
-    timeout_(0),
-    pkcs11_(false),
-    chunkedBody_(NULL),
-    allowChunkedTransfers_(true)
-  {
-  }
-
-
-  void HttpClient::AddHeaders(const HttpHeaders& headers)
-  {
-    for (HttpHeaders::const_iterator it = headers.begin();
-         it != headers.end(); ++it)
-    {
-      headers_[it->first] = it->second;
-    }
-  }
-
-  
-  void HttpClient::SetCredentials(const std::string& username,
-                                  const std::string& password)
-  {
-    username_ = username;
-    password_ = password;
-  }
-
-  
-  void HttpClient::ClearCredentials()
-  {
-    username_.empty();
-    password_.empty();
-  }
-
-
-  void HttpClient::SetCertificate(const std::string& certificateFile,
-                                  const std::string& keyFile,
-                                  const std::string& keyPassword)
-  {
-    certificateFile_ = certificateFile;
-    certificateKeyFile_ = keyFile;
-    certificateKeyPassword_ = keyPassword;
-  }
-
-  
-  void HttpClient::ClearCertificate()
-  {
-    certificateFile_.clear();
-    certificateKeyFile_.clear();
-    certificateKeyPassword_.clear();
-  }
-
-
-  void HttpClient::ClearBody()
-  {
-    fullBody_.clear();
-    chunkedBody_ = NULL;
-  }
-
-  
-  void HttpClient::SwapBody(std::string& body)
-  {
-    fullBody_.swap(body);
-    chunkedBody_ = NULL;
-  }
-
-  
-  void HttpClient::SetBody(const std::string& body)
-  {
-    fullBody_ = body;
-    chunkedBody_ = NULL;
-  }
-
-  
-  void HttpClient::SetBody(IRequestBody& body)
-  {
-    fullBody_.clear();
-    chunkedBody_ = &body;
-  }
-
-
-  namespace
-  {
-    class HeadersWrapper : public boost::noncopyable
-    {
-    private:
-      std::vector<const char*>  headersKeys_;
-      std::vector<const char*>  headersValues_;
-
-    public:
-      HeadersWrapper(const HttpClient::HttpHeaders& headers)
-      {
-        headersKeys_.reserve(headers.size());
-        headersValues_.reserve(headers.size());
-
-        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
-        {
-          headersKeys_.push_back(it->first.c_str());
-          headersValues_.push_back(it->second.c_str());
-        }
-      }
-
-      void AddStaticString(const char* key,
-                           const char* value)
-      {
-        headersKeys_.push_back(key);
-        headersValues_.push_back(value);
-      }
-
-      uint32_t GetCount() const
-      {
-        return headersKeys_.size();
-      }
-
-      const char* const* GetKeys() const
-      {
-        return headersKeys_.empty() ? NULL : &headersKeys_[0];
-      }
-
-      const char* const* GetValues() const
-      {
-        return headersValues_.empty() ? NULL : &headersValues_[0];
-      }
-    };
-
-
-    class MemoryRequestBody : public HttpClient::IRequestBody
-    {
-    private:
-      std::string  body_;
-      bool         done_;
-
-    public:
-      MemoryRequestBody(const std::string& body) :
-        body_(body),
-        done_(false)
-      {
-        if (body_.empty())
-        {
-          done_ = true;
-        }
-      }
-
-      virtual bool ReadNextChunk(std::string& chunk)
-      {
-        if (done_)
-        {
-          return false;
-        }
-        else
-        {
-          chunk.swap(body_);
-          done_ = true;
-          return true;
-        }
-      }
-    };
-
-
-    // This class mimics Orthanc::ChunkedBuffer
-    class ChunkedBuffer : public boost::noncopyable
-    {
-    private:
-      typedef std::list<std::string*>  Content;
-
-      Content  content_;
-      size_t   size_;
-
-    public:
-      ChunkedBuffer() :
-        size_(0)
-      {
-      }
-
-      ~ChunkedBuffer()
-      {
-        Clear();
-      }
-
-      void Clear()
-      {
-        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
-        {
-          assert(*it != NULL);
-          delete *it;
-        }
-
-        content_.clear();
-      }
-
-      void Flatten(std::string& target) const
-      {
-        target.resize(size_);
-
-        size_t pos = 0;
-
-        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
-        {
-          assert(*it != NULL);
-          size_t s = (*it)->size();
-
-          if (s != 0)
-          {
-            memcpy(&target[pos], (*it)->c_str(), s);
-            pos += s;
-          }
-        }
-
-        assert(size_ == 0 ||
-               pos == target.size());
-      }
-
-      void AddChunk(const void* data,
-                    size_t size)
-      {
-        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
-        size_ += size;
-      }
-
-      void AddChunk(const std::string& chunk)
-      {
-        content_.push_back(new std::string(chunk));
-        size_ += chunk.size();
-      }
-    };
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    class MemoryAnswer : public HttpClient::IAnswer
-    {
-    private:
-      HttpClient::HttpHeaders  headers_;
-      ChunkedBuffer            body_;
-
-    public:
-      const HttpClient::HttpHeaders& GetHeaders() const
-      {
-        return headers_;
-      }
-
-      const ChunkedBuffer& GetBody() const
-      {
-        return body_;
-      }
-
-      virtual void AddHeader(const std::string& key,
-                             const std::string& value)
-      {
-        headers_[key] = value;
-      }
-
-      virtual void AddChunk(const void* data,
-                            size_t size)
-      {
-        body_.AddChunk(data, size);
-      }
-    };
-#endif
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
-                                     IAnswer& answer,
-                                     IRequestBody& body) const
-  {
-    HeadersWrapper h(headers_);
-
-    if (method_ == OrthancPluginHttpMethod_Post ||
-        method_ == OrthancPluginHttpMethod_Put)
-    {
-      // Automatically set the "Transfer-Encoding" header if absent
-      bool found = false;
-
-      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
-      {
-        if (boost::iequals(it->first, "Transfer-Encoding"))
-        {
-          found = true;
-          break;
-        }
-      }
-
-      if (!found)
-      {
-        h.AddStaticString("Transfer-Encoding", "chunked");
-      }
-    }
-
-    RequestBodyWrapper request(body);
-        
-    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
-      GetGlobalContext(),
-      &answer,
-      AnswerAddChunkCallback,
-      AnswerAddHeaderCallback,
-      &httpStatus,
-      method_,
-      url_.c_str(),
-      h.GetCount(),
-      h.GetKeys(),
-      h.GetValues(),
-      &request,
-      RequestBodyWrapper::IsDone,
-      RequestBodyWrapper::GetChunkData,
-      RequestBodyWrapper::GetChunkSize,
-      RequestBodyWrapper::Next,
-      username_.empty() ? NULL : username_.c_str(),
-      password_.empty() ? NULL : password_.c_str(),
-      timeout_,
-      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
-      pkcs11_ ? 1 : 0);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-#endif    
-
-
-  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
-                                        HttpHeaders& answerHeaders,
-                                        std::string& answerBody,
-                                        const std::string& body) const
-  {
-    HeadersWrapper headers(headers_);
-
-    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
-
-    OrthancPluginErrorCode error = OrthancPluginHttpClient(
-      GetGlobalContext(),
-      *answerBodyBuffer,
-      *answerHeadersBuffer,
-      &httpStatus,
-      method_,
-      url_.c_str(),
-      headers.GetCount(),
-      headers.GetKeys(),
-      headers.GetValues(),
-      body.empty() ? NULL : body.c_str(),
-      body.size(),
-      username_.empty() ? NULL : username_.c_str(),
-      password_.empty() ? NULL : password_.c_str(),
-      timeout_,
-      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
-      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
-      pkcs11_ ? 1 : 0);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-
-    Json::Value v;
-    answerHeadersBuffer.ToJson(v);
-
-    if (v.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Value::Members members = v.getMemberNames();
-    answerHeaders.clear();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& h = v[members[i]];
-      if (h.type() != Json::stringValue)
-      {
-        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-      }
-      else
-      {
-        answerHeaders[members[i]] = h.asString();
-      }
-    }
-
-    answerBodyBuffer.ToString(answerBody);
-  }
-
-
-  void HttpClient::Execute(IAnswer& answer)
-  {
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    if (allowChunkedTransfers_)
-    {
-      if (chunkedBody_ != NULL)
-      {
-        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
-      }
-      else
-      {
-        MemoryRequestBody wrapper(fullBody_);
-        ExecuteWithStream(httpStatus_, answer, wrapper);
-      }
-
-      return;
-    }
-#endif
-    
-    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
-    // transfers are disabled. This results in higher memory usage
-    // (all chunks from the answer body are sent at once)
-
-    HttpHeaders answerHeaders;
-    std::string answerBody;
-    Execute(answerHeaders, answerBody);
-
-    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
-         it != answerHeaders.end(); ++it)
-    {
-      answer.AddHeader(it->first, it->second);      
-    }
-
-    if (!answerBody.empty())
-    {
-      answer.AddChunk(answerBody.c_str(), answerBody.size());
-    }
-  }
-
-
-  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
-                           std::string& answerBody /* out */)
-  {
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    if (allowChunkedTransfers_)
-    {
-      MemoryAnswer answer;
-      Execute(answer);
-      answerHeaders = answer.GetHeaders();
-      answer.GetBody().Flatten(answerBody);
-      return;
-    }
-#endif
-    
-    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
-    // transfers are disabled. This results in higher memory usage
-    // (all chunks from the request body are sent at once)
-
-    if (chunkedBody_ != NULL)
-    {
-      ChunkedBuffer buffer;
-      
-      std::string chunk;
-      while (chunkedBody_->ReadNextChunk(chunk))
-      {
-        buffer.AddChunk(chunk);
-      }
-
-      std::string body;
-      buffer.Flatten(body);
-
-      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
-    }
-    else
-    {
-      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
-    }
-  }
-
-
-  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
-                           Json::Value& answerBody /* out */)
-  {
-    std::string body;
-    Execute(answerHeaders, body);
-    
-    Json::Reader reader;
-    if (!reader.parse(body, answerBody))
-    {
-      LogError("Cannot convert HTTP answer body to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  void HttpClient::Execute()
-  {
-    HttpHeaders answerHeaders;
-    std::string body;
-    Execute(answerHeaders, body);
-  }
-
-#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
-
-
-
-
-
-  /******************************************************************
-   ** CHUNKED HTTP SERVER
-   ******************************************************************/
-
-  namespace Internals
-  {
-    void NullRestCallback(OrthancPluginRestOutput* output,
-                          const char* url,
-                          const OrthancPluginHttpRequest* request)
-    {
-    }
-  
-    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
-                                                   const OrthancPluginHttpRequest* request)
-    {
-      return NULL;
-    }
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
-
-    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
-      OrthancPluginServerChunkedRequestReader* reader,
-      const void*                              data,
-      uint32_t                                 size)
-    {
-      try
-      {
-        if (reader == NULL)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-
-        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-
-    
-    OrthancPluginErrorCode ChunkedRequestReaderExecute(
-      OrthancPluginServerChunkedRequestReader* reader,
-      OrthancPluginRestOutput*                 output)
-    {
-      try
-      {
-        if (reader == NULL)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-
-        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-
-    
-    void ChunkedRequestReaderFinalize(
-      OrthancPluginServerChunkedRequestReader* reader)
-    {
-      if (reader != NULL)
-      {
-        delete reinterpret_cast<IChunkedRequestReader*>(reader);
-      }
-    }
-
-#else
-    
-    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
-                                                    const char* url,
-                                                    const OrthancPluginHttpRequest* request,
-                                                    RestCallback         GetHandler,
-                                                    ChunkedRestCallback  PostHandler,
-                                                    RestCallback         DeleteHandler,
-                                                    ChunkedRestCallback  PutHandler)
-    {
-      try
-      {
-        std::string allowed;
-
-        if (GetHandler != Internals::NullRestCallback)
-        {
-          allowed += "GET";
-        }
-
-        if (PostHandler != Internals::NullChunkedRestCallback)
-        {
-          if (!allowed.empty())
-          {
-            allowed += ",";
-          }
-        
-          allowed += "POST";
-        }
-
-        if (DeleteHandler != Internals::NullRestCallback)
-        {
-          if (!allowed.empty())
-          {
-            allowed += ",";
-          }
-        
-          allowed += "DELETE";
-        }
-
-        if (PutHandler != Internals::NullChunkedRestCallback)
-        {
-          if (!allowed.empty())
-          {
-            allowed += ",";
-          }
-        
-          allowed += "PUT";
-        }
-      
-        switch (request->method)
-        {
-          case OrthancPluginHttpMethod_Get:
-            if (GetHandler == Internals::NullRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              GetHandler(output, url, request);
-            }
-
-            break;
-
-          case OrthancPluginHttpMethod_Post:
-            if (PostHandler == Internals::NullChunkedRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
-              if (reader.get() == NULL)
-              {
-                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-              }
-              else
-              {
-                reader->AddChunk(request->body, request->bodySize);
-                reader->Execute(output);
-              }
-            }
-
-            break;
-
-          case OrthancPluginHttpMethod_Delete:
-            if (DeleteHandler == Internals::NullRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              DeleteHandler(output, url, request);
-            }
-
-            break;
-
-          case OrthancPluginHttpMethod_Put:
-            if (PutHandler == Internals::NullChunkedRestCallback)
-            {
-              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
-            }
-            else
-            {
-              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
-              if (reader.get() == NULL)
-              {
-                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-              }
-              else
-              {
-                reader->AddChunk(request->body, request->bodySize);
-                reader->Execute(output);
-              }
-            }
-
-            break;
-
-          default:
-            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-        }
-
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
-        if (HasGlobalContext() &&
-            e.HasDetails())
-        {
-          // The "false" instructs Orthanc not to log the detailed
-          // error message. This is to avoid duplicating the details,
-          // because "OrthancException" already does it on construction.
-          OrthancPluginSetHttpErrorDetails
-            (GetGlobalContext(), output, e.GetDetails(), false);
-        }
-#endif
-
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-#endif
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
-  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
-    OrthancPluginStorageCommitmentFailureReason* target,
-    void* rawHandler,
-    const char* sopClassUid,
-    const char* sopInstanceUid)
-  {
-    assert(target != NULL &&
-           rawHandler != NULL);
-      
-    try
-    {
-      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
-      *target = handler.Lookup(sopClassUid, sopInstanceUid);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
-  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
-  {
-    assert(rawHandler != NULL);
-    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
-  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
-    toFree_(false),
-    instance_(instance)
-  {
-  }
-#else
-  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
-    toFree_(false),
-    instance_(instance)
-  {
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  DicomInstance::DicomInstance(const void* buffer,
-                               size_t size) :
-    toFree_(true),
-    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
-  {
-    if (instance_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
-    }
-  }
-#endif
-
-
-  DicomInstance::~DicomInstance()
-  {
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    if (toFree_ &&
-        instance_ != NULL)
-    {
-      OrthancPluginFreeDicomInstance(
-        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
-    }
-#endif
-  }
-
-  
-  std::string DicomInstance::GetRemoteAet() const
-  {
-    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
-    if (s == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      return std::string(s);
-    }
-  }
-
-
-  void DicomInstance::GetJson(Json::Value& target) const
-  {
-    OrthancString s;
-    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
-    s.ToJson(target);
-  }
-  
-
-  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
-  {
-    OrthancString s;
-    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
-    s.ToJson(target);
-  }
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-  std::string DicomInstance::GetTransferSyntaxUid() const
-  {
-    OrthancString s;
-    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
-
-    std::string result;
-    s.ToString(result);
-    return result;
-  }
-#endif
-
-  
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-  bool DicomInstance::HasPixelData() const
-  {
-    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
-    if (result < 0)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      return (result != 0);
-    }
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
-  void DicomInstance::GetRawFrame(std::string& target,
-                                  unsigned int frameIndex) const
-  {
-    MemoryBuffer buffer;
-    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
-      GetGlobalContext(), *buffer, instance_, frameIndex);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      buffer.ToString(target);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
-  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
-  {
-    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
-      GetGlobalContext(), instance_, frameIndex);
-
-    if (image == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      return new OrthancImage(image);
-    }
-  }
-#endif  
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  void DicomInstance::Serialize(std::string& target) const
-  {
-    MemoryBuffer buffer;
-    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
-      GetGlobalContext(), *buffer, instance_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      buffer.ToString(target);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
-  }
-#endif
-  
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  DicomInstance* DicomInstance::Transcode(const void* buffer,
-                                          size_t size,
-                                          const std::string& transferSyntax)
-  {
-    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
-      GetGlobalContext(), buffer, size, transferSyntax.c_str());
-
-    if (instance == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
-    }
-    else
-    {
-      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
-      result->toFree_ = true;
-      return result.release();
-    }
-  }
-#endif
-}
--- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1228 +0,0 @@
-/**
- * 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.
- * 
- * 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 "OrthancPluginException.h"
-
-#include <orthanc/OrthancCPlugin.h>
-#include <boost/noncopyable.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <json/value.h>
-#include <vector>
-#include <list>
-#include <set>
-#include <map>
-
-
-
-/**
- * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
- * backward compatibility with Orthanc SDK <= 1.3.0.
- * 
- *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
- *
- **/
-#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
-
-
-#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
-#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
-  (ORTHANC_VERSION_MAJOR > major ||                                     \
-   (ORTHANC_VERSION_MAJOR == major &&                                   \
-    (ORTHANC_VERSION_MINOR > minor ||                                   \
-     (ORTHANC_VERSION_MINOR == minor &&                                 \
-      ORTHANC_VERSION_REVISION >= revision))))
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
-// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
-#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
-#else
-#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
-#endif
-
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
-#  define HAS_ORTHANC_PLUGIN_PEERS  1
-#  define HAS_ORTHANC_PLUGIN_JOB    1
-#else
-#  define HAS_ORTHANC_PLUGIN_PEERS  0
-#  define HAS_ORTHANC_PLUGIN_JOB    0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
-#else
-#  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
-#  define HAS_ORTHANC_PLUGIN_METRICS  1
-#else
-#  define HAS_ORTHANC_PLUGIN_METRICS  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
-#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  1
-#else
-#  define HAS_ORTHANC_PLUGIN_HTTP_CLIENT  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  1
-#else
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
-#else
-#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
-#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
-#else
-#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
-#endif
-
-
-
-namespace OrthancPlugins
-{
-  typedef void (*RestCallback) (OrthancPluginRestOutput* output,
-                                const char* url,
-                                const OrthancPluginHttpRequest* request);
-
-  void SetGlobalContext(OrthancPluginContext* context);
-
-  bool HasGlobalContext();
-
-  OrthancPluginContext* GetGlobalContext();
-
-  
-  class OrthancImage;
-
-
-  class MemoryBuffer : public boost::noncopyable
-  {
-  private:
-    OrthancPluginMemoryBuffer  buffer_;
-
-    void Check(OrthancPluginErrorCode code);
-
-    bool CheckHttp(OrthancPluginErrorCode code);
-
-  public:
-    MemoryBuffer();
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    // This constructor makes a copy of the given buffer in the memory
-    // handled by the Orthanc core
-    MemoryBuffer(const void* buffer,
-                 size_t size);
-#endif
-
-    ~MemoryBuffer()
-    {
-      Clear();
-    }
-
-    OrthancPluginMemoryBuffer* operator*()
-    {
-      return &buffer_;
-    }
-
-    // This transfers ownership from "other" to "this"
-    void Assign(OrthancPluginMemoryBuffer& other);
-
-    void Swap(MemoryBuffer& other);
-
-    OrthancPluginMemoryBuffer Release();
-
-    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;
-    }
-
-    bool IsEmpty() const
-    {
-      return GetSize() == 0 || GetData() == NULL;
-    }
-
-    void Clear();
-
-    void ToString(std::string& target) const;
-
-    void ToJson(Json::Value& target) const;
-
-    bool RestApiGet(const std::string& uri,
-                    bool applyPlugins);
-
-    bool RestApiGet(const std::string& uri,
-                    const std::map<std::string, std::string>& httpHeaders,
-                    bool applyPlugins);
-
-    bool RestApiPost(const std::string& uri,
-                     const void* body,
-                     size_t bodySize,
-                     bool applyPlugins);
-
-    bool RestApiPut(const std::string& uri,
-                    const void* body,
-                    size_t bodySize,
-                    bool applyPlugins);
-
-    bool RestApiPost(const std::string& uri,
-                     const Json::Value& body,
-                     bool applyPlugins);
-
-    bool RestApiPut(const std::string& uri,
-                    const Json::Value& body,
-                    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);
-    }
-
-    void CreateDicom(const Json::Value& tags,
-                     OrthancPluginCreateDicomFlags flags);
-
-    void CreateDicom(const Json::Value& tags,
-                     const OrthancImage& pixelData,
-                     OrthancPluginCreateDicomFlags flags);
-
-    void ReadFile(const std::string& path);
-
-    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
-
-    void DicomToJson(Json::Value& target,
-                     OrthancPluginDicomToJsonFormat format,
-                     OrthancPluginDicomToJsonFlags flags,
-                     uint32_t maxStringLength);
-
-    bool HttpGet(const std::string& url,
-                 const std::string& username,
-                 const std::string& password);
-
-    bool HttpPost(const std::string& url,
-                  const std::string& body,
-                  const std::string& username,
-                  const std::string& password);
-
-    bool HttpPut(const std::string& url,
-                 const std::string& body,
-                 const std::string& username,
-                 const std::string& password);
-
-    void GetDicomInstance(const std::string& instanceId);
-  };
-
-
-  class OrthancString : public boost::noncopyable
-  {
-  private:
-    char*   str_;
-
-    void Clear();
-
-  public:
-    OrthancString() :
-      str_(NULL)
-    {
-    }
-
-    ~OrthancString()
-    {
-      Clear();
-    }
-
-    // This transfers ownership, warning: The string must have been
-    // allocated by the Orthanc core
-    void Assign(char* str);
-
-    const char* GetContent() const
-    {
-      return str_;
-    }
-
-    void ToString(std::string& target) const;
-
-    void ToJson(Json::Value& target) const;
-  };
-
-
-  class OrthancConfiguration : public boost::noncopyable
-  {
-  private:
-    Json::Value  configuration_;  // Necessarily a Json::objectValue
-    std::string  path_;
-
-    std::string GetPath(const std::string& key) const;
-
-    void LoadConfiguration();
-    
-  public:
-    OrthancConfiguration();
-
-    explicit OrthancConfiguration(bool load);
-
-    const Json::Value& GetJson() const
-    {
-      return configuration_;
-    }
-
-    bool IsSection(const std::string& key) const;
-
-    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;
-
-    bool LookupListOfStrings(std::list<std::string>& target,
-                             const std::string& key,
-                             bool allowSingleString) const;
-
-    bool LookupSetOfStrings(std::set<std::string>& target,
-                            const std::string& key,
-                            bool allowSingleString) 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;
-
-    void GetDictionary(std::map<std::string, std::string>& target,
-                       const std::string& key) const;
-  };
-
-  class OrthancImage : public boost::noncopyable
-  {
-  private:
-    OrthancPluginImage*    image_;
-
-    void Clear();
-
-    void CheckImageAvailable() const;
-
-  public:
-    OrthancImage();
-
-    explicit OrthancImage(OrthancPluginImage* image);
-
-    OrthancImage(OrthancPluginPixelFormat  format,
-                 uint32_t                  width,
-                 uint32_t                  height);
-
-    OrthancImage(OrthancPluginPixelFormat  format,
-                 uint32_t                  width,
-                 uint32_t                  height,
-                 uint32_t                  pitch,
-                 void*                     buffer);
-
-    ~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() const;
-
-    unsigned int GetWidth() const;
-
-    unsigned int GetHeight() const;
-
-    unsigned int GetPitch() const;
-    
-    void* GetBuffer() const;
-
-    const OrthancPluginImage* GetObject() const
-    {
-      return image_;
-    }
-
-    void CompressPngImage(MemoryBuffer& target) const;
-
-    void CompressJpegImage(MemoryBuffer& target,
-                           uint8_t quality) const;
-
-    void AnswerPngImage(OrthancPluginRestOutput* output) const;
-
-    void AnswerJpegImage(OrthancPluginRestOutput* output,
-                         uint8_t quality) const;
-    
-    void* GetWriteableBuffer();
-
-    OrthancPluginImage* Release();
-  };
-
-
-#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
-  class FindMatcher : public boost::noncopyable
-  {
-  private:
-    OrthancPluginFindMatcher*          matcher_;
-    const OrthancPluginWorklistQuery*  worklist_;
-
-    void SetupDicom(const void*            query,
-                    uint32_t               size);
-
-  public:
-    explicit FindMatcher(const OrthancPluginWorklistQuery*  worklist);
-
-    FindMatcher(const void*  query,
-                uint32_t     size)
-    {
-      SetupDicom(query, size);
-    }
-
-    explicit FindMatcher(const MemoryBuffer&  dicom)
-    {
-      SetupDicom(dicom.GetData(), dicom.GetSize());
-    }
-
-    ~FindMatcher();
-
-    bool IsMatch(const void*  dicom,
-                 uint32_t     size) const;
-
-    bool IsMatch(const MemoryBuffer& dicom) const
-    {
-      return IsMatch(dicom.GetData(), dicom.GetSize());
-    }
-  };
-#endif
-
-
-  bool RestApiGet(Json::Value& result,
-                  const std::string& uri,
-                  bool applyPlugins);
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        bool applyPlugins);
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        const std::map<std::string, std::string>& httpHeaders,
-                        bool applyPlugins);
-
-  bool RestApiPost(std::string& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins);
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const void* body,
-                   size_t bodySize,
-                   bool applyPlugins);
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const Json::Value& body,
-                   bool applyPlugins);
-
-  inline bool RestApiPost(Json::Value& result,
-                          const std::string& uri,
-                          const std::string& body,
-                          bool applyPlugins)
-  {
-    return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
-                       body.size(), applyPlugins);
-  }
-
-  inline bool RestApiPost(Json::Value& result,
-                          const std::string& uri,
-                          const MemoryBuffer& body,
-                          bool applyPlugins)
-  {
-    return RestApiPost(result, uri, body.GetData(),
-                       body.GetSize(), applyPlugins);
-  }
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const void* body,
-                  size_t bodySize,
-                  bool applyPlugins);
-
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const Json::Value& body,
-                  bool applyPlugins);
-
-  inline bool RestApiPut(Json::Value& result,
-                         const std::string& uri,
-                         const std::string& body,
-                         bool applyPlugins)
-  {
-    return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
-                      body.size(), applyPlugins);
-  }
-
-  bool RestApiDelete(const std::string& uri,
-                     bool applyPlugins);
-
-  bool HttpDelete(const std::string& url,
-                  const std::string& username,
-                  const std::string& password);
-
-  void AnswerJson(const Json::Value& value,
-                  OrthancPluginRestOutput* output);
-
-  void AnswerString(const std::string& answer,
-                    const char* mimeType,
-                    OrthancPluginRestOutput* output);
-
-  void AnswerHttpError(uint16_t httpError,
-                       OrthancPluginRestOutput* output);
-
-  void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-  const char* AutodetectMimeType(const std::string& path);
-#endif
-
-  void LogError(const std::string& message);
-
-  void LogWarning(const std::string& message);
-
-  void LogInfo(const std::string& message);
-
-  void ReportMinimalOrthancVersion(unsigned int major,
-                                   unsigned int minor,
-                                   unsigned int revision);
-  
-  bool CheckMinimalOrthancVersion(unsigned int major,
-                                  unsigned int minor,
-                                  unsigned int revision);
-
-
-  namespace Internals
-  {
-    template <RestCallback Callback>
-    static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
-                                          const char* url,
-                                          const OrthancPluginHttpRequest* request)
-    {
-      try
-      {
-        Callback(output, url, request);
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
-        if (HasGlobalContext() &&
-            e.HasDetails())
-        {
-          // The "false" instructs Orthanc not to log the detailed
-          // error message. This is to avoid duplicating the details,
-          // because "OrthancException" already does it on construction.
-          OrthancPluginSetHttpErrorDetails
-            (GetGlobalContext(), output, e.GetDetails(), false);
-        }
-#endif
-
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-  }
-
-  
-  template <RestCallback Callback>
-  void RegisterRestCallback(const std::string& uri,
-                            bool isThreadSafe)
-  {
-    if (isThreadSafe)
-    {
-      OrthancPluginRegisterRestCallbackNoLock
-        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
-    }
-    else
-    {
-      OrthancPluginRegisterRestCallback
-        (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
-    }
-  }
-
-
-#if HAS_ORTHANC_PLUGIN_PEERS == 1
-  class OrthancPeers : public boost::noncopyable
-  {
-  private:
-    typedef std::map<std::string, uint32_t>   Index;
-
-    OrthancPluginPeers   *peers_;
-    Index                 index_;
-    uint32_t              timeout_;
-
-    size_t GetPeerIndex(const std::string& name) const;
-
-  public:
-    OrthancPeers();
-
-    ~OrthancPeers();
-
-    uint32_t GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    void SetTimeout(uint32_t timeout)
-    {
-      timeout_ = timeout;
-    }
-
-    bool LookupName(size_t& target,
-                    const std::string& name) const;
-
-    std::string GetPeerName(size_t index) const;
-
-    std::string GetPeerUrl(size_t index) const;
-
-    std::string GetPeerUrl(const std::string& name) const;
-
-    size_t GetPeersCount() const
-    {
-      return index_.size();
-    }
-
-    bool LookupUserProperty(std::string& value,
-                            size_t index,
-                            const std::string& key) const;
-
-    bool LookupUserProperty(std::string& value,
-                            const std::string& peer,
-                            const std::string& key) const;
-
-    bool DoGet(MemoryBuffer& target,
-               size_t index,
-               const std::string& uri) const;
-
-    bool DoGet(MemoryBuffer& target,
-               const std::string& name,
-               const std::string& uri) const;
-
-    bool DoGet(Json::Value& target,
-               size_t index,
-               const std::string& uri) const;
-
-    bool DoGet(Json::Value& target,
-               const std::string& name,
-               const std::string& uri) const;
-
-    bool DoPost(MemoryBuffer& target,
-                size_t index,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPost(MemoryBuffer& target,
-                const std::string& name,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPost(Json::Value& target,
-                size_t index,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPost(Json::Value& target,
-                const std::string& name,
-                const std::string& uri,
-                const std::string& body) const;
-
-    bool DoPut(size_t index,
-               const std::string& uri,
-               const std::string& body) const;
-
-    bool DoPut(const std::string& name,
-               const std::string& uri,
-               const std::string& body) const;
-
-    bool DoDelete(size_t index,
-                  const std::string& uri) const;
-
-    bool DoDelete(const std::string& name,
-                  const std::string& uri) const;
-  };
-#endif
-
-
-
-#if HAS_ORTHANC_PLUGIN_JOB == 1
-  class OrthancJob : public boost::noncopyable
-  {
-  private:
-    std::string   jobType_;
-    std::string   content_;
-    bool          hasSerialized_;
-    std::string   serialized_;
-    float         progress_;
-
-    static void CallbackFinalize(void* job);
-
-    static float CallbackGetProgress(void* job);
-
-    static const char* CallbackGetContent(void* job);
-
-    static const char* CallbackGetSerialized(void* job);
-
-    static OrthancPluginJobStepStatus CallbackStep(void* job);
-
-    static OrthancPluginErrorCode CallbackStop(void* job,
-                                               OrthancPluginJobStopReason reason);
-
-    static OrthancPluginErrorCode CallbackReset(void* job);
-
-  protected:
-    void ClearContent();
-
-    void UpdateContent(const Json::Value& content);
-
-    void ClearSerialized();
-
-    void UpdateSerialized(const Json::Value& serialized);
-
-    void UpdateProgress(float progress);
-    
-  public:
-    OrthancJob(const std::string& jobType);
-    
-    virtual ~OrthancJob()
-    {
-    }
-
-    virtual OrthancPluginJobStepStatus Step() = 0;
-
-    virtual void Stop(OrthancPluginJobStopReason reason) = 0;
-    
-    virtual void Reset() = 0;
-
-    static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
-
-    static std::string Submit(OrthancJob* job /* takes ownership */,
-                              int priority);
-
-    static void SubmitAndWait(Json::Value& result,
-                              OrthancJob* job /* takes ownership */,
-                              int priority);
-
-    // Submit a job from a POST on the REST API with the same
-    // conventions as in the Orthanc core (according to the
-    // "Synchronous" and "Priority" options)
-    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
-                                      const Json::Value& body,
-                                      OrthancJob* job);
-  };
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_METRICS == 1
-  inline void SetMetricsValue(char* name,
-                              float value)
-  {
-    OrthancPluginSetMetricsValue(GetGlobalContext(), name,
-                                 value, OrthancPluginMetricsType_Default);
-  }
-
-  class MetricsTimer : public boost::noncopyable
-  {
-  private:
-    std::string               name_;
-    boost::posix_time::ptime  start_;
-
-  public:
-    explicit MetricsTimer(const char* name);
-
-    ~MetricsTimer();
-  };
-#endif
-
-
-#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
-  class HttpClient : public boost::noncopyable
-  {
-  public:
-    typedef std::map<std::string, std::string>  HttpHeaders;
-
-    class IRequestBody : public boost::noncopyable
-    {
-    public:
-      virtual ~IRequestBody()
-      {
-      }
-
-      virtual bool ReadNextChunk(std::string& chunk) = 0;
-    };
-
-
-    class IAnswer : public boost::noncopyable
-    {
-    public:
-      virtual ~IAnswer()
-      {
-      }
-
-      virtual void AddHeader(const std::string& key,
-                             const std::string& value) = 0;
-
-      virtual void AddChunk(const void* data,
-                            size_t size) = 0;
-    };
-
-
-  private:
-    class RequestBodyWrapper;
-
-    uint16_t                 httpStatus_;
-    OrthancPluginHttpMethod  method_;
-    std::string              url_;
-    HttpHeaders              headers_;
-    std::string              username_;
-    std::string              password_;
-    uint32_t                 timeout_;
-    std::string              certificateFile_;
-    std::string              certificateKeyFile_;
-    std::string              certificateKeyPassword_;
-    bool                     pkcs11_;
-    std::string              fullBody_;
-    IRequestBody*            chunkedBody_;
-    bool                     allowChunkedTransfers_;
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
-    void ExecuteWithStream(uint16_t& httpStatus,  // out
-                           IAnswer& answer,       // out
-                           IRequestBody& body) const;
-#endif
-
-    void ExecuteWithoutStream(uint16_t& httpStatus,        // out
-                              HttpHeaders& answerHeaders,  // out
-                              std::string& answerBody,     // out
-                              const std::string& body) const;
-    
-  public:
-    HttpClient();
-
-    uint16_t GetHttpStatus() const
-    {
-      return httpStatus_;
-    }
-
-    void SetMethod(OrthancPluginHttpMethod method)
-    {
-      method_ = method;
-    }
-
-    const std::string& GetUrl() const
-    {
-      return url_;
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    void SetHeaders(const HttpHeaders& headers)
-    {
-      headers_ = headers;
-    }
-
-    void AddHeader(const std::string& key,
-                   const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    void AddHeaders(const HttpHeaders& headers);
-
-    void SetCredentials(const std::string& username,
-                        const std::string& password);
-
-    void ClearCredentials();
-
-    void SetTimeout(unsigned int timeout)  // 0 for default timeout
-    {
-      timeout_ = timeout;
-    }
-
-    void SetCertificate(const std::string& certificateFile,
-                        const std::string& keyFile,
-                        const std::string& keyPassword);
-
-    void ClearCertificate();
-
-    void SetPkcs11(bool pkcs11)
-    {
-      pkcs11_ = pkcs11;
-    }
-
-    void ClearBody();
-
-    void SwapBody(std::string& body);
-
-    void SetBody(const std::string& body);
-
-    void SetBody(IRequestBody& body);
-
-    // This function can be used to disable chunked transfers if the
-    // remote server is Orthanc with a version <= 1.5.6.
-    void SetChunkedTransfersAllowed(bool allow)
-    {
-      allowChunkedTransfers_ = allow;
-    }
-
-    bool IsChunkedTransfersAllowed() const
-    {
-      return allowChunkedTransfers_;
-    }
-
-    void Execute(IAnswer& answer);
-
-    void Execute(HttpHeaders& answerHeaders /* out */,
-                 std::string& answerBody /* out */);
-
-    void Execute(HttpHeaders& answerHeaders /* out */,
-                 Json::Value& answerBody /* out */);
-
-    void Execute();
-  };
-#endif
-
-
-
-  class IChunkedRequestReader : public boost::noncopyable
-  {
-  public:
-    virtual ~IChunkedRequestReader()
-    {
-    }
-
-    virtual void AddChunk(const void* data,
-                          size_t size) = 0;
-
-    virtual void Execute(OrthancPluginRestOutput* output) = 0;
-  };
-
-
-  typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
-                                                         const OrthancPluginHttpRequest* request);
-
-
-  namespace Internals
-  {
-    void NullRestCallback(OrthancPluginRestOutput* output,
-                          const char* url,
-                          const OrthancPluginHttpRequest* request);
-  
-    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
-                                                   const OrthancPluginHttpRequest* request);
-
-
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
-    template <ChunkedRestCallback Callback>
-    static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
-                                                const char* url,
-                                                const OrthancPluginHttpRequest* request)
-    {
-      try
-      {
-        if (reader == NULL)
-        {
-          return OrthancPluginErrorCode_InternalError;
-        }
-        else
-        {
-          *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
-          if (*reader == NULL)
-          {
-            return OrthancPluginErrorCode_Plugin;
-          }
-          else
-          {
-            return OrthancPluginErrorCode_Success;
-          }
-        }
-      }
-      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-      {
-        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return OrthancPluginErrorCode_BadFileFormat;
-      }
-      catch (...)
-      {
-        return OrthancPluginErrorCode_Plugin;
-      }
-    }
-
-    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
-      OrthancPluginServerChunkedRequestReader* reader,
-      const void*                              data,
-      uint32_t                                 size);
-
-    OrthancPluginErrorCode ChunkedRequestReaderExecute(
-      OrthancPluginServerChunkedRequestReader* reader,
-      OrthancPluginRestOutput*                 output);
-
-    void ChunkedRequestReaderFinalize(
-      OrthancPluginServerChunkedRequestReader* reader);
-
-#else  
-
-    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
-                                                    const char* url,
-                                                    const OrthancPluginHttpRequest* request,
-                                                    RestCallback GetHandler,
-                                                    ChunkedRestCallback PostHandler,
-                                                    RestCallback DeleteHandler,
-                                                    ChunkedRestCallback PutHandler);
-
-    template<
-      RestCallback         GetHandler,
-      ChunkedRestCallback  PostHandler,
-      RestCallback         DeleteHandler,
-      ChunkedRestCallback  PutHandler
-      >
-    inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
-                                                           const char* url,
-                                                           const OrthancPluginHttpRequest* request)
-    {
-      return ChunkedRestCompatibility(output, url, request, GetHandler,
-                                      PostHandler, DeleteHandler, PutHandler);
-    }
-#endif
-  }
-
-
-
-  // NB: We use a templated class instead of a templated function, because
-  // default values are only available in functions since C++11
-  template<
-    RestCallback         GetHandler    = Internals::NullRestCallback,
-    ChunkedRestCallback  PostHandler   = Internals::NullChunkedRestCallback,
-    RestCallback         DeleteHandler = Internals::NullRestCallback,
-    ChunkedRestCallback  PutHandler    = Internals::NullChunkedRestCallback
-    >
-  class ChunkedRestRegistration : public boost::noncopyable
-  {
-  public:
-    static void Apply(const std::string& uri)
-    {
-#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
-      OrthancPluginRegisterChunkedRestCallback(
-        GetGlobalContext(), uri.c_str(),
-        GetHandler == Internals::NullRestCallback         ? NULL : Internals::Protect<GetHandler>,
-        PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
-        DeleteHandler == Internals::NullRestCallback      ? NULL : Internals::Protect<DeleteHandler>,
-        PutHandler == Internals::NullChunkedRestCallback  ? NULL : Internals::ChunkedProtect<PutHandler>,
-        Internals::ChunkedRequestReaderAddChunk,
-        Internals::ChunkedRequestReaderExecute,
-        Internals::ChunkedRequestReaderFinalize);
-#else
-      OrthancPluginRegisterRestCallbackNoLock(
-        GetGlobalContext(), uri.c_str(), 
-        Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
-#endif
-    }
-  };
-
-  
-
-#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
-  class IStorageCommitmentScpHandler : public boost::noncopyable
-  {
-  public:
-    virtual ~IStorageCommitmentScpHandler()
-    {
-    }
-    
-    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
-                                                               const std::string& sopInstanceUid) = 0;
-    
-    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
-                                         void* rawHandler,
-                                         const char* sopClassUid,
-                                         const char* sopInstanceUid);
-
-    static void Destructor(void* rawHandler);
-  };
-#endif
-
-
-  class DicomInstance : public boost::noncopyable
-  {
-  private:
-    bool toFree_;
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
-    const OrthancPluginDicomInstance*  instance_;
-#else
-    OrthancPluginDicomInstance*  instance_;
-#endif
-    
-  public:
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
-    explicit DicomInstance(const OrthancPluginDicomInstance* instance);
-#else
-    explicit DicomInstance(OrthancPluginDicomInstance* instance);
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    DicomInstance(const void* buffer,
-                  size_t size);
-#endif
-
-    ~DicomInstance();
-
-    std::string GetRemoteAet() const;
-
-    const void* GetBuffer() const
-    {
-      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
-    }
-
-    size_t GetSize() const
-    {
-      return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
-    }
-
-    void GetJson(Json::Value& target) const;
-
-    void GetSimplifiedJson(Json::Value& target) const;
-
-    OrthancPluginInstanceOrigin GetOrigin() const
-    {
-      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
-    }
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-    std::string GetTransferSyntaxUid() const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
-    bool HasPixelData() const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    unsigned int GetFramesCount() const
-    {
-      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
-    }
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    void GetRawFrame(std::string& target,
-                     unsigned int frameIndex) const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    void Serialize(std::string& target) const;
-#endif
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    static DicomInstance* Transcode(const void* buffer,
-                                    size_t size,
-                                    const std::string& transferSyntax);
-#endif
-  };
-}
--- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/**
- * 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.
- * 
- * 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(HAS_ORTHANC_EXCEPTION)
-#  error The macro HAS_ORTHANC_EXCEPTION must be defined
-#endif
-
-
-#if HAS_ORTHANC_EXCEPTION == 1
-#  include <OrthancException.h>
-#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
-#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
-#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
-#else
-#  include <orthanc/OrthancCPlugin.h>
-#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
-#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
-#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
-#endif
-
-
-#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
-  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
-
-
-#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
-  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
-                                                  
-
-#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
-  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
-  {                                                                 \
-    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
-  }
-
-
-namespace OrthancPlugins
-{
-#if HAS_ORTHANC_EXCEPTION == 0
-  class PluginException
-  {
-  private:
-    OrthancPluginErrorCode  code_;
-
-  public:
-    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
-    {
-    }
-
-    OrthancPluginErrorCode GetErrorCode() const
-    {
-      return code_;
-    }
-
-    const char* What(OrthancPluginContext* context) const
-    {
-      const char* description = OrthancPluginGetErrorDescription(context, code_);
-      if (description)
-      {
-        return description;
-      }
-      else
-      {
-        return "No description available";
-      }
-    }
-  };
-#endif
-}
--- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-# 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.
-# 
-# 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/>.
-
-
-# In Orthanc <= 1.7.1, the instructions below were part of
-# "Compiler.cmake", and were protected by the (now unused) option
-# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map")
-elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list")
-endif()
--- a/StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map	Tue Aug 11 12:47:59 2020 +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/StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4740 +0,0 @@
-/**
- * \mainpage
- *
- * This C/C++ SDK allows external developers to create plugins that
- * can be loaded into Orthanc to extend its functionality. Each
- * Orthanc plugin must expose 4 public functions with the following
- * signatures:
- * 
- * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
- *    This function is invoked by Orthanc when it loads the plugin on startup.
- *    The plugin must:
- *    - Check its compatibility with the Orthanc version using
- *      ::OrthancPluginCheckVersion().
- *    - Store the context pointer so that it can use the plugin 
- *      services of Orthanc.
- *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
- *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
- *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
- *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
- *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
- *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
- *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
- * -# <tt>void OrthancPluginFinalize()</tt>:
- *    This function is invoked by Orthanc during its shutdown. The plugin
- *    must free all its memory.
- * -# <tt>const char* OrthancPluginGetName()</tt>:
- *    The plugin must return a short string to identify itself.
- * -# <tt>const char* OrthancPluginGetVersion()</tt>:
- *    The plugin must return a string containing its version number.
- *
- * The name and the version of a plugin is only used to prevent it
- * from being loaded twice. Note that, in C++, it is mandatory to
- * declare these functions within an <tt>extern "C"</tt> section.
- * 
- * To ensure multi-threading safety, the various REST callbacks are
- * guaranteed to be executed in mutual exclusion since Orthanc
- * 0.8.5. If this feature is undesired (notably when developing
- * high-performance plugins handling simultaneous requests), use
- * ::OrthancPluginRegisterRestCallbackNoLock().
- **/
-
-
-
-/**
- * @defgroup Images Images and compression
- * @brief Functions to deal with images and compressed buffers.
- *
- * @defgroup REST REST
- * @brief Functions to answer REST requests in a callback.
- *
- * @defgroup Callbacks Callbacks
- * @brief Functions to register and manage callbacks by the plugins.
- *
- * @defgroup Worklists Worklists
- * @brief Functions to register and manage worklists.
- *
- * @defgroup Orthanc Orthanc
- * @brief Functions to access the content of the Orthanc server.
- **/
-
-
-
-/**
- * @defgroup Toolbox Toolbox
- * @brief Generic functions to help with the creation of plugins.
- **/
-
-
-
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-#pragma once
-
-
-#include <stdio.h>
-#include <string.h>
-
-#ifdef WIN32
-#define ORTHANC_PLUGINS_API __declspec(dllexport)
-#else
-#define ORTHANC_PLUGINS_API
-#endif
-
-#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
-#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 already in use */,
-    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
-    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
-    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
-    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
-    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
-    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
-    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
-    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
-    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
-    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
-    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
-    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
-    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
-    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
-    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
-    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
-    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
-    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
-    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
-    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
-    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
-    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
-    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
-    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
-    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
-    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
-    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
-    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
-    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
-    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
-    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
-    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
-    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
-    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
-    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
-    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
-    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
-
-    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
-  } OrthancPluginErrorCode;
-
-
-  /**
-   * Forward declaration of one of the mandatory functions for Orthanc
-   * plugins.
-   **/
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
-
-
-  /**
-   * The various HTTP methods for a REST call.
-   **/
-  typedef enum
-  {
-    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
-    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
-    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
-    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
-
-    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
-  } OrthancPluginHttpMethod;
-
-
-  /**
-   * @brief The parameters of a REST request.
-   * @ingroup Callbacks
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The HTTP method.
-     **/
-    OrthancPluginHttpMethod method;    
-
-    /**
-     * @brief The number of groups of the regular expression.
-     **/
-    uint32_t                groupsCount;
-
-    /**
-     * @brief The matched values for the groups of the regular expression.
-     **/
-    const char* const*      groups;
-
-    /**
-     * @brief For a GET request, the number of GET parameters.
-     **/
-    uint32_t                getCount;
-
-    /**
-     * @brief For a GET request, the keys of the GET parameters.
-     **/
-    const char* const*      getKeys;
-
-    /**
-     * @brief For a GET request, the values of the GET parameters.
-     **/
-    const char* const*      getValues;
-
-    /**
-     * @brief For a PUT or POST request, the content of the body.
-     **/
-    const char*             body;
-
-    /**
-     * @brief For a PUT or POST request, the number of bytes of the body.
-     **/
-    uint32_t                bodySize;
-
-
-    /* --------------------------------------------------
-       New in version 0.8.1
-       -------------------------------------------------- */
-
-    /**
-     * @brief The number of HTTP headers.
-     **/
-    uint32_t                headersCount;
-
-    /**
-     * @brief The keys of the HTTP headers (always converted to low-case).
-     **/
-    const char* const*      headersKeys;
-
-    /**
-     * @brief The values of the HTTP headers.
-     **/
-    const char* const*      headersValues;
-
-  } OrthancPluginHttpRequest;
-
-
-  typedef enum 
-  {
-    /* Generic services */
-    _OrthancPluginService_LogInfo = 1,
-    _OrthancPluginService_LogWarning = 2,
-    _OrthancPluginService_LogError = 3,
-    _OrthancPluginService_GetOrthancPath = 4,
-    _OrthancPluginService_GetOrthancDirectory = 5,
-    _OrthancPluginService_GetConfigurationPath = 6,
-    _OrthancPluginService_SetPluginProperty = 7,
-    _OrthancPluginService_GetGlobalProperty = 8,
-    _OrthancPluginService_SetGlobalProperty = 9,
-    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
-    _OrthancPluginService_GetCommandLineArgument = 11,
-    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
-    _OrthancPluginService_GetConfiguration = 13,
-    _OrthancPluginService_BufferCompression = 14,
-    _OrthancPluginService_ReadFile = 15,
-    _OrthancPluginService_WriteFile = 16,
-    _OrthancPluginService_GetErrorDescription = 17,
-    _OrthancPluginService_CallHttpClient = 18,
-    _OrthancPluginService_RegisterErrorCode = 19,
-    _OrthancPluginService_RegisterDictionaryTag = 20,
-    _OrthancPluginService_DicomBufferToJson = 21,
-    _OrthancPluginService_DicomInstanceToJson = 22,
-    _OrthancPluginService_CreateDicom = 23,
-    _OrthancPluginService_ComputeMd5 = 24,
-    _OrthancPluginService_ComputeSha1 = 25,
-    _OrthancPluginService_LookupDictionary = 26,
-
-    /* Registration of callbacks */
-    _OrthancPluginService_RegisterRestCallback = 1000,
-    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
-    _OrthancPluginService_RegisterStorageArea = 1002,
-    _OrthancPluginService_RegisterOnChangeCallback = 1003,
-    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
-    _OrthancPluginService_RegisterWorklistCallback = 1005,
-    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
-
-    /* Sending answers to REST calls */
-    _OrthancPluginService_AnswerBuffer = 2000,
-    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
-    _OrthancPluginService_Redirect = 2002,
-    _OrthancPluginService_SendHttpStatusCode = 2003,
-    _OrthancPluginService_SendUnauthorized = 2004,
-    _OrthancPluginService_SendMethodNotAllowed = 2005,
-    _OrthancPluginService_SetCookie = 2006,
-    _OrthancPluginService_SetHttpHeader = 2007,
-    _OrthancPluginService_StartMultipartAnswer = 2008,
-    _OrthancPluginService_SendMultipartItem = 2009,
-    _OrthancPluginService_SendHttpStatus = 2010,
-    _OrthancPluginService_CompressAndAnswerImage = 2011,
-    _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 worklists */
-    _OrthancPluginService_WorklistAddAnswer = 7000,
-    _OrthancPluginService_WorklistMarkIncomplete = 7001,
-    _OrthancPluginService_WorklistIsMatch = 7002,
-    _OrthancPluginService_WorklistGetDicomQuery = 7003,
-
-    _OrthancPluginService_INTERNAL = 0x7fffffff
-  } _OrthancPluginService;
-
-
-  typedef enum
-  {
-    _OrthancPluginProperty_Description = 1,
-    _OrthancPluginProperty_RootUri = 2,
-    _OrthancPluginProperty_OrthancExplorer = 3,
-
-    _OrthancPluginProperty_INTERNAL = 0x7fffffff
-  } _OrthancPluginProperty;
-
-
-
-  /**
-   * The memory layout of the pixels of an image.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    /**
-     * @brief Graylevel 8bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * one byte.
-     **/
-    OrthancPluginPixelFormat_Grayscale8 = 1,
-
-    /**
-     * @brief Graylevel, unsigned 16bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * two bytes.
-     **/
-    OrthancPluginPixelFormat_Grayscale16 = 2,
-
-    /**
-     * @brief Graylevel, signed 16bpp image.
-     *
-     * The image is graylevel. Each pixel is signed and stored in two
-     * bytes.
-     **/
-    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
-
-    /**
-     * @brief Color image in RGB24 format.
-     *
-     * This format describes a color image. The pixels are stored in 3
-     * consecutive bytes. The memory layout is RGB.
-     **/
-    OrthancPluginPixelFormat_RGB24 = 4,
-
-    /**
-     * @brief Color image in RGBA32 format.
-     *
-     * This format describes a color image. The pixels are stored in 4
-     * consecutive bytes. The memory layout is RGBA.
-     **/
-    OrthancPluginPixelFormat_RGBA32 = 5,
-
-    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
-
-    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginPixelFormat;
-
-
-
-  /**
-   * The content types that are supported by Orthanc plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
-    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
-    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
-
-    _OrthancPluginContentType_INTERNAL = 0x7fffffff
-  } OrthancPluginContentType;
-
-
-
-  /**
-   * The supported types of DICOM resources.
-   **/
-  typedef enum
-  {
-    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
-    OrthancPluginResourceType_Study = 1,       /*!< Study */
-    OrthancPluginResourceType_Series = 2,      /*!< Series */
-    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
-    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
-
-    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
-  } OrthancPluginResourceType;
-
-
-
-  /**
-   * The supported types of changes that can happen to DICOM resources.
-   * @ingroup Callbacks
-   **/
-  typedef enum
-  {
-    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
-    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
-    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
-    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
-    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
-    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
-    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
-    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
-    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
-    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
-    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
-    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
-    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
-    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
-
-    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
-  } OrthancPluginChangeType;
-
-
-  /**
-   * The compression algorithms that are supported by the Orthanc core.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
-    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
-    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
-    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
-
-    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
-  } OrthancPluginCompressionType;
-
-
-  /**
-   * The image formats that are supported by the Orthanc core.
-   * @ingroup Images
-   **/
-  typedef enum
-  {
-    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
-    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
-    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
-
-    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginImageFormat;
-
-
-  /**
-   * The value representations present in the DICOM standard (version 2013).
-   * @ingroup Toolbox
-   **/
-  typedef enum
-  {
-    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
-    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
-    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
-    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
-    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
-    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
-    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
-    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
-    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
-    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
-    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
-    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
-    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
-    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
-    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
-    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
-    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
-    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
-    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
-    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
-    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
-    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
-    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
-    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
-    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
-    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
-    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
-
-    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
-  } OrthancPluginValueRepresentation;
-
-
-  /**
-   * The possible output formats for a DICOM-to-JSON conversion.
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomToJson()
-   **/
-  typedef enum
-  {
-    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
-    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
-    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
-
-    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
-  } OrthancPluginDicomToJsonFormat;
-
-
-  /**
-   * Flags to customize a DICOM-to-JSON conversion. By default, binary
-   * tags are formatted using Data URI scheme.
-   * @ingroup Toolbox
-   **/
-  typedef enum
-  {
-    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
-    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
-    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
-    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
-    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
-    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
-
-    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
-  } OrthancPluginDicomToJsonFlags;
-
-
-  /**
-   * Flags to the creation of a DICOM file.
-   * @ingroup Toolbox
-   * @see OrthancPluginCreateDicom()
-   **/
-  typedef enum
-  {
-    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
-    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
-
-    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
-  } OrthancPluginCreateDicomFlags;
-
-
-  /**
-   * The constraints on the DICOM identifiers that must be supported
-   * by the database plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
-    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
-    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
-    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
-
-    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
-  } OrthancPluginIdentifierConstraint;
-
-
-  /**
-   * The origin of a DICOM instance that has been received by Orthanc.
-   **/
-  typedef enum
-  {
-    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
-    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
-    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
-    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
-    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
-
-    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
-  } OrthancPluginInstanceOrigin;
-
-
-  /**
-   * @brief A memory buffer allocated by the core system of Orthanc.
-   *
-   * A memory buffer allocated by the core system of Orthanc. When the
-   * content of the buffer is not useful anymore, it must be free by a
-   * call to ::OrthancPluginFreeMemoryBuffer().
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The content of the buffer.
-     **/
-    void*      data;
-
-    /**
-     * @brief The number of bytes in the buffer.
-     **/
-    uint32_t   size;
-  } OrthancPluginMemoryBuffer;
-
-
-
-
-  /**
-   * @brief Opaque structure that represents the HTTP connection to the client application.
-   * @ingroup Callback
-   **/
-  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
-
-
-
-  /**
-   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
-   **/
-  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
-
-
-
-  /**
-   * @brief Opaque structure that represents an image that is uncompressed in memory.
-   * @ingroup Images
-   **/
-  typedef struct _OrthancPluginImage_t OrthancPluginImage;
-
-
-
-  /**
-   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
-   * @ingroup Images
-   **/
-  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
-
-
-
-  /**
-   * @brief Opaque structure to an object that represents a C-Find query.
-   * @ingroup Worklists
-   **/
-  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
-
-
-
-  /**
-   * @brief Opaque structure to an object that represents the answers to a C-Find query.
-   * @ingroup Worklists
-   **/
-  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
-
-
-
-  /**
-   * @brief Signature of a callback function that answers to a REST request.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
-    OrthancPluginRestOutput* output,
-    const char* url,
-    const OrthancPluginHttpRequest* request);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
-    OrthancPluginDicomInstance* instance,
-    const char* instanceId);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
-    OrthancPluginChangeType changeType,
-    OrthancPluginResourceType resourceType,
-    const char* resourceId);
-
-
-
-  /**
-   * @brief Signature of a callback function to decode a DICOM instance as an image.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
-    OrthancPluginImage** target,
-    const void* dicom,
-    const uint32_t size,
-    uint32_t frameIndex);
-
-
-
-  /**
-   * @brief Signature of a function to free dynamic memory.
-   **/
-  typedef void (*OrthancPluginFree) (void* buffer);
-
-
-
-  /**
-   * @brief Callback for writing to the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
-   *
-   * @param uuid The UUID of the file.
-   * @param content The content of the file.
-   * @param size The size of the file.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
-    const char* uuid,
-    const void* content,
-    int64_t size,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for reading from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
-   *
-   * @param content The content of the file (output).
-   * @param size The size of the file (output).
-   * @param uuid The UUID of the file of interest.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
-    void** content,
-    int64_t* size,
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for removing a file from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
-   *
-   * @param uuid The UUID of the file to be removed.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
-   *
-   * Signature of a callback function that is triggered when Orthanc
-   * receives a C-Find SCP request against modality worklists.
-   *
-   * @param answers The target structure where answers must be stored.
-   * @param query The worklist query.
-   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
-   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
-    OrthancPluginWorklistAnswers*     answers,
-    const OrthancPluginWorklistQuery* query,
-    const char*                       remoteAet,
-    const char*                       calledAet);
-
-
-
-  /**
-   * @brief Data structure that contains information about the Orthanc core.
-   **/
-  typedef struct _OrthancPluginContext_t
-  {
-    void*                     pluginsManager;
-    const char*               orthancVersion;
-    OrthancPluginFree         Free;
-    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
-                                              _OrthancPluginService service,
-                                              const void* params);
-  } OrthancPluginContext;
-
-
-  
-  /**
-   * @brief An entry in the dictionary of DICOM tags.
-   **/
-  typedef struct
-  {
-    uint16_t                          group;            /*!< The group of the tag */
-    uint16_t                          element;          /*!< The element of the tag */
-    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
-    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
-    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
-  } OrthancPluginDictionaryEntry;
-
-
-
-  /**
-   * @brief Free a string.
-   * 
-   * Free a string that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param str The string to be freed.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
-    OrthancPluginContext* context, 
-    char* str)
-  {
-    if (str != NULL)
-    {
-      context->Free(str);
-    }
-  }
-
-
-  /**
-   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
-   * 
-   * This function checks whether the version of this C header is
-   * compatible with the current version of Orthanc. The result of
-   * this function should always be checked in the
-   * OrthancPluginInitialize() entry point of the plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return 1 if and only if the versions are compatible. If the
-   * result is 0, the initialization of the plugin should fail.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
-    OrthancPluginContext* context)
-  {
-    int major, minor, revision;
-
-    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
-        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
-        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
-        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
-    {
-      /* Mismatch in the size of the enumerations */
-      return 0;
-    }
-
-    /* Assume compatibility with the mainline */
-    if (!strcmp(context->orthancVersion, "mainline"))
-    {
-      return 1;
-    }
-
-    /* Parse the version of the Orthanc core */
-    if ( 
-#ifdef _MSC_VER
-      sscanf_s
-#else
-      sscanf
-#endif
-      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
-    {
-      return 0;
-    }
-
-    /* Check the major number of the version */
-
-    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the minor number of the version */
-
-    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the revision number of the version */
-
-    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
-    {
-      return 1;
-    }
-    else
-    {
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Free a memory buffer.
-   * 
-   * Free a memory buffer that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer to release.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
-    OrthancPluginContext* context, 
-    OrthancPluginMemoryBuffer* buffer)
-  {
-    context->Free(buffer->data);
-  }
-
-
-  /**
-   * @brief Log an error.
-   *
-   * Log an error message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogError, message);
-  }
-
-
-  /**
-   * @brief Log a warning.
-   *
-   * Log a warning message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
-  }
-
-
-  /**
-   * @brief Log an information.
-   *
-   * Log an information message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
-  }
-
-
-
-  typedef struct
-  {
-    const char* pathRegularExpression;
-    OrthancPluginRestCallback callback;
-  } _OrthancPluginRestCallback;
-
-  /**
-   * @brief Register a REST callback.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   *
-   * Each REST callback is guaranteed to run in mutual exclusion.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   * @see OrthancPluginRegisterRestCallbackNoLock()
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
-  }
-
-
-
-  /**
-   * @brief Register a REST callback, without locking.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   *
-   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
-   * will NOT be invoked in mutual exclusion. This can be useful for
-   * high-performance plugins that must handle concurrent requests
-   * (Orthanc uses a pool of threads, one thread being assigned to
-   * each incoming HTTP request). Of course, it is up to the plugin to
-   * implement the required locking mechanisms.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   * @see OrthancPluginRegisterRestCallback()
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnStoredInstanceCallback callback;
-  } _OrthancPluginOnStoredInstanceCallback;
-
-  /**
-   * @brief Register a callback for received instances.
-   *
-   * This function registers a callback function that is called
-   * whenever a new DICOM instance is stored into the Orthanc core.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
-    OrthancPluginContext*                  context,
-    OrthancPluginOnStoredInstanceCallback  callback)
-  {
-    _OrthancPluginOnStoredInstanceCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              answer;
-    uint32_t                 answerSize;
-    const char*              mimeType;
-  } _OrthancPluginAnswerBuffer;
-
-  /**
-   * @brief Answer to a REST request.
-   *
-   * This function answers to a REST request with the content of a memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param answer Pointer to the memory buffer containing the answer.
-   * @param answerSize Number of bytes of the answer.
-   * @param mimeType The MIME type of the answer.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              answer,
-    uint32_t                 answerSize,
-    const char*              mimeType)
-  {
-    _OrthancPluginAnswerBuffer params;
-    params.output = output;
-    params.answer = answer;
-    params.answerSize = answerSize;
-    params.mimeType = mimeType;
-    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginPixelFormat  format;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-  } _OrthancPluginCompressAndAnswerPngImage;
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginImageFormat  imageFormat;
-    OrthancPluginPixelFormat  pixelFormat;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-    uint8_t                   quality;
-  } _OrthancPluginCompressAndAnswerImage;
-
-
-  /**
-   * @brief Answer to a REST request with a PNG image.
-   *
-   * This function answers to a REST request with a PNG image. The
-   * parameters of this function describe a memory buffer that
-   * contains an uncompressed image. The image will be automatically compressed
-   * as a PNG image by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer containing the uncompressed image.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
-    OrthancPluginContext*     context,
-    OrthancPluginRestOutput*  output,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    const void*               buffer)
-  {
-    _OrthancPluginCompressAndAnswerImage params;
-    params.output = output;
-    params.imageFormat = OrthancPluginImageFormat_Png;
-    params.pixelFormat = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    params.quality = 0;  /* No quality for PNG */
-    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 instanceId;
-  } _OrthancPluginGetDicomForInstance;
-
-  /**
-   * @brief Retrieve a DICOM instance using its Orthanc identifier.
-   * 
-   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
-   * file is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 instanceId)
-  {
-    _OrthancPluginGetDicomForInstance params;
-    params.target = target;
-    params.instanceId = instanceId;
-    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-  } _OrthancPluginRestApiGet;
-
-  /**
-   * @brief Make a GET call to the built-in Orthanc REST API.
-   * 
-   * Make a GET call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGetAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
-  }
-
-
-
-  /**
-   * @brief Make a GET call to the REST API, as tainted by the plugins.
-   * 
-   * Make a GET call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    const char*                 body;
-    uint32_t                    bodySize;
-  } _OrthancPluginRestApiPostPut;
-
-  /**
-   * @brief Make a POST call to the built-in Orthanc REST API.
-   * 
-   * Make a POST call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPostAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
-  }
-
-
-  /**
-   * @brief Make a POST call to the REST API, as tainted by the plugins.
-   * 
-   * Make a POST call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPost
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
-  }
-
-
-
-  /**
-   * @brief Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiDeleteAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
-  }
-
-
-  /**
-   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
-   * 
-   * Make a DELETE call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. 
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiDelete
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the built-in Orthanc REST API.
-   * 
-   * Make a PUT call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPutAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the REST API, as tainted by the plugins.
-   * 
-   * Make a PUT call to the Orthanc REST API, after all the plugins
-   * are applied. In other words, if some plugin overrides or adds the
-   * called URI to the built-in Orthanc REST API, this call will
-   * return the result provided by this plugin. The result to the
-   * query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiPut
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              argument;
-  } _OrthancPluginOutputPlusArgument;
-
-  /**
-   * @brief Redirect a REST request.
-   *
-   * This function answers to a REST request by redirecting the user
-   * to another URI using HTTP status 301.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param redirection Where to redirect.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              redirection)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = redirection;
-    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const char*  argument;
-  } _OrthancPluginRetrieveDynamicString;
-
-  /**
-   * @brief Look for a patient.
-   *
-   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored patients).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param patientID The Patient ID of interest.
-   * @return The NULL value if the patient is non-existent, or a string containing the 
-   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
-    OrthancPluginContext*  context,
-    const char*            patientID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = patientID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study.
-   *
-   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param studyUID The Study Instance UID of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
-    OrthancPluginContext*  context,
-    const char*            studyUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = studyUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study, using the accession number.
-   *
-   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param accessionNumber The Accession Number of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
-    OrthancPluginContext*  context,
-    const char*            accessionNumber)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = accessionNumber;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a series.
-   *
-   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored series).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param seriesUID The Series Instance UID of interest.
-   * @return The NULL value if the series is non-existent, or a string containing the 
-   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
-    OrthancPluginContext*  context,
-    const char*            seriesUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = seriesUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for an instance.
-   *
-   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored instances).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param sopInstanceUID The SOP Instance UID of interest.
-   * @return The NULL value if the instance is non-existent, or a string containing the 
-   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
-    OrthancPluginContext*  context,
-    const char*            sopInstanceUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = sopInstanceUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    uint16_t                 status;
-  } _OrthancPluginSendHttpStatusCode;
-
-  /**
-   * @brief Send a HTTP status code.
-   *
-   * This function answers to a REST request by sending a HTTP status
-   * code (such as "400 - Bad Request"). Note that:
-   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
-   * - Redirections (status 301) must use ::OrthancPluginRedirect().
-   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
-   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param status The HTTP status code to be sent.
-   * @ingroup REST
-   * @see OrthancPluginSendHttpStatus()
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    uint16_t                 status)
-  {
-    _OrthancPluginSendHttpStatusCode params;
-    params.output = output;
-    params.status = status;
-    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
-  }
-
-
-  /**
-   * @brief Signal that a REST request is not authorized.
-   *
-   * This function answers to a REST request by signaling that it is
-   * not authorized.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param realm The realm for the authorization process.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              realm)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = realm;
-    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
-  }
-
-
-  /**
-   * @brief Signal that this URI does not support this HTTP method.
-   *
-   * This function answers to a REST request by signaling that the
-   * queried URI does not support this method.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              allowedMethods)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = allowedMethods;
-    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              key;
-    const char*              value;
-  } _OrthancPluginSetHttpHeader;
-
-  /**
-   * @brief Set a cookie.
-   *
-   * This function sets a cookie in the HTTP client.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param cookie The cookie to be set.
-   * @param value The value of the cookie.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              cookie,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = cookie;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
-  }
-
-
-  /**
-   * @brief Set some HTTP header.
-   *
-   * This function sets a HTTP header in the HTTP answer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param key The HTTP header to be set.
-   * @param value The value of the HTTP header.
-   * @ingroup REST
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              key,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = key;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
-  }
-
-
-  typedef struct
-  {
-    char**                       resultStringToFree;
-    const char**                 resultString;
-    int64_t*                     resultInt64;
-    const char*                  key;
-    OrthancPluginDicomInstance*  instance;
-    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
-  } _OrthancPluginAccessDicomInstance;
-
-
-  /**
-   * @brief Get the AET of a DICOM instance.
-   *
-   * This function returns the Application Entity Title (AET) of the
-   * DICOM modality from which a DICOM instance originates.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The AET if success, NULL if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the size of a DICOM file.
-   *
-   * This function returns the number of bytes of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The size of the file, -1 in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    int64_t size;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &size;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return size;
-    }
-  }
-
-
-  /**
-   * @brief Get the data of a DICOM file.
-   *
-   * This function returns a pointer to the content of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The pointer to the DICOM data, NULL in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file.
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance. In contrast with
-   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
-   * its simplified version.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Check whether a DICOM instance is associated with some metadata.
-   *
-   * This function checks whether the DICOM instance of interest is
-   * associated with some metadata. As of Orthanc 0.8.1, in the
-   * callbacks registered by
-   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
-   * possibly available metadata are "ReceptionDate", "RemoteAET" and
-   * "IndexInSeries".
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    int64_t result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return (result != 0);
-    }
-  }
-
-
-  /**
-   * @brief Get the value of some metadata associated with a given DICOM instance.
-   *
-   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
-   * Before calling this function, the existence of the metadata must have been checked with
-   * ::OrthancPluginHasInstanceMetadata().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return The metadata value if success, NULL if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginStorageCreate  create;
-    OrthancPluginStorageRead    read;
-    OrthancPluginStorageRemove  remove;
-    OrthancPluginFree           free;
-  } _OrthancPluginRegisterStorageArea;
-
-  /**
-   * @brief Register a custom storage area.
-   *
-   * This function registers a custom storage area, to replace the
-   * built-in way Orthanc stores its files on the filesystem. This
-   * function must be called during the initialization of the plugin,
-   * i.e. inside the OrthancPluginInitialize() public function.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param create The callback function to store a file on the custom storage area.
-   * @param read The callback function to read a file from the custom storage area.
-   * @param remove The callback function to remove a file from the custom storage area.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
-    OrthancPluginContext*       context,
-    OrthancPluginStorageCreate  create,
-    OrthancPluginStorageRead    read,
-    OrthancPluginStorageRemove  remove)
-  {
-    _OrthancPluginRegisterStorageArea params;
-    params.create = create;
-    params.read = read;
-    params.remove = remove;
-
-#ifdef  __cplusplus
-    params.free = ::free;
-#else
-    params.free = free;
-#endif
-
-    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
-  }
-
-
-
-  /**
-   * @brief Return the path to the Orthanc executable.
-   *
-   * This function returns the path to the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the directory containing the Orthanc.
-   *
-   * This function returns the path to the directory containing the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the path to the configuration file(s).
-   *
-   * This function returns the path to the configuration file(s) that
-   * was specified when starting Orthanc. Since version 0.9.1, this
-   * path can refer to a folder that stores a set of configuration
-   * files. This function is deprecated in favor of
-   * OrthancPluginGetConfiguration().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   * @see OrthancPluginGetConfiguration()
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnChangeCallback callback;
-  } _OrthancPluginOnChangeCallback;
-
-  /**
-   * @brief Register a callback to monitor changes.
-   *
-   * This function registers a callback function that is called
-   * whenever a change happens to some DICOM resource.
-   *
-   * @warning If your change callback has to call the REST API of
-   * Orthanc, you should make these calls in a separate thread (with
-   * the events passing through a message queue). Otherwise, this
-   * could result in deadlocks in the presence of other plugins or Lua
-   * script.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginOnChangeCallback  callback)
-  {
-    _OrthancPluginOnChangeCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    const char* plugin;
-    _OrthancPluginProperty property;
-    const char* value;
-  } _OrthancPluginSetPluginProperty;
-
-
-  /**
-   * @brief Set the URI where the plugin provides its Web interface.
-   *
-   * For plugins that come with a Web interface, this function
-   * declares the entry path where to find this interface. This
-   * information is notably used in the "Plugins" page of Orthanc
-   * Explorer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The root URI for this plugin.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
-    OrthancPluginContext*  context,
-    const char*            uri)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_RootUri;
-    params.value = uri;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  /**
-   * @brief Set a description for this plugin.
-   *
-   * Set a description for this plugin. It is displayed in the
-   * "Plugins" page of Orthanc Explorer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param description The description.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
-    OrthancPluginContext*  context,
-    const char*            description)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_Description;
-    params.value = description;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  /**
-   * @brief Extend the JavaScript code of Orthanc Explorer.
-   *
-   * Add JavaScript code to customize the default behavior of Orthanc
-   * Explorer. This can for instance be used to add new buttons.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param javascript The custom JavaScript code.
-   **/ 
-  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
-    OrthancPluginContext*  context,
-    const char*            javascript)
-  {
-    _OrthancPluginSetPluginProperty params;
-    params.plugin = OrthancPluginGetName();
-    params.property = _OrthancPluginProperty_OrthancExplorer;
-    params.value = javascript;
-
-    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
-  }
-
-
-  typedef struct
-  {
-    char**       result;
-    int32_t      property;
-    const char*  value;
-  } _OrthancPluginGlobalProperty;
-
-
-  /**
-   * @brief Get the value of a global property.
-   *
-   * Get the value of a global property that is stored in the Orthanc database. Global
-   * properties whose index is below 1024 are reserved by Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param property The global property of interest.
-   * @param defaultValue The value to return, if the global property is unset.
-   * @return The value of the global property, or NULL in the case of an error. This
-   * string must be freed by OrthancPluginFreeString().
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
-    OrthancPluginContext*  context,
-    int32_t                property,
-    const char*            defaultValue)
-  {
-    char* result;
-
-    _OrthancPluginGlobalProperty params;
-    params.result = &result;
-    params.property = property;
-    params.value = defaultValue;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Set the value of a global property.
-   *
-   * Set the value of a global property into the Orthanc
-   * database. Setting a global property can be used by plugins to
-   * save their internal parameters. Plugins are only allowed to set
-   * properties whose index are above or equal to 1024 (properties
-   * below 1024 are read-only and reserved by Orthanc).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param property The global property of interest.
-   * @param value The value to be set in the global property.
-   * @return 0 if success, or the error code if failure.
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
-    OrthancPluginContext*  context,
-    int32_t                property,
-    const char*            value)
-  {
-    _OrthancPluginGlobalProperty params;
-    params.result = NULL;
-    params.property = property;
-    params.value = value;
-
-    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
-  }
-
-
-
-  typedef struct
-  {
-    int32_t   *resultInt32;
-    uint32_t  *resultUint32;
-    int64_t   *resultInt64;
-    uint64_t  *resultUint64;
-  } _OrthancPluginReturnSingleValue;
-
-  /**
-   * @brief Get the number of command-line arguments.
-   *
-   * Retrieve the number of command-line arguments that were used to launch Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The number of arguments.
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-  /**
-   * @brief Get the value of a command-line argument.
-   *
-   * Get the value of one of the command-line arguments that were used
-   * to launch Orthanc. The number of available arguments can be
-   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param argument The index of the argument.
-   * @return The value of the argument, or NULL in the case of an error. This
-   * string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
-    OrthancPluginContext*  context,
-    uint32_t               argument)
-  {
-    char* result;
-
-    _OrthancPluginGlobalProperty params;
-    params.result = &result;
-    params.property = (int32_t) argument;
-    params.value = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the expected version of the database schema.
-   *
-   * Retrieve the expected version of the database schema.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return The version.
-   * @ingroup Callbacks
-   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
-   **/
-  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
-    OrthancPluginContext*  context)
-  {
-    uint32_t count = 0;
-
-    _OrthancPluginReturnSingleValue params;
-    memset(&params, 0, sizeof(params));
-    params.resultUint32 = &count;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return 0;
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-
-  /**
-   * @brief Return the content of the configuration file(s).
-   *
-   * This function returns the content of the configuration that is
-   * used by Orthanc, formatted as a JSON string.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the configuration. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              subType;
-    const char*              contentType;
-  } _OrthancPluginStartMultipartAnswer;
-
-  /**
-   * @brief Start an HTTP multipart answer.
-   *
-   * Initiates a HTTP multipart answer, as the result of a REST request.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param subType The sub-type of the multipart answer ("mixed" or "related").
-   * @param contentType The MIME type of the items in the multipart answer.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginSendMultipartItem(), 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 char*                     buffer;
-    uint32_t                        size;
-    OrthancPluginDicomToJsonFormat  format;
-    OrthancPluginDicomToJsonFlags   flags;
-    uint32_t                        maxStringLength;
-  } _OrthancPluginDicomToJson;
-
-
-  /**
-   * @brief Format a DICOM memory buffer as a JSON string.
-   *
-   * This function takes as input a memory buffer containing a DICOM
-   * file, and outputs a JSON string representing the tags of this
-   * DICOM file.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer containing the DICOM file.
-   * @param size The size of the memory buffer.
-   * @param format The output format.
-   * @param flags Flags governing the output.
-   * @param maxStringLength The maximum length of a field. Too long fields will
-   * be output as "null". The 0 value means no maximum length.
-   * @return The NULL value if the case of an error, or the JSON
-   * string. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
-    OrthancPluginContext*           context,
-    const char*                     buffer,
-    uint32_t                        size,
-    OrthancPluginDicomToJsonFormat  format,
-    OrthancPluginDicomToJsonFlags   flags, 
-    uint32_t                        maxStringLength)
-  {
-    char* result;
-
-    _OrthancPluginDicomToJson params;
-    memset(&params, 0, sizeof(params));
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-    params.format = format;
-    params.flags = flags;
-    params.maxStringLength = maxStringLength;
-
-    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Format a DICOM instance as a JSON string.
-   *
-   * This function formats a DICOM instance that is stored in Orthanc,
-   * and outputs a JSON string representing the tags of this DICOM
-   * instance.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instanceId The Orthanc identifier of the instance.
-   * @param format The output format.
-   * @param flags Flags governing the output.
-   * @param maxStringLength The maximum length of a field. Too long fields will
-   * be output as "null". The 0 value means no maximum length.
-   * @return The NULL value if the case of an error, or the JSON
-   * string. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomInstanceToJson
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
-    OrthancPluginContext*           context,
-    const char*                     instanceId,
-    OrthancPluginDicomToJsonFormat  format,
-    OrthancPluginDicomToJsonFlags   flags, 
-    uint32_t                        maxStringLength)
-  {
-    char* result;
-
-    _OrthancPluginDicomToJson params;
-    memset(&params, 0, sizeof(params));
-    params.result = &result;
-    params.instanceId = instanceId;
-    params.format = format;
-    params.flags = flags;
-    params.maxStringLength = maxStringLength;
-
-    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    uint32_t                    headersCount;
-    const char* const*          headersKeys;
-    const char* const*          headersValues;
-    int32_t                     afterPlugins;
-  } _OrthancPluginRestApiGet2;
-
-  /**
-   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
-   * 
-   * Make a GET call to the Orthanc REST API with extended
-   * parameters. The result to the query is stored into a newly
-   * allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param uri The URI in the built-in Orthanc API.
-   * @param headersCount The number of HTTP headers.
-   * @param headersKeys Array containing the keys of the HTTP headers.
-   * @param headersValues Array containing the values of the HTTP headers.
-   * @param afterPlugins If 0, the built-in API of Orthanc is used.
-   * If 1, the API is tainted by the plugins.
-   * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
-   * @ingroup Orthanc
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    uint32_t                    headersCount,
-    const char* const*          headersKeys,
-    const char* const*          headersValues,
-    int32_t                     afterPlugins)
-  {
-    _OrthancPluginRestApiGet2 params;
-    params.target = target;
-    params.uri = uri;
-    params.headersCount = headersCount;
-    params.headersKeys = headersKeys;
-    params.headersValues = headersValues;
-    params.afterPlugins = afterPlugins;
-
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginWorklistCallback callback;
-  } _OrthancPluginWorklistCallback;
-
-  /**
-   * @brief Register a callback to handle modality worklists requests.
-   *
-   * This function registers a callback to handle C-Find SCP requests
-   * on modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginWorklistCallback  callback)
-  {
-    _OrthancPluginWorklistCallback params;
-    params.callback = callback;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
-  }
-
-
-  
-  typedef struct
-  {
-    OrthancPluginWorklistAnswers*      answers;
-    const OrthancPluginWorklistQuery*  query;
-    const void*                        dicom;
-    uint32_t                           size;
-  } _OrthancPluginWorklistAnswersOperation;
-
-  /**
-   * @brief Add one answer to some modality worklist request.
-   *
-   * This function adds one worklist (encoded as a DICOM file) to the
-   * set of answers corresponding to some C-Find SCP request against
-   * modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param answers The set of answers.
-   * @param query The worklist query, as received by the callback.
-   * @param dicom The worklist to answer, encoded as a DICOM file.
-   * @param size The size of the DICOM file.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
-    OrthancPluginContext*             context,
-    OrthancPluginWorklistAnswers*     answers,
-    const OrthancPluginWorklistQuery* query,
-    const void*                       dicom,
-    uint32_t                          size)
-  {
-    _OrthancPluginWorklistAnswersOperation params;
-    params.answers = answers;
-    params.query = query;
-    params.dicom = dicom;
-    params.size = size;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
-  }
-
-
-  /**
-   * @brief Mark the set of worklist answers as incomplete.
-   *
-   * This function marks as incomplete the set of answers
-   * corresponding to some C-Find SCP request against modality
-   * worklists. This must be used if canceling the handling of a
-   * request when too many answers are to be returned.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param answers The set of answers.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
-    OrthancPluginContext*          context,
-    OrthancPluginWorklistAnswers*  answers)
-  {
-    _OrthancPluginWorklistAnswersOperation params;
-    params.answers = answers;
-    params.query = NULL;
-    params.dicom = NULL;
-    params.size = 0;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
-  }
-
-
-  typedef struct
-  {
-    const OrthancPluginWorklistQuery*  query;
-    const void*                        dicom;
-    uint32_t                           size;
-    int32_t*                           isMatch;
-    OrthancPluginMemoryBuffer*         target;
-  } _OrthancPluginWorklistQueryOperation;
-
-  /**
-   * @brief Test whether a worklist matches the query.
-   *
-   * This function checks whether one worklist (encoded as a DICOM
-   * file) matches the C-Find SCP query against modality
-   * worklists. This function must be called before adding the
-   * worklist as an answer through OrthancPluginWorklistAddAnswer().
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param query The worklist query, as received by the callback.
-   * @param dicom The worklist to answer, encoded as a DICOM file.
-   * @param size The size of the DICOM file.
-   * @return 1 if the worklist matches the query, 0 otherwise.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
-    OrthancPluginContext*              context,
-    const OrthancPluginWorklistQuery*  query,
-    const void*                        dicom,
-    uint32_t                           size)
-  {
-    int32_t isMatch = 0;
-
-    _OrthancPluginWorklistQueryOperation params;
-    params.query = query;
-    params.dicom = dicom;
-    params.size = size;
-    params.isMatch = &isMatch;
-    params.target = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
-    {
-      return isMatch;
-    }
-    else
-    {
-      /* Error: Assume non-match */
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Retrieve the worklist query as a DICOM file.
-   *
-   * This function retrieves the DICOM file that underlies a C-Find
-   * SCP query against modality worklists.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param query The worklist query, as received by the callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Worklists
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
-    OrthancPluginContext*              context,
-    OrthancPluginMemoryBuffer*         target,
-    const OrthancPluginWorklistQuery*  query)
-  {
-    _OrthancPluginWorklistQueryOperation params;
-    params.query = query;
-    params.dicom = NULL;
-    params.size = 0;
-    params.isMatch = NULL;
-    params.target = target;
-
-    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
-  }
-
-
-  /**
-   * @brief Get the origin of a DICOM file.
-   *
-   * This function returns the origin of a DICOM instance that has been received by Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The origin of the instance.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    OrthancPluginInstanceOrigin origin;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultOrigin = &origin;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return OrthancPluginInstanceOrigin_Unknown;
-    }
-    else
-    {
-      return origin;
-    }
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*     target;
-    const char*                    json;
-    const OrthancPluginImage*      pixelData;
-    OrthancPluginCreateDicomFlags  flags;
-  } _OrthancPluginCreateDicom;
-
-  /**
-   * @brief Create a DICOM instance from a JSON string and an image.
-   *
-   * This function takes as input a string containing a JSON file
-   * describing the content of a DICOM instance. As an output, it
-   * writes the corresponding DICOM instance to a newly allocated
-   * memory buffer. Additionally, an image to be encoded within the
-   * DICOM instance can also be provided.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
-   * @param json The input JSON file.
-   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
-   * @param flags Flags governing the output.
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   * @see OrthancPluginDicomBufferToJson
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
-    OrthancPluginContext*          context,
-    OrthancPluginMemoryBuffer*     target,
-    const char*                    json,
-    const OrthancPluginImage*      pixelData,
-    OrthancPluginCreateDicomFlags  flags)
-  {
-    _OrthancPluginCreateDicom params;
-    params.target = target;
-    params.json = json;
-    params.pixelData = pixelData;
-    params.flags = flags;
-
-    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginDecodeImageCallback callback;
-  } _OrthancPluginDecodeImageCallback;
-
-  /**
-   * @brief Register a callback to handle the decoding of DICOM images.
-   *
-   * This function registers a custom callback to the decoding of
-   * DICOM images, replacing the built-in decoder of Orthanc.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback.
-   * @return 0 if success, other value if error.
-   * @ingroup Callbacks
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
-    OrthancPluginContext*             context,
-    OrthancPluginDecodeImageCallback  callback)
-  {
-    _OrthancPluginDecodeImageCallback params;
-    params.callback = callback;
-
-    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
-  }
-  
-
-
-  typedef struct
-  {
-    OrthancPluginImage**       target;
-    OrthancPluginPixelFormat   format;
-    uint32_t                   width;
-    uint32_t                   height;
-    uint32_t                   pitch;
-    void*                      buffer;
-    const void*                constBuffer;
-    uint32_t                   bufferSize;
-    uint32_t                   frameIndex;
-  } _OrthancPluginCreateImage;
-
-
-  /**
-   * @brief Create an image.
-   *
-   * This function creates an image of given size and format.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param format The format of the pixels.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
-    OrthancPluginContext*     context,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-
-    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-  /**
-   * @brief Create an image pointing to a memory buffer.
-   *
-   * This function creates an image whose content points to a memory
-   * buffer managed by the plugin. Note that the buffer is directly
-   * accessed, no memory is allocated and no data is copied.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param format The format of the pixels.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer).
-   * @param buffer The memory buffer.
-   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
-    OrthancPluginContext*     context,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    void*                     buffer)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-
-    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  /**
-   * @brief Decode one frame from a DICOM instance.
-   *
-   * This function decodes one frame of a DICOM image that is stored
-   * in a memory buffer. This function will give the same result as
-   * OrthancPluginUncompressImage() for single-frame DICOM images.
-   *
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer Pointer to a memory buffer containing the DICOM image.
-   * @param bufferSize Size of the memory buffer containing the DICOM image.
-   * @param frameIndex The index of the frame of interest in a multi-frame image.
-   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
-   * @ingroup Images
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               bufferSize,
-    uint32_t               frameIndex)
-  {
-    OrthancPluginImage* target = NULL;
-
-    _OrthancPluginCreateImage params;
-    memset(&params, 0, sizeof(params));
-    params.target = &target;
-    params.constBuffer = buffer;
-    params.bufferSize = bufferSize;
-    params.frameIndex = frameIndex;
-
-    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
-    {
-      return NULL;
-    }
-    else
-    {
-      return target;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const void*  buffer;
-    uint32_t     size;
-  } _OrthancPluginComputeHash;
-
-  /**
-   * @brief Compute an MD5 hash.
-   *
-   * This functions computes the MD5 cryptographic hash of the given memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The source memory buffer.
-   * @param size The size in bytes of the source buffer.
-   * @return The NULL value in case of error, or a string containing the cryptographic hash.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               size)
-  {
-    char* result;
-
-    _OrthancPluginComputeHash params;
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-
-    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Compute a SHA-1 hash.
-   *
-   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The source memory buffer.
-   * @param size The size in bytes of the source buffer.
-   * @return The NULL value in case of error, or a string containing the cryptographic hash.
-   * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
-    OrthancPluginContext*  context,
-    const void*            buffer,
-    uint32_t               size)
-  {
-    char* result;
-
-    _OrthancPluginComputeHash params;
-    params.result = &result;
-    params.buffer = buffer;
-    params.size = size;
-
-    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginDictionaryEntry* target;
-    const char*                   name;
-  } _OrthancPluginLookupDictionary;
-
-  /**
-   * @brief Get information about the given DICOM tag.
-   *
-   * This functions makes a lookup in the dictionary of DICOM tags
-   * that are known to Orthanc, and returns information about this
-   * tag. The tag can be specified using its human-readable name
-   * (e.g. "PatientName") or a set of two hexadecimal numbers
-   * (e.g. "0010-0020").
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target Where to store the information about the tag.
-   * @param name The name of the DICOM tag.
-   * @return 0 if success, other value if error.
-   * @ingroup Toolbox
-   **/
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
-    OrthancPluginContext*          context,
-    OrthancPluginDictionaryEntry*  target,
-    const char*                    name)
-  {
-    _OrthancPluginLookupDictionary params;
-    params.target = target;
-    params.name = name;
-    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
-  }
-
-
-
-  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);
-  }
-
-
-#ifdef  __cplusplus
-}
-#endif
-
-
-/** @} */
-
--- a/StoneWebViewer/Resources/Styles/_button.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-// clean icon buttons
-.wvButton {
-    // Remove <a> default styles. Take care - this class may either be used
-    // with <a> or <button>.
-    &:hover {
-        text-decoration: none;
-        color: white;
-    }
-
-    // Remove <button> default styles.
-    outline: none;
-    background-color: transparent;
-    border: none;
-    border-radius: 0;
-
-    // Set relative to position button absolutely
-    position: relative;
-
-    // Style button
-    display: inline-block;
-    cursor: pointer;
-    font-variant: small-caps;
-    text-transform: lowercase;
-    text-align: center;
-    font-size: 1.3rem;
-    font-weight: 400;
-    color: hsl(0, 0%, 85%);
-    transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
-
-    // Position button
-    margin: 0;
-    min-width: 3rem;
-    padding: 0 10px;
-    line-height: 3.6rem;
-    &.wvLargeButton{
-        font-size: 2rem;
-        line-height: 6.2rem;
-        padding: 0 20px;
-    }
-}
-
-.wvButton--rotate {
-    @extend .wvButton;
-    // Rotate only the icon
-    &:before, &:after{
-        transform: rotate(90deg);
-        display: inline-block;
-    }
-
-}
-
-.wvButton--vflip {
-    @extend .wvButton;
-    // flip only the icon
-    &:before, &:after{
-        transform: scaleX(-1);
-        display: inline-block;
-    }
-
-}
-
-// button w/ blue underline
-.wvButton--underline, .fa.wvButton--underline {
-    @extend .wvButton;
-
-    position: relative;
-
-    background-color:inherit;
-    text-decoration: none;
-    text-align:left;
-    font-size: 1.2rem;
-    &.wvLargeButton{
-        font-size: 2rem;
-        width: 6.4rem;
-    }
-    * {
-        pointer-events: none;
-    }
-    &:hover, &:active, &:focus{
-        outline:0;
-    }
-
-    width: 3.2rem;
-    vertical-align: middle;
-    color:white;
-    opacity: 0.75;
-    border:none;
-    border-bottom: 2px solid rgba(255,255,255,0.1);
-    &:hover, &:focus{
-        border-color: rgba(255,255,255,1);
-        opacity:1;
-        .wvButton__bottomTriangle{
-            border-left-color: rgba(255,255,255,1);
-            &.toggled{
-                // border-color: rgba(255, 255, 255, 1);
-            }
-        }
-    }
-    &.active{
-        opacity: 1;
-        border-color: $primary;
-    }
-
-    // Make sure the 2px border is not hidden by viewports and other parts of
-    // the layout (.glyphicon class sets top to 1px)
-    top: 0px;
-
-    // Compensate glyphicon whitespace
-    &::before {
-        position: relative;
-        top: -1px;
-    }
-
-    // Adapt font-awesome icon to glyphicon styles
-    &.fa {
-        top: 0px;
-        font-weight: 800;
-    }
-}
-
-.wvButton__bottomTriangle{
-    transition: 0.3s border ease, 0.3s opacity ease;
-
-    display:block;
-    position: absolute;
-    bottom:0;
-    left:0;
-
-    width: 0;
-    height: 0;
-    border-style: solid;
-    border-width: 10px 0 0 10px;
-    border-color: transparent transparent transparent rgba(255,255,255,0.1);
-    &.active{
-        border-color: transparent transparent transparent $primary !important;
-        &.toggled{
-            border-left-color: $primary !important;
-        }
-    }
-    &.toggled{
-        // border-width: 15px 0 0 15px;
-    }
-}
-
-// button w/ border
-.wvButton--border {
-    @extend .wvButton;
-
-    // Prevent multi line buttons.
-    max-height: calc(2.8rem - 3px);
-    max-width: 100%;
-    overflow: hidden;
-
-    // Set margin
-    margin: 0.6rem;
-    margin-left: 0rem;
-    margin-right: 0rem;
-    & + & {
-        margin-left: 0.7rem;
-    }
-
-    // Set button size
-    line-height: 2rem;
-
-    // Align text
-    padding-top: 0.1rem;
-    padding-bottom: 0.5rem;
-
-    // Style button
-    font-size: 1.4rem;
-    border: 1px solid hsl(0, 0%, 27%);
-
-    // Set best looking font with small-caps.
-    font-family: Arial;
-
-    // Change background on hover
-    background-color: hsl(0, 0%, 0%);
-    &:hover {
-        background-color: hsl(0, 0%, 10%);
-    }
-
-    & > .glyphicon { // used with the same element as glyphicons
-        // Position button
-        position: relative;
-        display: inline-block;
-        top: 3px;
-        margin-right: 4px;
-    }
-}
-
-// button w/ border + white modifier to use when the background is white.
-.wvButton--borderAndWhite {
-    @extend .wvButton--border;
-
-    // Text color
-    color: hsl(0, 0%, 10%);
-    border: 1px solid hsl(0, 0%, 73%);
-
-    // Change background on hover
-    background-color: hsl(0, 0%, 100%);
-    &:hover {
-        color: hsl(0, 0%, 10%);
-        background-color: hsl(0, 0%, 90%);
-    }
-}
--- a/StoneWebViewer/Resources/Styles/_exitButton.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-.wvExitButton {
-    margin-left: 1rem;
-    margin-top: .25rem;
-    font-size: 1.25em;
-    color: white;
-    opacity: .66;
-
-    transition: .3s opacity ease;
-    background-color: inherit;
-    border: none;
-    text-decoration: none;
-    text-align: left;
-    padding: 0;
-    cursor: pointer;
-    font-family: inherit;
-    line-height: inherit;
-
-    &:hover, &:focus {
-        opacity: 1;
-        outline: 0;
-    }
-}
-
-.wvExitButton__text {
-    // Align text with glyph-icon.
-    position: relative;
-    top: -1px;
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_helpers.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1115 +0,0 @@
-/*
- *  Source code taken from private Osimis' frontend toolbox 3.2.1.
- */
-
-/**
-    _overlay.scss
- */
-.overlay__transparent{
-    position:fixed;
-    top:0;
-    left:0;
-    width:100%;
-    height:100%;
-    z-index: 1;
-}
-
-/** _transition.scss **/
-.transition,
-%transition{
-    &{
-        transition: 0.3s all ease;
-    }
-}
-
-.transition--long,
-%transition--long{
-    &{
-        transition: 0.6s all ease;
-    }
-}
-
-
-/** _list.scss **/
-// This is used in the study information panel.
-dd+dt{
-    clear:both;
-}
-.listDefinition{
-    width:100%;
-    line-height: 1.3;
-}
-.listDefinition__term{
-    clear:both;
-    float:left;
-    text-align: right;
-    padding-right:10px;
-    width:50%;
-    @extend .font__light;
-
-}
-.listDefinition__data{
-    text-align: left;
-    padding-left:10px;
-    float:right;
-    width:50%;
-    @extend .font__normal;
-
-}
-
-
-/** _animation.scss **/
-@keyframes blink__primary {
-    0% { color: $primary; }
-    100% { color: $text-color; }
-}
-
-.blink__primary{
-  animation: blink__primary 0.8s linear infinite;
-}
-
-[translate-cloak]{
-    transition: 0.3s all ease;
-    opacity: 1;
-    &.translate-cloak{
-        opacity: 0;
-    }
-}
-
-/** _button.scss **/
-.button__unstyled,
-%button__unstyled{
-    background-color:inherit;
-    border:none;
-    text-decoration: none;
-    text-align:left;
-    padding:0;
-    cursor: pointer;
-    *{
-        pointer-events: none;
-    }
-    &:hover, &:active, &:focus{
-        outline:0;
-    }
-}
-.button__base,
-%button__base{
-    @extend .button__unstyled;
-    transition: 0.3s all ease;
-    //& *,& *:before,& *:after{
-    //  @extend .transition;
-    //}
-}
-.button__state--active{
-    @extend .button__base;
-    opacity:1;
-    &:hover{
-        opacity:0.9;
-        color:$primary;
-    }
-}
-.button__state--inactive{
-    @extend .button__base;
-    opacity:0.333;
-    &:hover{
-        opacity:0.4333;
-        color:$primary;
-    }
-}
-.button__iconed{
-    @extend .button__base;
-    opacity: 1;
-    &:hover, &:focus, &.active{
-        opacity: 0.75;
-        color:$primary;
-    }
-}
-.button__switch--base,
-%button__switch--base{
-    @extend .button__base;
-    padding:5px 0px;
-    display:inline-block;
-    text-align: center;
-    border-top: 1px solid;
-    border-bottom: 1px solid;
-    border-color: $primary;
-    background-color: white;
-    overflow: hidden;
-    &:hover, &:focus{
-        background-color: lighten($primary, 10);
-        color:white;
-    }
-    &.active{
-        background-color:$primary;
-        color:white;
-    }
-}
-.button__switch{
-    @extend .button__switch--base;
-}
-.button__switch--first{
-    @extend .button__switch--base;
-    border-left: 1px solid $primary;
-    border-radius: 5px 0 0 5px;
-}
-.button__switch--last{
-    @extend .button__switch--base;
-    border-right: 1px solid $primary;
-    border-radius: 0 5px 5px 0;
-}
-.button__lightgrey--hover{
-    @extend .button__base;
-    &:hover, &:focus, &.active{
-        background-color: $lightGrey;
-    }
-}
-.button__text-danger--hover{
-    @extend .button__base;
-    &:hover, &:focus, &.active{
-        color: $dangerColor;
-    }
-}
-.button__text-primary--hover{
-    @extend .button__base;
-    &:hover, &:focus, &.active{
-        color: $primary;
-    }
-}
-.button__danger--hover{
-    @extend .button__base;
-    &:hover, &:focus, &.active{
-        background-color: $dangerColor;
-        color:white;
-    }
-}
-.button__text{
-    @extend .button__base;
-    opacity:0.66;
-    &:hover, &.active, &:focus{
-        opacity:1;
-    }
-}
-.button__text--underlined{
-    @extend .button__base;
-    text-decoration: underline;
-    &:hover, &.active, &:focus{
-        text-decoration:none;
-    }
-}
-.button__bordered{
-    @extend .button__base;
-    border-bottom: 2px solid $text-color;
-    &:hover, &:focus, &.active{
-        border-color: $primary;
-    }
-}
-.button__bordered--inverted{
-    @extend .button__base;
-    border-bottom: 2px solid white;
-}
-.button__close{
-    @extend .button__base;
-    position:absolute;
-    top:0;
-    right:0;
-    opacity:0.6;
-    z-index:10;
-    &:hover, &:focus{
-        opacity:1;
-    }
-}
-
-
-/** _block.scss **/
-.block{
-    display: block !important;
-}
-
-
-/** _boxsizing.scss **/
-.boxsizing__borderbox{
-    box-sizing:border-box;
-}
-.boxsizing__contentbox{
-    box-sizing:content-box;
-}
-
-
-/** _scrollable.scss **/
-.scrollable{
-    overflow-y:auto;
-}
-.scrollable--x{
-    overflow-x:auto;
-}
-.no-scroll{
-    overflow:hidden;
-}
-
-
-/** _float.scss **/
-.float__right,
-%float__right{
-    float:right;
-}
-.float__left,
-%float__left{
-    float:left;
-}
-
-
-/** _fonts.scss **/
-.font__bold,
-%font__bold{
-    font-weight:600;
-}
-.font__normal,
-%font__normal{
-    font-weight: 400;
-}
-.font__light,
-%font__light{
-    font-weight: 200;
-}
-.fontColor__primary{
-    color:$primary;
-}
-.fontColor__lightGrey{
-    color:$lightGrey;
-}
-.fontColor__normal{
-    color:$text-color;
-}
-.fontColor__darker{
-    color:$darkGrey;
-}
-.fontColor__white{
-    color:white;
-}
-
-
-/** _forms.scss **/
-.textarea__unstyled{
-    border:none;
-    outline:none;
-    background-color:transparent;
-    color:inherit;
-    resize:none;
-    padding:0;
-    margin:0;
-    width:100%;
-}
-
-
-/** _position.scss **/
-.position__relative{
-    position: relative;
-}
-
-
-/** _margin.scss **/
-.margin__auto{
-    margin:auto;
-}
-
-
-/** _helpers.scss **/
-$steps: (
-    0,5,8,10,
-    11,12,13,14,15,16,17,18,19,20,
-    21,22,23,24,25,26,27,28,29,30,
-    31,32,33,34,35,36,37,38,39,40,
-    41,42,43,44,45,46,47,48,49,50,
-    55,60,64,65,70,72,75,80,85,90,95,96,100,
-    110,120,130,140,150,160,170,180,190,200,
-    210,220,230,240,250,260,270,280,290,300,
-    310,320,330,340,350,360,370,380,390,400,
-    410,420,430,440,450,460,470,480,490,500
-);
-
-
-/*************HELPERS**************/
-/**********************************/
-
-/*** identical width and height ***/
-@each $step in $steps {
-    .wh__#{$step} {
-        width: $step + px !important;
-        height: $step + px !important;
-    }
-    .lh__#{$step} {
-        line-height: $step + px !important;
-    }
-}
-.lh__1{
-    line-height: 1 !important;
-}
-.no-wrap{
-    white-space: nowrap;
-}
-
-.ov-h{
-    overflow: hidden;
-}
-.va-m{
-    vertical-align: middle;
-}
-.bg-inherit{
-    background-color:inherit;
-}
-.bg-black{
-    background-color: black;
-}
-.v-center{
-    &:before {
-        content: '';
-        display: inline-block;
-        height: 100%;
-        vertical-align: middle;
-        margin-top: -0.25em; /* Adjusts for spacing */
-    }
-}
-.fluid-height{
-    height:100%;
-}
-.visibility__hidden{
-    visibility: hidden;
-}
-
-.pointerEvents__none{
-    pointer-events: none;
-}
-
-/* Padding Helpers */
-.pn {
-  padding: 0 !important;
-}
-.p1 {
-  padding: 1px !important;
-}
-.p2 {
-  padding: 2px !important;
-}
-.p3 {
-  padding: 3px !important;
-}
-.p4 {
-  padding: 4px !important;
-}
-.p5 {
-  padding: 5px !important;
-}
-.p6 {
-  padding: 6px !important;
-}
-.p7 {
-  padding: 7px !important;
-}
-.p8 {
-  padding: 8px !important;
-}
-.p10 {
-  padding: 10px !important;
-}
-.p12 {
-  padding: 12px !important;
-}
-.p15 {
-  padding: 15px !important;
-}
-.p20 {
-  padding: 20px !important;
-}
-.p25 {
-  padding: 25px !important;
-}
-.p30 {
-  padding: 30px !important;
-}
-.p35 {
-  padding: 35px !important;
-}
-.p40 {
-  padding: 40px !important;
-}
-.p50 {
-  padding: 50px !important;
-}
-.ptn {
-  padding-top: 0 !important;
-}
-.pt5 {
-  padding-top: 5px !important;
-}
-.pt10 {
-  padding-top: 10px !important;
-}
-.pt15 {
-  padding-top: 15px !important;
-}
-.pt20 {
-  padding-top: 20px !important;
-}
-.pt25 {
-  padding-top: 25px !important;
-}
-.pt30 {
-  padding-top: 30px !important;
-}
-.pt35 {
-  padding-top: 35px !important;
-}
-.pt40 {
-  padding-top: 40px !important;
-}
-.pt50 {
-  padding-top: 50px !important;
-}
-.prn {
-  padding-right: 0 !important;
-}
-.pr5 {
-  padding-right: 5px !important;
-}
-.pr10 {
-  padding-right: 10px !important;
-}
-.pr15 {
-  padding-right: 15px !important;
-}
-.pr20 {
-  padding-right: 20px !important;
-}
-.pr25 {
-  padding-right: 25px !important;
-}
-.pr30 {
-  padding-right: 30px !important;
-}
-.pr35 {
-  padding-right: 35px !important;
-}
-.pr40 {
-  padding-right: 40px !important;
-}
-.pr50 {
-  padding-right: 50px !important;
-}
-.pbn {
-  padding-bottom: 0 !important;
-}
-.pb5 {
-  padding-bottom: 5px !important;
-}
-.pb10 {
-  padding-bottom: 10px !important;
-}
-.pb15 {
-  padding-bottom: 15px !important;
-}
-.pb20 {
-  padding-bottom: 20px !important;
-}
-.pb25 {
-  padding-bottom: 25px !important;
-}
-.pb30 {
-  padding-bottom: 30px !important;
-}
-.pb35 {
-  padding-bottom: 35px !important;
-}
-.pb40 {
-  padding-bottom: 40px !important;
-}
-.pb50 {
-  padding-bottom: 50px !important;
-}
-.pln {
-  padding-left: 0 !important;
-}
-.pl5 {
-  padding-left: 5px !important;
-}
-.pl10 {
-  padding-left: 10px !important;
-}
-.pl15 {
-  padding-left: 15px !important;
-}
-.pl20 {
-  padding-left: 20px !important;
-}
-.pl25 {
-  padding-left: 25px !important;
-}
-.pl30 {
-  padding-left: 30px !important;
-}
-.pl35 {
-  padding-left: 35px !important;
-}
-.pl40 {
-  padding-left: 40px !important;
-}
-.pl50 {
-  padding-left: 50px !important;
-}
-
-/* Axis Padding (both top/bottom or left/right) */
-.pv5 {
-  padding-top: 5px !important;
-  padding-bottom: 5px !important;
-}
-.pv8 {
-  padding-top: 8px !important;
-  padding-bottom: 8px !important;
-}
-.pv10 {
-  padding-top: 10px !important;
-  padding-bottom: 10px !important;
-}
-.pv15 {
-  padding-top: 15px !important;
-  padding-bottom: 15px !important;
-}
-.pv20 {
-  padding-top: 20px !important;
-  padding-bottom: 20px !important;
-}
-.pv25 {
-  padding-top: 25px !important;
-  padding-bottom: 25px !important;
-}
-.pv30 {
-  padding-top: 30px !important;
-  padding-bottom: 30px !important;
-}
-.pv40 {
-  padding-top: 40px !important;
-  padding-bottom: 40px !important;
-}
-.pv50 {
-  padding-top: 50px !important;
-  padding-bottom: 50px !important;
-}
-.ph5 {
-  padding-left: 5px !important;
-  padding-right: 5px !important;
-}
-.ph8 {
-  padding-left: 8px !important;
-  padding-right: 8px !important;
-}
-.ph10 {
-  padding-left: 10px !important;
-  padding-right: 10px !important;
-}
-.ph15 {
-  padding-left: 15px !important;
-  padding-right: 15px !important;
-}
-.ph20 {
-  padding-left: 20px !important;
-  padding-right: 20px !important;
-}
-.ph25 {
-  padding-left: 25px !important;
-  padding-right: 25px !important;
-}
-.ph30 {
-  padding-left: 30px !important;
-  padding-right: 30px !important;
-}
-.ph40 {
-  padding-left: 40px !important;
-  padding-right: 40px !important;
-}
-.ph50 {
-  padding-left: 50px !important;
-  padding-right: 50px !important;
-}
-
-/* margin center helper */
-.mauto {
-  margin-left: auto;
-  margin-right: auto;
-}
-.mn {
-  margin: 0 !important;
-}
-.m1 {
-  margin: 1px !important;
-}
-.m2 {
-  margin: 2px !important;
-}
-.m3 {
-  margin: 3px !important;
-}
-.m4 {
-  margin: 4px !important;
-}
-.m5 {
-  margin: 5px !important;
-}
-.m8 {
-  margin: 8px !important;
-}
-.m10 {
-  margin: 10px !important;
-}
-.m15 {
-  margin: 15px !important;
-}
-.m20 {
-  margin: 20px !important;
-}
-.m25 {
-  margin: 25px !important;
-}
-.m30 {
-  margin: 30px !important;
-}
-.m35 {
-  margin: 35px !important;
-}
-.m40 {
-  margin: 40px !important;
-}
-.m50 {
-  margin: 50px !important;
-}
-.mtn {
-  margin-top: 0 !important;
-}
-.mt5 {
-  margin-top: 5px !important;
-}
-.mt10 {
-  margin-top: 10px !important;
-}
-.mt15 {
-  margin-top: 15px !important;
-}
-.mt20 {
-  margin-top: 20px !important;
-}
-.mt25 {
-  margin-top: 25px !important;
-}
-.mt30 {
-  margin-top: 30px !important;
-}
-.mt35 {
-  margin-top: 35px !important;
-}
-.mt40 {
-  margin-top: 40px !important;
-}
-.mt50 {
-  margin-top: 50px !important;
-}
-.mt70 {
-  margin-top: 70px !important;
-}
-.mrn {
-  margin-right: 0 !important;
-}
-.mr5 {
-  margin-right: 5px !important;
-}
-.mr10 {
-  margin-right: 10px !important;
-}
-.mr15 {
-  margin-right: 15px !important;
-}
-.mr20 {
-  margin-right: 20px !important;
-}
-.mr25 {
-  margin-right: 25px !important;
-}
-.mr30 {
-  margin-right: 30px !important;
-}
-.mr35 {
-  margin-right: 35px !important;
-}
-.mr40 {
-  margin-right: 40px !important;
-}
-.mr50 {
-  margin-right: 50px !important;
-}
-.mbn {
-  margin-bottom: 0 !important;
-}
-.mb5 {
-  margin-bottom: 5px !important;
-}
-.mb10 {
-  margin-bottom: 10px !important;
-}
-.mb15 {
-  margin-bottom: 15px !important;
-}
-.mb20 {
-  margin-bottom: 20px !important;
-}
-.mb25 {
-  margin-bottom: 25px !important;
-}
-.mb30 {
-  margin-bottom: 30px !important;
-}
-.mb35 {
-  margin-bottom: 35px !important;
-}
-.mb40 {
-  margin-bottom: 40px !important;
-}
-.mb50 {
-  margin-bottom: 50px !important;
-}
-.mb70 {
-  margin-bottom: 70px !important;
-}
-.mln {
-  margin-left: 0 !important;
-}
-.ml5 {
-  margin-left: 5px !important;
-}
-.ml10 {
-  margin-left: 10px !important;
-}
-.ml15 {
-  margin-left: 15px !important;
-}
-.ml20 {
-  margin-left: 20px !important;
-}
-.ml25 {
-  margin-left: 25px !important;
-}
-.ml30 {
-  margin-left: 30px !important;
-}
-.ml35 {
-  margin-left: 35px !important;
-}
-.ml40 {
-  margin-left: 40px !important;
-}
-.ml50 {
-  margin-left: 50px !important;
-}
-
-/* Axis Margins (both top/bottom or left/right) */
-.mv5 {
-  margin-top: 5px !important;
-  margin-bottom: 5px !important;
-}
-.mv10 {
-  margin-top: 10px !important;
-  margin-bottom: 10px !important;
-}
-.mv15 {
-  margin-top: 15px !important;
-  margin-bottom: 15px !important;
-}
-.mv20 {
-  margin-top: 20px !important;
-  margin-bottom: 20px !important;
-}
-.mv25 {
-  margin-top: 25px !important;
-  margin-bottom: 25px !important;
-}
-.mv30 {
-  margin-top: 30px !important;
-  margin-bottom: 30px !important;
-}
-.mv40 {
-  margin-top: 40px !important;
-  margin-bottom: 40px !important;
-}
-.mv50 {
-  margin-top: 50px !important;
-  margin-bottom: 50px !important;
-}
-.mv70 {
-  margin-top: 70px !important;
-  margin-bottom: 70px !important;
-}
-.mh5 {
-  margin-left: 5px !important;
-  margin-right: 5px !important;
-}
-.mh10 {
-  margin-left: 10px !important;
-  margin-right: 10px !important;
-}
-.mh15 {
-  margin-left: 15px !important;
-  margin-right: 15px !important;
-}
-.mh20 {
-  margin-left: 20px !important;
-  margin-right: 20px !important;
-}
-.mh25 {
-  margin-left: 25px !important;
-  margin-right: 25px !important;
-}
-.mh30 {
-  margin-left: 30px !important;
-  margin-right: 30px !important;
-}
-.mh40 {
-  margin-left: 40px !important;
-  margin-right: 40px !important;
-}
-.mh50 {
-  margin-left: 50px !important;
-  margin-right: 50px !important;
-}
-.mh70 {
-  margin-left: 70px !important;
-  margin-right: 70px !important;
-}
-
-/* Negative Margin Helpers */
-.mtn5 {
-  margin-top: -5px !important;
-}
-.mtn10 {
-  margin-top: -10px !important;
-}
-.mtn15 {
-  margin-top: -15px !important;
-}
-.mtn20 {
-  margin-top: -20px !important;
-}
-.mtn30 {
-  margin-top: -30px !important;
-}
-.mrn5 {
-  margin-right: -5px !important;
-}
-.mrn10 {
-  margin-right: -10px !important;
-}
-.mrn15 {
-  margin-right: -15px !important;
-}
-.mrn20 {
-  margin-right: -20px !important;
-}
-.mrn30 {
-  margin-right: -30px !important;
-}
-.mbn5 {
-  margin-bottom: -5px !important;
-}
-.mbn10 {
-  margin-bottom: -10px !important;
-}
-.mbn15 {
-  margin-bottom: -15px !important;
-}
-.mbn20 {
-  margin-bottom: -20px !important;
-}
-.mbn30 {
-  margin-bottom: -30px !important;
-}
-.mln5 {
-  margin-left: -5px !important;
-}
-.mln10 {
-  margin-left: -10px !important;
-}
-.mln15 {
-  margin-left: -15px !important;
-}
-.mln20 {
-  margin-left: -20px !important;
-}
-.mln30 {
-  margin-left: -30px !important;
-}
-
-/* Vertical Negative Margin "mv" + "n" + "x" */
-.mvn5 {
-  margin-top: -5px !important;
-  margin-bottom: -5px !important;
-}
-.mvn10 {
-  margin-top: -10px !important;
-  margin-bottom: -10px !important;
-}
-.mvn15 {
-  margin-top: -15px !important;
-  margin-bottom: -15px !important;
-}
-.mvn20 {
-  margin-top: -20px !important;
-  margin-bottom: -20px !important;
-}
-.mvn30 {
-  margin-top: -30px !important;
-  margin-bottom: -30px !important;
-}
-
-/* Horizontal Negative Margin "mh" + "n" + "x" */
-.mhn5 {
-  margin-left: -5px !important;
-  margin-right: -5px !important;
-}
-.mhn10 {
-  margin-left: -10px !important;
-  margin-right: -10px !important;
-}
-.mhn15 {
-  margin-left: -15px !important;
-  margin-right: -15px !important;
-}
-.mhn20 {
-  margin-left: -20px !important;
-  margin-right: -20px !important;
-}
-.mhn30 {
-  margin-left: -30px !important;
-  margin-right: -30px !important;
-}
-
-/* Vertical Align Helpers */
-.va-t {
-  vertical-align: top !important;
-}
-.va-m {
-  vertical-align: middle !important;
-}
-.va-b {
-  vertical-align: bottom !important;
-}
-.va-s {
-  vertical-align: super !important;
-}
-
-/* Text Helpers */
-.text-left {
-  text-align: left !important;
-}
-.text-right {
-  text-align: right !important;
-}
-.text-center {
-  text-align: center !important;
-}
-.text-justify {
-  text-align: justify !important;
-}
-.text-nowrap {
-  white-space: nowrap !important;
-}
-
-/* Inline Block Helper */
-.ib,
-.inline-object {
-  display: inline-block !important;
-}
-
-.clear {
-  clear: both;
-}
-
-// warning popup
-
-.wvWarning {
-  position: relative;
-  width: 320px;
-  min-height: 130px;
-  z-index: 999;
-  left: calc(50% - 160px);
-  border: #000 solid 1px;
-  -webkit-border-radius: 7px;
-  -moz-border-radius: 7px;
-  border-radius: 7px;
-  color: #FF5722;
-  box-shadow: 0px 3px 23px #ff980078;
-  -webkit-animation-name: example;  /* Safari 4.0 - 8.0 */
-  -webkit-animation-duration: 3s;  /* Safari 4.0 - 8.0 */    
-  -webkit-animation-fill-mode: both; /* Safari 4.0 - 8.0 */
-  animation-name: example;
-  animation-duration: 2s;    
-  animation-fill-mode: both;
-  animation-timing-function: ease-out;
-}
-
-@-webkit-keyframes example {
-    from {top: 0vh;opacity: 0;background: #868686}
-    to {top: 10vh;opacity: 1;background: #ffffff}
-}
-
-@keyframes example {
-  from {top: 0vh;opacity: 0;background: #868686}
-    to {top: 10vh;opacity: 1;background: #ffffff}
-}
-
-.wvWarning-content{
-  position: relative;
-  width: 190px;
-  min-height: 88px;
-  max-height: 80vh;
-  margin: auto;
-}
-.wvWarning-icon{
-  font-size: 32px;
-}
-.wvWarning-text{
-  position: relative;
-}
-.wvWarning-button{
-  background-color: #f1ededcc;
-  color: #607D8B;
-  width: 50px;
-  font-weight: 600;
-  margin-top: 2px;
-  margin-right: 30px;
-}
-
-.wvScreenToSmallWarning {
-  position: fixed;
-  display: block;
-  top: 0;
-  left: 0;
-  background-color: white;
-  color: #333;
-  width: 100%;
-  height: 100%;
-  z-index: 1000;
-}
-
-.wvScreenToSmallWarning-content {
-  padding: 10px;
-  text-align: center;
-}
-
-/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
-@media only screen and (min-width: 550px) and (min-height: 280px) { 
-  .wvScreenToSmallWarning {
-    display: none;
-  }
-}
-
-/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
-@media only screen and (min-width: 280px) and (min-height: 550px)  {
-  .wvScreenToSmallWarning {
-    display: none;
-  }
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_layout.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,498 +0,0 @@
-$topHeight: 42px;
-$bottomHeightSmall: 7rem; // On small width, we provide two-lines bottom zone to compensate the smaller width
-$bottomHeightLarge: 5rem; // On large width, we provide one-line bottom zone
-
-$asideWidth: 32rem;
-$asideMinifyWidth: 12rem;
-
-$asideRightMinifyWidth: 85px; // eq. 7.5rem * 12px - ( $asideRightPadding / 2 )
-$asideRightPadding: 10px;
-
-/* layout: left section */
-
-// Adapt left aside based on state (opened/closed).
-.wvLayoutLeft {
-    // Set general properties.
-    position:absolute;
-    z-index:2;
-    background-color:black;
-    width: $asideWidth;
-
-    // Position the left side below the top zone if it is shown
-    &.wvLayoutLeft--toppadding {
-        top: $topHeight;
-    }
-    &:not(.wvLayoutLeft--toppadding) {
-        top: 0;
-    }
-
-    // Position the left section over the bottom one if the latter is shown
-    &.wvLayoutLeft--bottompadding {
-        @media screen and (max-device-width: 374px) {
-            bottom: $bottomHeightSmall;
-        }
-        @media screen and (min-device-width: 375px) {
-            bottom: $bottomHeightLarge;
-        }
-    }
-    &:not(.wvLayoutLeft--bottompadding) {
-        bottom: 0;
-    }
-
-    // Position the left side on the left
-    left: 0;
-
-    // When layout left is shown, nothing special happens (default state)
-    &:not(.wvLayoutLeft--closed) {
-    }
-
-    // When layout left is closed, move it aside
-    &.wvLayoutLeft--closed {
-        transform: translateX(- $asideWidth); // Move all out of the screen
-        &.wvLayoutLeft--small {
-            transform: translateX(-$asideMinifyWidth);
-        }
-    }
-    &.wvLayoutLeft--small{
-        width: $asideMinifyWidth;
-        & .wvLayoutLeft__contentTop, & .wvLayoutLeft__contentMiddle, & .wvLayoutLeft__contentBottom{
-            width: 100%;
-        }
-    }
-}
-
-// Layout-Left Flexbox containers for the content.
-.wvLayoutLeft__content {
-    border-right: 1px solid #AAA;
-
-    // Display flex mode so optional actions can be positionned at the bottom
-    // side.
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-
-    // Make it scrollable.
-    overflow-y: auto;
-    height: 100%;
-}
-
-.wvLayoutLeft__contentTop {
-    // We have to set a static height since we use floating to make white space
-    // disapear in nested content.
-    // note: could be deactivate with the clearfix so we can have a dynamic height
-    // max-height: 6rem;
-    padding: 0rem 1rem 0rem 1rem;
-
-    // Enforce width even if there is a scrollbar on win/IE11 (-1 px for
-    // border).
-    width: $asideWidth - 0.1rem;
-
-    &:after{
-        content: "";
-        display:block;
-        height:0;
-        width:0;
-        clear:both;
-    }
-}
-
-.wvLayoutLeft__contentMiddle {
-    // Let the middle zone take as much space as required.
-    flex: 1 0 auto;
-
-    // Enforce width even if there is a scrollbar on win/IE11 (-1 px for
-    // border).
-    width: $asideWidth - 0.1rem;
-
-}
-
-.wvLayoutLeft__contentBottom {
-    // Enforce width even if there is a scrollbar on win/IE11 (-1 px for
-    // border).
-    width: $asideWidth - 0.1rem;
-}
-.wvLayout__leftBottom.wvLayout__leftBottom--enabled {
-    border-top: 1px solid hsla(0, 0%, 100%, 0.2);
-    margin-top: 1rem;
-    padding: 1rem;
-
-    // Prevent from taking more space than intended.
-    // flex-grow: 0;
-}
-
-.wvLayoutLeft__actions,
-%wvLayoutLeft__actions{
-    display:block;
-    position:absolute;
-    right:1px; // border
-    top: 50%;
-    transform: translateY(-50%);
-    width:25px;
-}
-.wvLayoutLeft__actions--outside{
-    @extend.wvLayoutLeft__actions;
-    right:-25px; // width + border
-}
-.wvLayoutLeft__action{
-    background-color:$primary;
-    opacity: 0.5;
-    color:white;
-    transition: none;
-    &:hover, &:focus{
-        opacity: 1;
-    }
-}
-
-
-/* layout: right section */
-
-// Adapt right aside based on state (opened/closed).
-.wvLayout__right {
-    display:block;
-    position:absolute;
-    z-index:2;
-    background-color:black;
-    width: $asideRightMinifyWidth;
-
-    // Position the left side below the top zone if it is shown
-    &.wvLayout__right--toppadding {
-        top: $topHeight;
-    }
-    &:not(.wvLayout__right--toppadding) {
-        top: 0;
-    }
-
-    // Position the right section over the bottom one if the latter is shown
-    &.wvLayout__right--bottompadding {
-        @media screen and (max-device-width: 374px) {
-            bottom: $bottomHeightSmall;
-        }
-        @media screen and (min-device-width: 375px) {
-            bottom: $bottomHeightLarge;
-        }
-    }
-    &:not(.wvLayout__right--bottompadding) {
-        bottom: 0;
-    }
-
-    // Position the right side on the right
-    right: 0;
-
-    // When layout right is shown, nothing special happens (default state)
-    &:not(.wvLayout__right--closed) {
-    }
-
-    // When layout right is closed, move it aside
-    &.wvLayout__right--closed {
-        transform: translateX(+ $asideRightMinifyWidth); // Move all out of the screen
-    }
-
-    // Set childrens to full height (so border-left appears at 100% height)
-    & > wv-layout-right,
-    & > wv-layout-right > .wvViewer__asideRight
-    {
-        display: block;
-        height: 100%;
-        width: 100%;
-    }
-}
-
-.wvAsideRight__content {
-    height: 100%;
-    float: left;
-
-    border-left: 1px solid #AAA;
-
-    padding: 0 $asideRightPadding/2;
-    width: $asideWidth;
-}
-
-.wvAsideRight__actions,
-%wvAsideRight__actions{
-    display:block;
-    position:absolute;
-    left:1px; // border
-    top: 50%;
-    transform: translateY(-50%);
-    width:25px;
-
-    // Compensate aside z-index to let user click on button when another button
-    // is behind the actions.
-    z-index: 3;
-}
-.wvAsideRight__actions--outside{
-    @extend.wvAsideRight__actions;
-    left:-25px; // width + border
-}
-.wvAsideRight__action{
-    background-color:$primary;
-    opacity: 0.5;
-    color:white;
-    transition: none;
-    &:hover, &:focus{
-        opacity: 1;
-    }
-}
-.wvAsideRight__fixOpenFullyTooltip + .tooltip { // Fix the "open fully" bad tooltip placement of the asideRight
-    left: -6.633em !important;
-    top: 1px !important;
-}
-
-
-/* layout: bottom section */
-
-// Set bottom section size & position
-.wvLayout__bottom {
-    position: absolute;
-
-    // Display the bottom bar in the bottom side
-    @media screen and (max-device-width: 374px) {
-        height: $bottomHeightSmall;
-    }
-    @media screen and (min-device-width: 375px) {
-        height: $bottomHeightLarge;
-    }
-
-    left: 0;
-    bottom: 0;
-    right: 0;
-
-    // Set grey background color (as it is only used to display notices)
-    background-color: hsl(0, 0%, 10%);
-}
-
-
-/* layout: main section */
-
-// Set main section size & position
-.wvLayout__main {
-    position: absolute;
-
-    // Align content (such as toolbar)
-    text-align: center;
-
-    // Position splitpane considering the toolbar when toolbar is present.
-    & .wvLayout__splitpane--toolbarAtTop {
-        display: block;
-        height: calc(100% - #{$toolbarHeight});
-        width: 100%;
-
-        position: relative;
-        top: $toolbarHeight;
-    }
-    & .wvLayout__splitpane--toolbarAtRight {
-        display: block;
-        height: 100%;
-        width: calc(100% - #{$toolbarHeight});
-    }
-
-    & .wvLayout__splitpane--bigToolbarAtTop {
-        display: block;
-        height: calc(100% - 68px);
-        width: 100%;
-
-        position: relative;
-        top: 68px;
-    }
-    & .wvLayout__splitpane--bigToolbarAtRight {
-        display: block;
-        height: 100%;
-        width: calc(100% - 68px);
-    }
-
-    // Position the main section below the top zone if the latter is shown
-    &.wvLayout__main--toppadding {
-        top: $topHeight;
-    }
-    &:not(.wvLayout__main--toppadding) {
-        top: 0;
-    }
-
-    // Position the main section over the bottom one if the latter is shown
-    &.wvLayout__main--bottompadding {
-        bottom:440px;
-        @media screen and (max-device-width: 374px) {
-            bottom: $bottomHeightSmall;
-        }
-        @media screen and (min-device-width: 375px) {
-            bottom: $bottomHeightLarge;
-        }
-    }
-    &:not(.wvLayout__main--bottompadding) {
-        bottom: 0;
-    }
-
-    // Make the main content fill the screen by default
-    // Depending on the browser, the left and right attributes are more
-    // optimized than padding's ones. The reason is that they based upon
-    // absolute positioning. The require no contextual positioning calculation
-    // and are less performance intensive, especially concidering transition
-    // animation.
-    right: 0;
-    left: 0;
-
-    // transition: 0.6s left ease, 0.6s right ease;
-
-    // Adapt main section's size based on aside left's state (opened/closed)
-    // 1. When aside left is not hidden , move the main section 300 pixel to
-    //   the right
-    &.wvLayout__main--leftpadding {
-        left: $asideWidth;
-    }
-    // 2. When aside left is hidden, let the main take 100% of the place
-    &:not(.wvLayout__main--leftpadding, .wvLayout__main--smallleftpadding) {
-        left: 0px;
-    }
-    &.wvLayout__main--smallleftpadding {
-        left: $asideMinifyWidth;
-    }
-
-    // Adapt main section's size based on aside right's state (opened/closed)
-    // 1. When aside right is not hidden , move the main section 84 pixel to
-    //   the left
-    &.wvLayout__main--rightpadding {
-        right: $asideRightMinifyWidth;
-    }
-    // 2. When aside right is hidden, let the main take 100% of the place
-    &:not(.wvLayout__main--rightpadding) {
-        right: 0px;
-    }
-}
-
-/* global */
-.popover {
-    // Set back black as default popover text color
-    color: black;
-}
-
-.wvViewer__editor--full{
-    position:absolute;
-    top:0;
-    right:0;
-    z-index:10;
-    opacity:0;
-    transform: translateX(100%);
-    width:100%;
-    height:100%;
-    background-color:white;
-    color:$text-color;
-    &.opened{
-        opacity:1;
-        transform: translateX(0);
-    }
-}
-
-.wvViewer__topBar{
-    width:100%;
-    // margin-top: 0.5rem;
-
-    // Allow user to scroll through the toolbar if screen is too small. Note we
-    // can't use z-index properly to show buttons on top of the viewer, as any
-    // popover will appear behind them (even with higher z-index) due to an
-    // overflow property hidden somewhere.
-    overflow-y: auto;
-    white-space: nowrap;
-    max-width: 100%;
-}
-.wvViewer__buttonGroup{
-    display:inline-block;
-}
-.wvViewer__buttonGroup--asideWidth{
-    width: $asideWidth;
-    padding-right: 1rem;
-}
-.wvViewer__buttonGroup--contentWidth{
-    width: calc(100% - #{$asideWidth});
-    padding-left: 1rem;
-    max-height: 4.2rem; // Make sure mobile keeps the menubar below a certain size
-}
-.wvViewer__iframe{
-    position:absolute;
-    left:0;
-    top:0;
-}
-
-/* bottom bar */
-.wvViewer__bottomBar,
-%wvViewer__bottomBar{
-    position:absolute;
-    left:0;
-    bottom:0;
-    width:100%;
-    background-color:#111111;
-}
-
-.wvViewer__bottomBar--expanded{
-    @extend .wvViewer__bottomBar;
-    height: 80px; //total height of the last serieList cell (64 + 10(margin bottom previous item) + item padding bottom +1 border-width (top item)
-    //border-top: 1px solid rgba(255,255,255,0.1);
-    color:white;
-
-    .wvViewer__timeline{
-        width: calc(100% - 80px);
-    }
-    .wvTimeline__hotspots{
-        bottom: -40px;
-    }
-}
-
-.wvViewer__bottomBar--minimized{
-    @extend .wvViewer__bottomBar;
-    color: white;
-
-    padding-top: 0.5rem;
-    padding-bottom: 0.5rem;
-    padding-left: 2.5rem;
-
-    .wvTimeline__hotspot{
-        top: -40px;
-        opacity:0;
-        visibility:hidden;
-        z-index:-1;
-        // transition: all 0.3s ease 0.6s; //adding a delay when mouse leave
-        // transition-property: opacity, visibility, z-index;
-    }
-    &:hover .wvTimeline__hotspot{
-        opacity:1;
-        visibility: visible;
-        z-index:5;
-        transition-delay: 0s;
-    }
-}
-
-.wvViewer__timeline{
-    height:24px;
-    //background-color:rgba(1,1,1,0.2);
-    line-height: 24px;
-    vertical-align: middle;
-    width:100%;
-}
-
-.wvViewer__trademark{
-    display:inline-block;
-    float:right;
-    width:80px;
-    height:80px;
-    float:right;
-    line-height: 80px;
-    vertical-align: middle;
-    text-align: center;
-}
-.wvTimeline__action--text{
-
-}
-.wvTimeline__input{
-    border-radius: 3px;
-    &:focus{
-        outline:none;
-    }
-    margin-top:2px;
-    border: 1px solid $border-color;
-}
-
-.wvTimeline__actions{
-    display:inline-block;
-    border-right: 1px solid $border-color;
-}
-.wvTimeline__wrapper{
-}
--- a/StoneWebViewer/Resources/Styles/_notice.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-wv-notice {
-    display: block;
-    height: 100%;
-    width: 100%;
-}
-
-.wvNotice {
-    // Set padding
-    padding: 0.5rem 0.25rem;
-
-    // Fill parent element so text can be centered
-    height: 100%;
-}
-
-.wvNotice__text {
-    // Center text 
-    position: relative;
-    top: 50%;
-    transform: translateY(-50%);
-    text-align: center;
-    margin-left: 1rem;
-
-    // Text style
-    font-weight: 400;
-    color: hsl(0, 0%, 70%);
-
-    // Keep space for button
-    float: left;
-    width: calc(100% - 7rem); // 3.5 rem + 3 rem margin (incl. button margin + text margin)
-}
-
-.wvNotice__closeButton {
-    // Position button on right
-    float: right;
-    margin-right: 0.5em;
-
-    // Center button vertically
-    position: relative;
-    top: 50%;
-    transform: translateY(-50%);
-
-    // Set button size
-    width: 3.5rem;
-    height: 2.5rem; // half the bottom zone height
-
-    // Configure button icon
-    text-align: center;
-    font-size: 1em;
-    font-weight: 100;
-    line-height: 2.2rem;
-
-    // Set button style
-    cursor: pointer;
-    border: 1px solid hsl(0, 0%, 27%);
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_print.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-.wvPrintExclude{
-    display:none;
-}
-
-.wvPrintFullPage{
-    width: 100% !important;
-    height: 100% !important;
-    position: absolute !important;
-    top: 0 !important;
-    left: 0 !important;
-    display:block !important;
-}
-
-.wvLayout__main{
-    top: 0 !important;
-    right: 0 !important;
-    left: 0 !important;
-    bottom: 0 !important;
-}
-
-.wvPrintViewer{
-    width: 100%;
-    height:100%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-}
-
-.wvPrintViewer canvas{
-    max-width: 100% !important;
-    max-height: 100% !important;
-    margin:auto;
-}
-
-.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft{
-    &, & * {
-        background-color: black !important;
-        -webkit-print-color-adjust: exact !important; 
-        color-adjust: exact !important;
-        color: orange !important;
-    }
-}
-.tooltip{
-    display: none !important;
-}
-body{
-    margin: 0;
-    padding: 0;
-    position: relative;
-    &, *{
-        background-color: black !important;
-        -webkit-print-color-adjust: exact !important; 
-    }
-    width: 8.5in;
-    height: 11in;
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_selectionActionlist.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.wvSelectionActionlist {
-    display: block;
-
-    text-align: center;
-}
-
-.wvSelectionActionlist__bottom {
-    
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_serieslist.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-$gray: gray;
-$blue: hsla(204, 70%, 53%, 0.7);
-$red: rgba(206, 0, 0, 0.7);
-$green: rgba(0, 160, 27, .7);
-$yellow: rgba(220, 200  , 0, .9);
-$violet: rgba(255, 31, 255, .7);
-
-$borderColor: rgba(255, 255, 255, 0.8);
-$borderColorActive: rgba(255, 255, 255, 0.6);
-$borderColorHighlighted: rgba(255, 255, 255, 1);
-$pictureSize: 6.5rem;
-
-.wvSerieslist {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-}
-
-.wvSerieslist__seriesItem--selectable {
-    // Pointer cursor (for `ng-click`)
-    cursor: pointer !important;
-
-    // Lighten up the icon on hover
-    &:hover {
-        color: white;
-    }
-}
-
-.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa { // Make sure it has precedence over .fa class {
-    position: absolute;
-
-    // Fill the li element
-    width: 100%;
-    height: 100%;
-
-    // Fill the li element with the fontawesome icon
-    font-size: $pictureSize/2;
-    line-height: $pictureSize;
-    text-align: center;
-}
-
-.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa { // Make sure it has precedence over .fa class
-    // Grey out (since no report is available)
-    color: #c3c3c3;
-
-    // Diagonal line crossing report icon (to tell none are available)
-    // position: relative;
-
-    &::after { // use after to not conflicts with font-awesome :before
-        position: absolute;
-
-        left: 0;
-        top: 50%;
-        right: 0;
-
-        transform: rotate(-45deg) scaleX(0.9);
-
-        border-top: 5px solid;
-        border-color: inherit;
-
-        content: "";
-    }
-}
-
-.wvSerieslist__picture{
-    display: inline-block;
-    font-size: 14px;
-    width: $pictureSize;
-    height: $pictureSize;
-    position: relative;
-
-    // Move picture behind the `toggle layout@ left` button.
-    z-index: -1;
-}
-.wvSerieslist__badge {
-    position: absolute;
-    bottom:5px;
-    right:5px;
-    font-size:10px;
-    line-height:15px;
-    width:15px;
-    height:15px;
-    border-radius: 100%;
-    background-color: $gray;
-    vertical-align: middle;
-    text-align: center;
-    font-weight: bold;
-}
-.wvSerieslist__information{
-    font-size: 14px;
-    float: right;
-    padding-left: 1rem;
-    width: calc(100% - #{$pictureSize});
-    height: $pictureSize;
-}
-.wvSerieslist__label{
-    white-space: nowrap;
-    width:calc(100% - 10px);
-    overflow:hidden;
-    height:$pictureSize/2;
-    line-height:$pictureSize/2;
-    vertical-align: middle;
-}
-.wvSerieslist__timeline{
-    //border-top: 0.1rem solid rgba(255,255,255,0.2);
-    height:$pictureSize/2;
-    line-height:$pictureSize/2;
-    vertical-align: middle;
-}
-
-.wvSerieslist__seriesItem {
-    // anchor
-    position: relative;
-
-    // unstyle list
-    padding-left: 0;
-    list-style: none;
-    font-size: 0;
-
-    // mimic on hover border for draggable
-    border-right: 0.2rem solid transparent;
-    border-left: 0.2rem solid transparent;
-    border-top: 0.2rem solid transparent;
-    border-bottom: 0.2rem solid transparent;
-    border-corner-shape: notch;
-
-    line-height: 0px;
-    margin: 0.1rem;
-
-    &.active{
-        border-color: $borderColorActive;
-        border-style: solid;
-    }
-
-    &.highlighted{
-        border-color: $borderColorHighlighted;
-        border-style: solid;
-    }
-
-    &:hover, &:focus, &.focused{
-        border-style: dashed;
-        border-color: $borderColor;
-    }
-}
-
-.wvSerieslist__seriesItem--list {
-    display: block;
-}
-.wvSerieslist__seriesItem--grid {
-    display: inline-block;
-}
-.wvSerieslist__seriesItem--oneCol{
-    text-align: center;
-}
-
-.wvSerieslist__seriesItem--activated,
-.wvSerieslist__videoItem--activated,
-.wvSerieslist__pdfItem--activated {
-    border: 0.2rem solid hsla(204, 70%, 53%, 1) !important;
-}
-
-// Color related modifiers
-.wvSerieslist__badge--blue {
-    @extend .wvSerieslist__badge;
-    background-color: $blue;
-}
-.wvSerieslist__badge--red {
-    @extend .wvSerieslist__badge;
-    background-color: $red;
-}
-.wvSerieslist__badge--green {
-    @extend .wvSerieslist__badge;
-    background-color: $green;
-}
-.wvSerieslist__badge--yellow {
-    @extend .wvSerieslist__badge;
-    background-color: $yellow;
-}
-.wvSerieslist__badge--violet {
-    @extend .wvSerieslist__badge;
-    background-color: $violet;
-}
--- a/StoneWebViewer/Resources/Styles/_studyInformationBreadcrumb.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-.wvStudyInformationBreadcrumb {
-}
-
-.wvStudyInformationBreadcrumb__patient {
-    display: inline-block;
-    background-color: rgba(255, 255, 255, 0.15);
-    padding: 0.2rem 1rem 0.3rem 1rem;
-    text-align: center;
-    font-size: 1em;
-    margin: 0.6rem;
-    font-weight: 400;
-    line-height: 2.3rem;
-
-    // Prevent doubled margin with the next item
-    margin-right: 0;
-}
-.wvStudyInformationBreadcrumb__patientName {
-
-}
-.wvStudyInformationBreadcrumb__patientBirthDate {
-
-}
-
-.wvStudyInformationBreadcrumb__study {
-    display: inline-block;
-    background-color: rgba(255, 255, 255, 0.15);
-    padding: 0.2rem 1rem 0.3rem 1rem;
-    text-align: center;
-    font-size: 1em;
-    margin: 0.6rem;
-    font-weight: 400;
-    line-height: 2.3rem;
-}
-.wvStudyInformationBreadcrumb__studyDescription {
-
-}
-.wvStudyInformationBreadcrumb__studyDate {
-
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_studyIsland.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-$gray: gray;
-$blue: hsla(204, 70%, 53%, 0.7);
-$red: rgba(206, 0, 0, 0.7);
-$green: rgba(0, 160, 27, .7);
-$yellow: rgba(220, 200  , 0, .9);
-$violet: rgba(255, 31, 255, .7);
-
-
-%wvStudyIsland {
-    margin: 1rem 1rem 1rem 1rem;
-    border: 0.3rem solid $gray;
-}
-
-%wvStudyIsland__header {
-    background-color: $gray;
-    padding: 0.5rem 0.5rem 0.8rem 0.5rem;
-    line-height: 1.35rem;
-    width: 100%;
-}
-.wvStudyIsland__actions {
-    float: right;
-
-    // Compensate header padding (since the inner download study button
-    // actually has margin).
-    margin-top: -0.8rem;
-    margin-right: -0.8rem;
-}
-
-.wvStudyIsland__actions--oneCol {
-    float: none;
-    text-align: center;
-}
-
-.wvStudyIsland__main {
-    padding: 0.4rem; // 0.7rem - 0.3rem (2px transparent border + 1px margin)
-    color: hsl(0, 0%, 100%);
-    width: 100%; // let some space for the 4-columns based items
-}
-
-
-// Color modifiers
-.wvStudyIsland--blue {
-    @extend %wvStudyIsland;
-    border-color: $blue;
-}
-
-.wvStudyIsland__header--blue {
-    @extend %wvStudyIsland__header;
-    background-color: $blue;
-}
-
-.wvStudyIsland--red {
-    @extend %wvStudyIsland;
-    border-color: $red;
-}
-
-.wvStudyIsland__header--red {
-    @extend %wvStudyIsland__header;
-    background-color: $red;
-}
-
-.wvStudyIsland--green {
-    @extend %wvStudyIsland;
-    border-color: $green;
-}
-
-.wvStudyIsland__header--green {
-    @extend %wvStudyIsland__header;
-    background-color: $green;
-}
-
-.wvStudyIsland--yellow {
-    @extend %wvStudyIsland;
-    border-color: $yellow;
-}
-
-.wvStudyIsland__header--yellow {
-    @extend %wvStudyIsland__header;
-    background-color: $yellow;
-}
-
-.wvStudyIsland--violet {
-    @extend %wvStudyIsland;
-    border-color: $violet;
-}
-
-.wvStudyIsland__header--violet {
-    @extend %wvStudyIsland__header;
-    background-color: $violet;
-}
--- a/StoneWebViewer/Resources/Styles/_toolbar.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-.wvToolbar {
-    position: absolute;
-}
-
-.wvToolbar--top {
-    top: 0;
-    height: $toolbarHeight;
-
-    // Position the toolbar to the right (even if it's positioned
-    // horizontally).
-    right: 0;
-    text-align: right;
-
-    // Allow user to scroll through the toolbar if screen is too small. Note we
-    // can't use z-index properly to show buttons on top of the viewer, as any
-    // popover will appear behind them (even with higher z-index) due to an
-    // overflow property hidden somewhere.
-    // overflow-y: auto;
-    white-space: nowrap;
-    max-width: 100%;
-}
-
-.wvToolbar--right {
-    right: 0;
-    width: 42px; // != $toolbarHeight since we're in the reverse order.
-
-    // Allow user to scroll through the toolbar if screen is too small.
-    // overflow-x: auto;
-    height: 100%;
-    z-index: 2;
-    &.wvToolbar--big{
-        width: 68px;
-    }
-}
-
-/* Splitpane Grid Configuration */
-
-.wvToolbar__splitpaneConfigPopover {
-    // Prevent white space between buttons.
-    font-size: 0;
-}
-
-.wvToolbar__splitpaneConfigNotice {
-    font-size: 1.25rem;
-    font-style: italic;
-    text-align: center;
-
-    color: #333;
-}
-
-input[type="radio"].wvToolbar__splitpaneConfigButtonInput {
-    // Hide the radio input, but make it fit the label, so we can rely on its
-    // html caracteristics without having to suffer from its design.
-    position: absolute;
-    width: 0;
-    height: 0;
-    left: 0;
-    top: 0;
-    bottom: 2px;
-    right: 0;
-    opacity: 0;
-}
-
-/* Windowing Preset */
-
-.wvToolbar__windowingPresetConfigPopover {
-
-}
-.wvToolbar__windowingPresetConfigNotice {
-    font-size: 1.25rem;
-    font-style: italic;
-    text-align: center;
-
-    color: #333;
-}
-
-.wvToolbar__windowingPresetList {
-    list-style: none;
-    margin: 0;
-    padding: 0;
-
-    font-size: 1.5rem;
-}
-.wvToolbar__windowingPresetListItem {
-    // Remove <a> default styles. Take care - this class may either be used
-    // with <a> or <button>.
-    &:hover {
-        text-decoration: none;
-        color: white;
-    }
-
-    // Remove <button> default styles.
-    outline: none;
-    background-color: transparent;
-    border: none;
-
-    // Set relative to position button absolutely
-    position: relative;
-
-    // Style button
-    display: inline-block;
-    cursor: pointer;
-    font-variant: small-caps;
-    text-transform: lowercase;
-    text-align: center;
-    font-size: 1.3rem;
-    font-weight: 400;
-    line-height: 2.2rem;
-    color: hsl(0, 0%, 85%);
-    transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
-
-    // Position button
-    margin: 0;
-    min-width: 3rem;
-    padding: 0 10px;
-    line-height: 3.6rem;
-
-
-
-    // Prevent multi line buttons.
-    max-height: 2.8rem;
-    max-width: 100%;
-    overflow: hidden;
-
-    // Set margin
-    margin: 0.6rem;
-    margin-left: 0rem;
-    margin-right: 0rem;
-    & + & {
-        margin-left: 0.7rem;
-    }
-
-    // Set button size
-    line-height: 2rem;
-
-    // Align text
-    padding-top: 0.1rem;
-    padding-bottom: 0.5rem;
-
-    // Style button
-    font-size: 1.4rem;
-    border: 1px solid hsl(0, 0%, 27%);
-
-    // Set best looking font with small-caps.
-    font-family: Arial;
-
-    // Change background on hover
-    background-color: hsl(0, 0%, 0%);
-    &:hover {
-        background-color: hsl(0, 0%, 10%);
-    }
-
-    & > .glyphicon { // used with the same element as glyphicons
-        // Position button
-        position: relative;
-        display: inline-block;
-        top: 3px;
-        margin-right: 4px;
-    }
-
-    // Text color
-    color: hsl(0, 0%, 10%);
-    border: 1px solid hsl(0, 0%, 73%);
-
-    // Change background on hover
-    background-color: hsl(0, 0%, 100%);
-    &:hover {
-        color: hsl(0, 0%, 10%);
-        background-color: hsl(0, 0%, 90%);
-    }
-
-
-    width: 100%;
-    margin: 0;
-    margin-left: 0 !important;
-    border-top: none;
-    border-bottom: none;
-}
--- a/StoneWebViewer/Resources/Styles/_variable.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-$primary-lighten: #57aae1;
-$primary: #3498db;
-$dangerColor: #E63F24;
-
-$panel-header: #fafafa;
-$border-color: #e7e7e7;
-$lightGrey: #cccccc;
-$text-color: #666666;
-
-$darkGrey: #333333;
-$darkBlue: #203A6F;
-$blueGrey: #303E4D;
-
-$toolbarHeight: 42px;
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_video.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.wvVideo {
-    // Align component vertically & horizontally
-    position: absolute;
-    top:50%;
-    left:0;
-    width: 100%;
-    height: auto;
-    transform: translateY(-50%);
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/styles.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-// bower:scss
-// endbower
-
-@import "webviewer.main.scss";
-@import "webviewer.components.scss";
-
-
-@media print {
-    @import "print";
-}
-
-.closePrintButton{
-    display:none;
-}
-
-body.print{
-    @import "print";
-
-    @media screen {
-        .closePrintButton{
-            display:block;
-            position: fixed;
-            top: 0;
-            right: 0;
-            padding: 10px;
-            font-size: 24px;
-            background-color: black;
-            color: white;
-            border: none;
-        }
-    }
-}
--- a/StoneWebViewer/Resources/Styles/tb-group.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-.tbGroup{
-    position:relative;
-}
-.tbGroup__buttons--base,
-%tbGroup__buttons--base{
-    z-index: 5;
-    background-color: black;
-    position: absolute;
-}
-
-.tbGroup__buttons--bottom{
-    @extend .tbGroup__buttons--base;
-    right:0;  // let the element at it's initial position but align him in right (natural position is below the toggl button element)
-    display:block;
-}
-.tbGroup__buttons--left{
-    @extend .tbGroup__buttons--base;
-    right:100%;
-    top:0;
-    display:block;
-}
-.tbGroup__icon{
-    display:block;
-    position: absolute;
-    bottom:0;
-    left:0;
-
-    width: 0;
-    height: 0;
-    border-style: solid;
-    border-width: 10px 0 0 10px;
-    border-color: transparent transparent transparent rgba(255,255,255,0.1);
-    &.active{
-        border-color: transparent transparent transparent $primary;
-    }
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/webviewer.components.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/* wvp-ui stuffs */
-wv-webviewer {
-    display: block;
-    height: 100%;
-    overflow: hidden;
-}
-
-@import "variable";
-@import "button";
-@import "exitButton";
-@import "studyIsland";
-@import "helpers";
-@import "notice";
-@import "layout";
-@import "serieslist";
-@import "toolbar";
-@import "video";
-@import "studyInformationBreadcrumb";
-@import "selectionActionlist";
-
-/* wvb-ui stuffs */
-@import "wv-overlay.scss";
-@import "wv-pdf-viewer.scss";
-@import "wv-splitpane.scss";
-@import "wv-timeline.scss";
-@import "wv-timeline-controls.scss";
-@import "wv-loadingbar.scss";
-@import "wv-disclaimer";
-@import "tb-group";
-
-wv-viewport { // make sure the element is sized when using with drag & drop
-  display: inline-block;
-  width: 100%;
-  height: 100%;
-
-  > div {
-    position: relative;
-    width: 100%;
-    height: 100%;
-  }
-
-  // We don't set 100% width/height to the canvas element, as it would stretch
-  // the pixels. Instead, we center it for more fluid transition when pane's 
-  // width changes (at least the content is kept centered even if the js hasn't
-  // yet reacted to layout reflow).
-  > div > .wv-cornerstone-enabled-image {
-    width: 100%;
-    height: 100%;
-    text-align: center;
-  }
-}
-
-.wv-draggable-clone {
-  width: 150px;
-  height: 150px;
-  background-color: rgba(255,255,255,0.25);
-
-  // No need to set z-index (already done by jquery ui lib).
-}
--- a/StoneWebViewer/Resources/Styles/webviewer.main.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-.browsehappy {
-  margin: 0.2em 0;
-  background: #ccc;
-  color: #000;
-  padding: 0.2em 0;
-}
-
-.wv-html, .wv-body {
-    height: 100%;
-    width: 100%;
-
-    margin: 0;
-    padding: 0;
-    
-    overflow: hidden;
-}
-.wv-body {
-    background-color: black;
-    color: white;
-    position: relative;
-    overflow: hidden;
-
-    font-family: "Open Sans", Helvetica, Arial, sans-serif;
-    -webkit-tap-highlight-color: hsla(0, 0%, 0%, 0);
-    font-size: 13px;
-    font-weight: 400;
-    line-height: 1.49;
-    font-size-adjust: 100%;
-
-    // Smooth text
-    -moz-osx-font-smoothing: grayscale !important;
-    font-smoothing: antialiased !important;
-    -webkit-font-smoothing: antialiased !important;
-}
-
-.wvLoadingScreen {
-    width: 100%;
-    height: 100%;
-    background-color: black;
-    position: fixed;
-    top: 0;
-    left: 0;
-    z-index: 9999;
-
-    display: flex;
-    align-items: center;
-    justify-content: center;
-}
-
-.wvLoadingSpinner {
-    margin: 100px auto 0;
-    width: 70px;
-    text-align: center;
-}
-  
-.wvLoadingSpinner > div {
-    width: 18px;
-    height: 18px;
-    background-color: #FFF;
-
-    border-radius: 100%;
-    display: inline-block;
-    -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
-    animation: sk-bouncedelay 1.4s infinite ease-in-out both;
-}
-
-.wvLoadingSpinner .bounce1 {
-    -webkit-animation-delay: -0.32s;
-    animation-delay: -0.32s;
-}
-
-.wvLoadingSpinner .bounce2 {
-    -webkit-animation-delay: -0.16s;
-    animation-delay: -0.16s;
-}
-
-@-webkit-keyframes sk-bouncedelay {
-    0%, 80%, 100% { -webkit-transform: scale(0) }
-    40% { -webkit-transform: scale(1.0) }
-}
-
-@keyframes sk-bouncedelay {
-    0%, 80%, 100% { 
-        -webkit-transform: scale(0);
-        transform: scale(0);
-    } 40% { 
-        -webkit-transform: scale(1.0);
-        transform: scale(1.0);
-    }
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-disclaimer.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-.disclaimer{
-    color: $dangerColor;
-    background-color: #303030;
-    padding:5px;
-    text-align: center;
-    font-weight: bold;
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-loadingbar.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-.wv-loadingbar-image-bar {
-    cursor: pointer;
-}
-.wv-loadingbar-not-loaded {
-    fill: rgba(255, 255, 255, 0.1);
-}
-.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality {
-    transition: none;
-}
-.wv-loadingbar-not-loaded:hover {
-    fill: rgba(255, 255, 255, 0.2);
-}
-.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality {
-    fill:rgba(0, 255, 0, 0.7);
-}
-.wv-loadingbar-LOSSLESS-quality:hover,
-.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active,
-.wv-loadingbar-PIXELDATA-quality:hover,
-.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active {
-    fill:rgba(0, 255, 0, 1);
-}
-.wv-loadingbar-LOW-quality {
-    fill:rgba(255, 0, 0, 0.7);
-}
-.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active {
-    fill:rgba(255, 0, 0, 1);
-}
-.wv-loadingbar-MEDIUM-quality {
-    fill:rgba(255, 95, 0, 0.7);
-}
-.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active {
-    fill:rgba(255, 95, 0, 1);
-}
--- a/StoneWebViewer/Resources/Styles/wv-overlay.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-$gray: gray;
-$blue: hsla(204, 70%, 53%, 0.7);
-$red: rgba(206, 0, 0, 0.7);
-$green: rgba(0, 160, 27, .7);
-$yellow: rgba(220, 200  , 0, .9);
-$violet: rgba(255, 31, 255, .7);
-
-.wv-overlay {
-    // width&height is 0x0 to avoid capturing viewport events
-    color: orange;
-}
-
-.wv-overlay-icon {
-    width: 64px;    
-}
-
-.wvOverlay__studyBadge {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 1.5rem;
-    height: 1.5rem;
-    background-color: $gray;
-    z-index: 1;
-}
-
-.wv-overlay-topleft {
-    position: absolute;
-    top: 0rem;
-    left: 0rem;
-    text-align: left;
-}
-
-.wv-overlay-topright {
-    position: absolute;
-    top: 0rem;
-    right: 0rem;
-    text-align: right;
-}
-
-.wv-overlay-bottomright {
-    position: absolute;
-    bottom: 2em; // save 2em for the timeline
-    right: 0rem;
-    text-align: right;
-}
-
-.wv-overlay-bottomleft {
-    position: absolute;
-    bottom: 2em; // save 2em for the timeline
-    left: 0rem;
-    text-align: left;
-}
-
-.wv-overlay-timeline-wrapper {
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    z-index: 1; // Make sure the representation of the selected image on the timeline appear on top of other overlay panels
-}
-
-.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft {
-    padding: 2rem;
-    transition: color 500ms, background-color 500ms;
-    background-color: rgba(0, 0, 0, 0.66);
-}
-
-.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover {
-    background-color: rgba(0, 0, 0, 0.9);
-}
-
-.wvPaneOverlay {
-    position: absolute;
-    top: 50%;
-    width: 100%;
-    transform: translateY(-50%);
-
-    font-weight: 100;
-    text-align: center;
-    color: white;
-    font-size: 2rem;
-}
-
-.wv-overlay-scrollbar-loaded {
-    position: absolute;
-    bottom:0;
-    left:0;
-    height: 5px;
-    background-color: red;
-    will-change: right;
-    transform-origin: 0% 50%;
-}
-
-.wv-overlay-scrollbar-loading {
-    position: absolute;
-    bottom:0;
-    left:0;
-    height: 5px;
-    background-color: #660000;
-    will-change: right;
-    transform-origin: 0% 50%;
-}
-
-.wv-overlay-scrollbar-text {
-    position: absolute;
-    bottom: calc(1em + 5px);
-    left: 5px;
-    height: 1em;
-    color: red;
-    font-size: 0.8em;
-    font-family: helvetica;
-}
-
-// Color related modifiers
-.wvOverlay__studyBadge--blue {
-    @extend .wvOverlay__studyBadge;
-    background-color: $blue;
-}
-.wvOverlay__studyBadge--red {
-    @extend .wvOverlay__studyBadge;
-    background-color: $red;
-}
-.wvOverlay__studyBadge--green {
-    @extend .wvOverlay__studyBadge;
-    background-color: $green;
-}
-.wvOverlay__studyBadge--yellow {
-    @extend .wvOverlay__studyBadge;
-    background-color: $yellow;
-}
-.wvOverlay__studyBadge--violet {
-    @extend .wvOverlay__studyBadge;
-    background-color: $violet;
-}
--- a/StoneWebViewer/Resources/Styles/wv-pdf-viewer.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1761 +0,0 @@
-wv-pdf-viewer {
-    display: block;
-    width: 100%;
-    height: 100%;
-}
-
-#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton { // We need high priority, !important keywords don't work
-    background-color: inherit;
-    color: hsl(0, 0%, 100%);
-    border: none;
-
-    padding: 2px;
-    margin-left: 4px;
-    margin-right: 2px;
-
-    &:hover {
-        color: black;
-    }
-}
-
-.fa.fa-window-close.wv-pdf-viewer-closebuttonicon { // We need high priority
-    font-size: 2rem;
-    line-height: 28px; // pdf.js toolbar size (- closebutton margin)
-}
-
-// The following code has been generated via:
-// 
-// ```bash
-// cd bower_components/pdf.js-viewer/
-// lessc --global-var='pdfjsImagePath="../images/pdf.js-viewer"' viewer.less viewer.css
-// ```
-
-.pdfjs .textLayer {
-  position: absolute;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  opacity: 0.2;
-}
-.pdfjs .textLayer > div {
-  color: transparent;
-  position: absolute;
-  white-space: pre;
-  cursor: text;
-  -webkit-transform-origin: 0 0;
-  -moz-transform-origin: 0 0;
-  -o-transform-origin: 0 0;
-  -ms-transform-origin: 0 0;
-  transform-origin: 0 0;
-}
-.pdfjs .textLayer .highlight {
-  margin: -1px;
-  padding: 1px;
-  background-color: #b400aa;
-  border-radius: 4px;
-}
-.pdfjs .textLayer .highlight.begin {
-  border-radius: 4px 0 0 4px;
-}
-.pdfjs .textLayer .highlight.end {
-  border-radius: 0 4px 4px 0;
-}
-.pdfjs .textLayer .highlight.middle {
-  border-radius: 0;
-}
-.pdfjs .textLayer .highlight.selected {
-  background-color: #006400;
-}
-.pdfjs .textLayer ::selection {
-  background: #00f;
-}
-.pdfjs .textLayer ::-moz-selection {
-  background: #00f;
-}
-.pdfjs .pdfViewer .canvasWrapper {
-  overflow: hidden;
-}
-.pdfjs .pdfViewer .page {
-  direction: ltr;
-  width: 816px;
-  height: 1056px;
-  margin: 1px auto -8px;
-  position: relative;
-  overflow: visible;
-  border: 9px solid transparent;
-  background-clip: content-box;
-  border-image: url('../images/pdf.js-viewer/shadow.png') 9 9 repeat;
-  background-color: #fff;
-}
-body {
-  height: 100%;
-}
-.pdfjs .pdfViewer.removePageBorders .page {
-  margin: 0 auto 10px;
-  border: none;
-}
-.pdfjs .pdfViewer .page canvas {
-  margin: 0;
-  display: block;
-}
-.pdfjs .pdfViewer .page .loadingIcon {
-  position: absolute;
-  display: block;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  background: url('../images/pdf.js-viewer/loading-icon.gif') center no-repeat;
-}
-.pdfjs .pdfViewer .page .annotLink > a:hover {
-  opacity: .2;
-  background: #ff0;
-  box-shadow: 0 2px 10px #ff0;
-}
-.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0;
-}
-.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0;
-}
-.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
-  margin-bottom: 100%!important;
-  border: 0;
-}
-.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0;
-}
-.pdfjs .pdfViewer .page .annotText > img {
-  position: absolute;
-  cursor: pointer;
-}
-.pdfjs .pdfViewer .page .annotTextContentWrapper {
-  position: absolute;
-  width: 20em;
-}
-.pdfjs .pdfViewer .page .annotTextContent {
-  z-index: 200;
-  float: left;
-  max-width: 20em;
-  background-color: #FF9;
-  box-shadow: 0 2px 5px #333;
-  border-radius: 2px;
-  padding: .6em;
-  cursor: pointer;
-}
-.pdfjs .pdfViewer .page .annotTextContent > h1 {
-  font-size: 1em;
-  border-bottom: 1px solid #000;
-  padding-bottom: 0.2em;
-}
-.pdfjs .pdfViewer .page .annotTextContent > p {
-  padding-top: 0.2em;
-}
-.pdfjs .pdfViewer .page .annotLink > a {
-  position: absolute;
-  font-size: 1em;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-}
-.pdfjs .pdfViewer .page .annotLink > a {
-  background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA\ LAAAAAABAAEAAAIBRAA7") 0 0 repeat;
-}
-.pdfjs * {
-  padding: 0;
-  margin: 0;
-}
-html {
-  height: 100%;
-  font-size: 10px;
-}
-.pdfjs input,
-.pdfjs button,
-.pdfjs select {
-  font: message-box;
-  outline: none;
-}
-.pdfjs .hidden {
-  display: none !important;
-}
-.pdfjs [hidden] {
-  display: none !important;
-}
-.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen {
-  top: 0;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -webkit-user-select: none;
-}
-.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen {
-  top: 0;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -moz-user-select: none;
-}
-.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen {
-  top: 0!important;
-  border-top: 2px solid transparent;
-  width: 100%;
-  height: 100%;
-  overflow: hidden!important;
-  cursor: none;
-  -ms-user-select: none;
-}
-.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop {
-  background-color: #000;
-}
-.pdfjs #viewerContainer.pdfPresentationMode:fullscreen {
-  top: 0;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-}
-.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) {
-  display: none;
-}
-.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) {
-  display: none;
-}
-.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) {
-  display: none !important;
-}
-.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) {
-  display: none;
-}
-.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div {
-  cursor: none;
-}
-.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div {
-  cursor: none;
-}
-.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div {
-  cursor: none;
-}
-.pdfjs .pdfPresentationMode:fullscreen .textLayer > div {
-  cursor: none;
-}
-.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *,
-.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
-  cursor: default;
-}
-.pdfjs .outerCenter {
-  pointer-events: none;
-  position: relative;
-}
-html[dir='ltr'] .pdfjs .outerCenter {
-  float: right;
-  right: 50%;
-}
-html[dir='rtl'] .pdfjs .outerCenter {
-  float: left;
-  left: 50%;
-}
-.pdfjs .innerCenter {
-  pointer-events: auto;
-  position: relative;
-}
-html[dir='ltr'] .pdfjs .innerCenter {
-  float: right;
-  right: -50%;
-}
-html[dir='rtl'] .pdfjs .innerCenter {
-  float: left;
-  left: -50%;
-}
-.pdfjs #outerContainer {
-  width: 100%;
-  height: 100%;
-  position: relative;
-  background-color: #404040;
-  background-image: url('../images/pdf.js-viewer/texture.png');
-}
-.pdfjs #sidebarContainer {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  width: 200px;
-  visibility: hidden;
-  -webkit-transition-duration: 200ms;
-  -webkit-transition-timing-function: ease;
-  transition-duration: 200ms;
-  transition-timing-function: ease;
-}
-html[dir='ltr'] .pdfjs #sidebarContainer {
-  -webkit-transition-property: left;
-  transition-property: left;
-  left: -200px;
-}
-html[dir='rtl'] .pdfjs #sidebarContainer {
-  -webkit-transition-property: right;
-  transition-property: right;
-  right: -200px;
-}
-.pdfjs #outerContainer.sidebarMoving > #sidebarContainer,
-.pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
-  visibility: visible;
-}
-html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
-  left: 0;
-}
-html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
-  right: 0;
-}
-.pdfjs #mainContainer {
-  position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  min-width: 320px;
-  -webkit-transition-duration: 200ms;
-  -webkit-transition-timing-function: ease;
-  transition-duration: 200ms;
-  transition-timing-function: ease;
-}
-html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-  -webkit-transition-property: left;
-  transition-property: left;
-  left: 200px;
-}
-html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-  -webkit-transition-property: right;
-  transition-property: right;
-  right: 200px;
-}
-.pdfjs #sidebarContent {
-  top: 32px;
-  bottom: 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  position: absolute;
-  width: 200px;
-  background-color: rgba(0, 0, 0, 0.1);
-}
-html[dir='ltr'] .pdfjs #sidebarContent {
-  left: 0;
-  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25);
-}
-html[dir='rtl'] .pdfjs #sidebarContent {
-  right: 0;
-  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25);
-}
-.pdfjs #viewerContainer {
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  position: absolute;
-  top: 32px;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  outline: none;
-}
-html[dir='ltr'] .pdfjs #viewerContainer {
-  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05);
-}
-html[dir='rtl'] .pdfjs #viewerContainer {
-  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05);
-}
-.pdfjs .toolbar {
-  position: relative;
-  left: 0;
-  right: 0;
-  // z-index: 9999;
-  cursor: default;
-}
-.pdfjs #toolbarContainer {
-  width: 100%;
-}
-.pdfjs #toolbarSidebar {
-  width: 200px;
-  height: 32px;
-  background-color: #424242;
-  background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95));
-}
-html[dir='ltr'] .pdfjs #toolbarSidebar {
-  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1);
-}
-html[dir='rtl'] .pdfjs #toolbarSidebar {
-  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1);
-}
-.pdfjs #toolbarContainer,
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  position: relative;
-  height: 32px;
-  background-color: #474747;
-  background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95));
-}
-html[dir='ltr'] .pdfjs #toolbarContainer,
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
-}
-html[dir='rtl'] .pdfjs #toolbarContainer,
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
-}
-.pdfjs #toolbarViewer {
-  height: 32px;
-}
-.pdfjs #loadingBar {
-  position: relative;
-  width: 100%;
-  height: 4px;
-  background-color: #333;
-  border-bottom: 1px solid #333;
-}
-.pdfjs #loadingBar .progress {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 0;
-  height: 100%;
-  background-color: #ddd;
-  overflow: hidden;
-  -webkit-transition: width 200ms;
-  transition: width 200ms;
-}
-@-webkit-keyframes progressIndeterminate {
-  0% {
-    left: 0;
-  }
-  50% {
-    left: 100%;
-  }
-  100% {
-    left: 100%;
-  }
-}
-@keyframes progressIndeterminate {
-  0% {
-    left: 0;
-  }
-  50% {
-    left: 100%;
-  }
-  100% {
-    left: 100%;
-  }
-}
-.pdfjs #loadingBar .progress.indeterminate {
-  background-color: #999;
-  -webkit-transition: none;
-  transition: none;
-}
-.pdfjs #loadingBar .indeterminate .glimmer {
-  position: absolute;
-  top: 0;
-  left: 0;
-  height: 100%;
-  width: 50px;
-  background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
-  background-size: 100% 100%;
-  background-repeat: no-repeat;
-  -webkit-animation: progressIndeterminate 2s linear infinite;
-  animation: progressIndeterminate 2s linear infinite;
-}
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  top: 32px;
-  position: absolute;
-  z-index: 10000;
-  height: 32px;
-  min-width: 16px;
-  padding: 0 6px;
-  margin: 4px 2px;
-  color: #d9d9d9;
-  font-size: 12px;
-  line-height: 14px;
-  text-align: left;
-  cursor: default;
-}
-html[dir='ltr'] .pdfjs .findbar {
-  left: 68px;
-}
-html[dir='rtl'] .pdfjs .findbar {
-  right: 68px;
-}
-.pdfjs .findbar label {
-  -webkit-user-select: none;
-  -moz-user-select: none;
-}
-.pdfjs #findInput[data-status="pending"] {
-  background-image: url('../images/pdf.js-viewer/loading-small.png');
-  background-repeat: no-repeat;
-  background-position: right;
-}
-html[dir='rtl'] .pdfjs #findInput[data-status="pending"] {
-  background-position: left;
-}
-.pdfjs .secondaryToolbar {
-  padding: 6px;
-  height: auto;
-  z-index: 30000;
-}
-html[dir='ltr'] .pdfjs .secondaryToolbar {
-  right: 4px;
-}
-html[dir='rtl'] .pdfjs .secondaryToolbar {
-  left: 4px;
-}
-.pdfjs #secondaryToolbarButtonContainer {
-  max-width: 200px;
-  max-height: 400px;
-  overflow-y: auto;
-  -webkit-overflow-scrolling: touch;
-  margin-bottom: -4px;
-}
-.pdfjs .doorHanger,
-.pdfjs .doorHangerRight {
-  border: 1px solid rgba(0, 0, 0, 0.5);
-  border-radius: 2px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
-}
-.pdfjs .doorHanger:after,
-.pdfjs .doorHanger:before,
-.pdfjs .doorHangerRight:after,
-.pdfjs .doorHangerRight:before {
-  bottom: 100%;
-  border: solid transparent;
-  content: " ";
-  height: 0;
-  width: 0;
-  position: absolute;
-  pointer-events: none;
-}
-.pdfjs .doorHanger:after,
-.pdfjs .doorHangerRight:after {
-  border-bottom-color: rgba(82, 82, 82, 0.99);
-  border-width: 8px;
-}
-.pdfjs .doorHanger:before,
-.pdfjs .doorHangerRight:before {
-  border-bottom-color: rgba(0, 0, 0, 0.5);
-  border-width: 9px;
-}
-html[dir='ltr'] .pdfjs .doorHanger:after,
-html[dir='rtl'] .pdfjs .doorHangerRight:after {
-  left: 13px;
-  margin-left: -8px;
-}
-html[dir='ltr'] .pdfjs .doorHanger:before,
-html[dir='rtl'] .pdfjs .doorHangerRight:before {
-  left: 13px;
-  margin-left: -9px;
-}
-html[dir='rtl'] .pdfjs .doorHanger:after,
-html[dir='ltr'] .pdfjs .doorHangerRight:after {
-  right: 13px;
-  margin-right: -8px;
-}
-html[dir='rtl'] .pdfjs .doorHanger:before,
-html[dir='ltr'] .pdfjs .doorHangerRight:before {
-  right: 13px;
-  margin-right: -9px;
-}
-.pdfjs #findMsg {
-  font-style: italic;
-  color: #A6B7D0;
-}
-.pdfjs #findInput.notFound {
-  background-color: #f66;
-}
-html[dir='ltr'] .pdfjs #toolbarViewerLeft {
-  margin-left: -1px;
-}
-html[dir='rtl'] .pdfjs #toolbarViewerRight {
-  margin-right: -1px;
-}
-html[dir='ltr'] .pdfjs #toolbarViewerLeft,
-html[dir='rtl'] .pdfjs #toolbarViewerRight {
-  position: absolute;
-  top: 0;
-  left: 0;
-}
-html[dir='ltr'] .pdfjs #toolbarViewerRight,
-html[dir='rtl'] .pdfjs #toolbarViewerLeft {
-  position: absolute;
-  top: 0;
-  right: 0;
-}
-html[dir='ltr'] .pdfjs #toolbarViewerLeft > *,
-html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *,
-html[dir='ltr'] .pdfjs #toolbarViewerRight > *,
-html[dir='ltr'] .pdfjs .findbar > * {
-  position: relative;
-  float: left;
-}
-html[dir='rtl'] .pdfjs #toolbarViewerLeft > *,
-html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *,
-html[dir='rtl'] .pdfjs #toolbarViewerRight > *,
-html[dir='rtl'] .pdfjs .findbar > * {
-  position: relative;
-  float: right;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButton {
-  margin: 3px 2px 4px 0;
-  display: inline-block;
-}
-html[dir='rtl'] .pdfjs .splitToolbarButton {
-  margin: 3px 0 4px 2px;
-  display: inline-block;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton {
-  border-radius: 0;
-  float: left;
-}
-html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton {
-  border-radius: 0;
-  float: right;
-}
-.pdfjs .toolbarButton,
-.pdfjs .secondaryToolbarButton,
-.pdfjs .overlayButton {
-  border: 0 none;
-  background: none;
-  width: 32px;
-  height: 25px;
-}
-.pdfjs .toolbarButton > span {
-  display: inline-block;
-  width: 0;
-  height: 0;
-  overflow: hidden;
-}
-.pdfjs .toolbarButton[disabled],
-.pdfjs .secondaryToolbarButton[disabled],
-.pdfjs .overlayButton[disabled] {
-  opacity: 0.5;
-}
-.pdfjs .toolbarButton.group {
-  margin-right: 0;
-}
-.pdfjs .splitToolbarButton.toggled .toolbarButton {
-  margin: 0;
-}
-.pdfjs .splitToolbarButton:hover > .toolbarButton,
-.pdfjs .splitToolbarButton:focus > .toolbarButton,
-.pdfjs .splitToolbarButton.toggled > .toolbarButton,
-.pdfjs .toolbarButton.textButton {
-  background-color: rgba(0, 0, 0, 0.12);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  border: 1px solid rgba(0, 0, 0, 0.35);
-  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease;
-}
-.pdfjs .splitToolbarButton > .toolbarButton:hover,
-.pdfjs .splitToolbarButton > .toolbarButton:focus,
-.pdfjs .dropdownToolbarButton:hover,
-.pdfjs .overlayButton:hover,
-.pdfjs .toolbarButton.textButton:hover,
-.pdfjs .toolbarButton.textButton:focus {
-  background-color: rgba(0, 0, 0, 0.2);
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05);
-  z-index: 199;
-}
-.pdfjs .splitToolbarButton > .toolbarButton {
-  position: relative;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child {
-  position: relative;
-  margin: 0;
-  margin-right: -1px;
-  border-top-left-radius: 2px;
-  border-bottom-left-radius: 2px;
-  border-right-color: transparent;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child {
-  position: relative;
-  margin: 0;
-  margin-left: -1px;
-  border-top-right-radius: 2px;
-  border-bottom-right-radius: 2px;
-  border-left-color: transparent;
-}
-.pdfjs .splitToolbarButtonSeparator {
-  padding: 8px 0;
-  width: 1px;
-  background-color: rgba(0, 0, 0, 0.5);
-  z-index: 99;
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
-  display: inline-block;
-  margin: 5px 0;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator {
-  float: left;
-}
-html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator {
-  float: right;
-}
-.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator,
-.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator {
-  padding: 12px 0;
-  margin: 1px 0;
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03);
-  -webkit-transition-property: padding;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: ease;
-  transition-property: padding;
-  transition-duration: 10ms;
-  transition-timing-function: ease;
-}
-.pdfjs .toolbarButton,
-.pdfjs .dropdownToolbarButton,
-.pdfjs .secondaryToolbarButton,
-.pdfjs .overlayButton {
-  min-width: 16px;
-  padding: 2px 6px 0;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  color: rgba(255, 255, 255, 0.8);
-  font-size: 12px;
-  line-height: 14px;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-  cursor: default;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease;
-}
-html[dir='ltr'] .pdfjs .toolbarButton,
-html[dir='ltr'] .pdfjs .overlayButton,
-html[dir='ltr'] .pdfjs .dropdownToolbarButton {
-  margin: 3px 2px 4px 0;
-}
-html[dir='rtl'] .pdfjs .toolbarButton,
-html[dir='rtl'] .pdfjs .overlayButton,
-html[dir='rtl'] .pdfjs .dropdownToolbarButton {
-  margin: 3px 0 4px 2px;
-}
-.pdfjs .toolbarButton:hover,
-.pdfjs .toolbarButton:focus,
-.pdfjs .dropdownToolbarButton,
-.pdfjs .overlayButton,
-.pdfjs .secondaryToolbarButton:hover,
-.pdfjs .secondaryToolbarButton:focus {
-  background-color: rgba(0, 0, 0, 0.12);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  border: 1px solid rgba(0, 0, 0, 0.35);
-  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-}
-.pdfjs .toolbarButton:hover:active,
-.pdfjs .overlayButton:hover:active,
-.pdfjs .dropdownToolbarButton:hover:active,
-.pdfjs .secondaryToolbarButton:hover:active {
-  background-color: rgba(0, 0, 0, 0.2);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45);
-  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 10ms;
-  transition-timing-function: linear;
-}
-.pdfjs .toolbarButton.toggled,
-.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled,
-.pdfjs .secondaryToolbarButton.toggled {
-  background-color: rgba(0, 0, 0, 0.3);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5);
-  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 10ms;
-  transition-timing-function: linear;
-}
-.pdfjs .toolbarButton.toggled:hover:active,
-.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active,
-.pdfjs .secondaryToolbarButton.toggled:hover:active {
-  background-color: rgba(0, 0, 0, 0.4);
-  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55);
-  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-}
-.pdfjs .dropdownToolbarButton {
-  width: 120px;
-  max-width: 120px;
-  padding: 0;
-  overflow: hidden;
-  background: url('../images/pdf.js-viewer/toolbarButton-menuArrows.png') no-repeat;
-}
-html[dir='ltr'] .pdfjs .dropdownToolbarButton {
-  background-position: 95%;
-}
-html[dir='rtl'] .pdfjs .dropdownToolbarButton {
-  background-position: 5%;
-}
-.pdfjs .dropdownToolbarButton > select {
-  min-width: 140px;
-  font-size: 12px;
-  color: #f2f2f2;
-  margin: 0;
-  padding: 3px 2px 2px;
-  border: none;
-  background: rgba(0, 0, 0, 0);
-}
-.pdfjs .dropdownToolbarButton > select > option {
-  background: #3d3d3d;
-}
-.pdfjs #customScaleOption {
-  display: none;
-}
-.pdfjs #pageWidthOption {
-  border-bottom: 1px rgba(255, 255, 255, 0.5) solid;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButton:first-child,
-html[dir='ltr'] .pdfjs .toolbarButton:first-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton:last-child,
-html[dir='rtl'] .pdfjs .toolbarButton:last-child {
-  margin-left: 4px;
-}
-html[dir='ltr'] .pdfjs .splitToolbarButton:last-child,
-html[dir='ltr'] .pdfjs .toolbarButton:last-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton:first-child,
-html[dir='rtl'] .pdfjs .toolbarButton:first-child {
-  margin-right: 4px;
-}
-.pdfjs .toolbarButtonSpacer {
-  width: 30px;
-  display: inline-block;
-  height: 1px;
-}
-.pdfjs .toolbarButtonFlexibleSpacer {
-  -webkit-box-flex: 1;
-  -moz-box-flex: 1;
-  min-width: 30px;
-}
-html[dir='ltr'] .pdfjs #findPrevious {
-  margin-left: 3px;
-}
-html[dir='ltr'] .pdfjs #findNext {
-  margin-right: 3px;
-}
-html[dir='rtl'] .pdfjs #findPrevious {
-  margin-right: 3px;
-}
-html[dir='rtl'] .pdfjs #findNext {
-  margin-left: 3px;
-}
-.pdfjs .toolbarButton::before,
-.pdfjs .secondaryToolbarButton::before {
-  position: absolute;
-  display: inline-block;
-  top: 4px;
-  left: 7px;
-}
-html[dir="ltr"] .pdfjs .secondaryToolbarButton::before {
-  left: 4px;
-}
-html[dir="rtl"] .pdfjs .secondaryToolbarButton::before {
-  right: 4px;
-}
-html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle.png');
-}
-html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png');
-}
-html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png');
-}
-html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png');
-}
-html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
-  content: url('../images/pdf.js-viewer/findbarButton-previous.png');
-}
-html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
-  content: url('../images/pdf.js-viewer/findbarButton-previous-rtl.png');
-}
-html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
-  content: url('../images/pdf.js-viewer/findbarButton-next.png');
-}
-html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
-  content: url('../images/pdf.js-viewer/findbarButton-next-rtl.png');
-}
-html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-pageUp.png');
-}
-html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png');
-}
-html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-pageDown.png');
-}
-html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png');
-}
-.pdfjs .toolbarButton.zoomOut::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-zoomOut.png');
-}
-.pdfjs .toolbarButton.zoomIn::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-zoomIn.png');
-}
-.pdfjs .toolbarButton.presentationMode::before,
-.pdfjs .secondaryToolbarButton.presentationMode::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-presentationMode.png');
-}
-.pdfjs .toolbarButton.print::before,
-.pdfjs .secondaryToolbarButton.print::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-print.png');
-}
-.pdfjs .toolbarButton.openFile::before,
-.pdfjs .secondaryToolbarButton.openFile::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-openFile.png');
-}
-.pdfjs .toolbarButton.download::before,
-.pdfjs .secondaryToolbarButton.download::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-download.png');
-}
-.pdfjs .toolbarButton.bookmark,
-.pdfjs .secondaryToolbarButton.bookmark {
-  -webkit-box-sizing: border-box;
-  -moz-box-sizing: border-box;
-  box-sizing: border-box;
-  outline: none;
-  padding-top: 4px;
-  text-decoration: none;
-}
-.pdfjs .secondaryToolbarButton.bookmark {
-  padding-top: 5px;
-}
-.pdfjs .bookmark[href='#'] {
-  opacity: .5;
-  pointer-events: none;
-}
-.pdfjs .toolbarButton.bookmark::before,
-.pdfjs .secondaryToolbarButton.bookmark::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-bookmark.png');
-}
-.pdfjs #viewThumbnail.toolbarButton::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail.png');
-}
-html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-viewOutline.png');
-}
-html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png');
-}
-.pdfjs #viewAttachments.toolbarButton::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments.png');
-}
-.pdfjs #viewFind.toolbarButton::before {
-  content: url('../images/pdf.js-viewer/toolbarButton-search.png');
-}
-.pdfjs .secondaryToolbarButton {
-  position: relative;
-  margin: 0 0 4px;
-  padding: 3px 0 1px;
-  height: auto;
-  min-height: 25px;
-  width: auto;
-  min-width: 100%;
-  white-space: normal;
-}
-html[dir="ltr"] .pdfjs .secondaryToolbarButton {
-  padding-left: 24px;
-  text-align: left;
-}
-html[dir="rtl"] .pdfjs .secondaryToolbarButton {
-  padding-right: 24px;
-  text-align: right;
-}
-html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark {
-  padding-left: 27px;
-}
-html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark {
-  padding-right: 27px;
-}
-html[dir="ltr"] .pdfjs .secondaryToolbarButton > span {
-  padding-right: 4px;
-}
-html[dir="rtl"] .pdfjs .secondaryToolbarButton > span {
-  padding-left: 4px;
-}
-.pdfjs .secondaryToolbarButton.firstPage::before {
-  content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png');
-}
-.pdfjs .secondaryToolbarButton.lastPage::before {
-  content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png');
-}
-.pdfjs .secondaryToolbarButton.rotateCcw::before {
-  content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png');
-}
-.pdfjs .secondaryToolbarButton.rotateCw::before {
-  content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png');
-}
-.pdfjs .secondaryToolbarButton.handTool::before {
-  content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool.png');
-}
-.pdfjs .secondaryToolbarButton.documentProperties::before {
-  content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png');
-}
-.pdfjs .verticalToolbarSeparator {
-  display: block;
-  padding: 8px 0;
-  margin: 8px 4px;
-  width: 1px;
-  background-color: rgba(0, 0, 0, 0.5);
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
-}
-html[dir='ltr'] .pdfjs .verticalToolbarSeparator {
-  margin-left: 2px;
-}
-html[dir='rtl'] .pdfjs .verticalToolbarSeparator {
-  margin-right: 2px;
-}
-.pdfjs .horizontalToolbarSeparator {
-  display: block;
-  margin: 0 0 4px;
-  height: 1px;
-  width: 100%;
-  background-color: rgba(0, 0, 0, 0.5);
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
-}
-.pdfjs .toolbarField {
-  padding: 3px 6px;
-  margin: 4px 0;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  background-color: rgba(255, 255, 255, 0.09);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  border: 1px solid rgba(0, 0, 0, 0.35);
-  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
-  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  color: #f2f2f2;
-  font-size: 12px;
-  line-height: 14px;
-  outline-style: none;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease;
-}
-.pdfjs .toolbarField[type=checkbox] {
-  display: inline-block;
-  margin: 8px 0;
-}
-.pdfjs .toolbarField.pageNumber {
-  -moz-appearance: textfield;
-  min-width: 16px;
-  text-align: right;
-  width: 40px;
-}
-.pdfjs .toolbarField.pageNumber.visiblePageIsLoading {
-  background-image: url('../images/pdf.js-viewer/loading-small.png');
-  background-repeat: no-repeat;
-  background-position: 1px;
-}
-.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button,
-.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button {
-  -webkit-appearance: none;
-  margin: 0;
-}
-.pdfjs .toolbarField:hover {
-  background-color: rgba(255, 255, 255, 0.11);
-  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45);
-}
-.pdfjs .toolbarField:focus {
-  background-color: rgba(255, 255, 255, 0.15);
-  border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9);
-}
-.pdfjs .toolbarLabel {
-  min-width: 16px;
-  padding: 3px 6px 3px 2px;
-  margin: 4px 2px 4px 0;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  color: #d9d9d9;
-  font-size: 12px;
-  line-height: 14px;
-  text-align: left;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  cursor: default;
-}
-.pdfjs #thumbnailView {
-  position: absolute;
-  width: 120px;
-  top: 0;
-  bottom: 0;
-  padding: 10px 40px 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-}
-.pdfjs .thumbnail {
-  float: left;
-  margin-bottom: 5px;
-}
-.pdfjs #thumbnailView > a:last-of-type > .thumbnail {
-  margin-bottom: 10px;
-}
-.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) {
-  margin-bottom: 9px;
-}
-.pdfjs .thumbnail:not([data-loaded]) {
-  border: 1px dashed rgba(255, 255, 255, 0.5);
-  margin: -1px -1px 4px;
-}
-.pdfjs .thumbnailImage {
-  border: 1px solid transparent;
-  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
-  opacity: .8;
-  z-index: 99;
-  background-color: #fff;
-  background-clip: content-box;
-}
-.pdfjs .thumbnailSelectionRing {
-  border-radius: 2px;
-  padding: 7px;
-}
-.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
-.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
-  opacity: 0.9;
-}
-.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing,
-.pdfjs .thumbnail:hover > .thumbnailSelectionRing {
-  background-color: rgba(255, 255, 255, 0.15);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: rgba(255, 255, 255, 0.9);
-}
-.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
-  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
-  opacity: 1;
-}
-.pdfjs .thumbnail.selected > .thumbnailSelectionRing {
-  background-color: rgba(255, 255, 255, 0.3);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: #ffffff;
-}
-.pdfjs #outlineView,
-.pdfjs #attachmentsView {
-  position: absolute;
-  width: 192px;
-  top: 0;
-  bottom: 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-}
-.pdfjs #outlineView {
-  padding: 4px 4px 0;
-}
-.pdfjs #attachmentsView {
-  padding: 3px 4px 0;
-}
-html[dir='ltr'] .pdfjs .outlineItem > .outlineItems {
-  margin-left: 20px;
-}
-html[dir='rtl'] .pdfjs .outlineItem > .outlineItems {
-  margin-right: 20px;
-}
-.pdfjs .outlineItem > a,
-.pdfjs .attachmentsItem > button {
-  text-decoration: none;
-  display: inline-block;
-  min-width: 95%;
-  height: auto;
-  margin-bottom: 1px;
-  border-radius: 2px;
-  color: rgba(255, 255, 255, 0.8);
-  font-size: 13px;
-  line-height: 15px;
-  -moz-user-select: none;
-  white-space: normal;
-}
-.pdfjs .attachmentsItem > button {
-  border: 0 none;
-  background: none;
-  cursor: pointer;
-  width: 100%;
-}
-html[dir='ltr'] .pdfjs .outlineItem > a {
-  padding: 2px 0 5px 10px;
-}
-html[dir='ltr'] .pdfjs .attachmentsItem > button {
-  padding: 2px 0 3px 7px;
-  text-align: left;
-}
-html[dir='rtl'] .pdfjs .outlineItem > a {
-  padding: 2px 10px 5px 0;
-}
-html[dir='rtl'] .pdfjs .attachmentsItem > button {
-  padding: 2px 7px 3px 0;
-  text-align: right;
-}
-.pdfjs .outlineItem > a:hover,
-.pdfjs .attachmentsItem > button:hover {
-  background-color: rgba(255, 255, 255, 0.02);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: rgba(255, 255, 255, 0.9);
-}
-.pdfjs .outlineItem.selected {
-  background-color: rgba(255, 255, 255, 0.08);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: #ffffff;
-}
-.pdfjs .noResults {
-  font-size: 12px;
-  color: rgba(255, 255, 255, 0.8);
-  font-style: italic;
-  cursor: default;
-}
-.pdfjs ::selection {
-  background: rgba(0, 0, 255, 0.3);
-}
-.pdfjs ::-moz-selection {
-  background: rgba(0, 0, 255, 0.3);
-}
-.pdfjs #errorWrapper {
-  background: none repeat scroll 0 0 #F55;
-  color: #fff;
-  left: 0;
-  position: absolute;
-  right: 0;
-  z-index: 1000;
-  padding: 3px;
-  font-size: 0.8em;
-}
-.pdfjs .loadingInProgress #errorWrapper {
-  top: 37px;
-}
-.pdfjs #errorMessageLeft {
-  float: left;
-}
-.pdfjs #errorMessageRight {
-  float: right;
-}
-.pdfjs #errorMoreInfo {
-  background-color: #FFF;
-  color: #000;
-  padding: 3px;
-  margin: 3px;
-  width: 98%;
-}
-.pdfjs .overlayButton {
-  width: auto;
-  margin: 3px 4px 2px!important;
-  padding: 2px 6px 3px;
-}
-.pdfjs #overlayContainer {
-  display: table;
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, 0.2);
-  z-index: 40000;
-}
-.pdfjs #overlayContainer > * {
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-}
-.pdfjs #overlayContainer > .container {
-  display: table-cell;
-  vertical-align: middle;
-  text-align: center;
-}
-.pdfjs #overlayContainer > .container > .dialog {
-  display: inline-block;
-  padding: 15px;
-  border-spacing: 4px;
-  color: #d9d9d9;
-  font-size: 12px;
-  line-height: 14px;
-  background-color: #474747;
-  background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95));
-  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
-  border: 1px solid rgba(0, 0, 0, 0.5);
-  border-radius: 4px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
-}
-.pdfjs .dialog > .row {
-  display: table-row;
-}
-.pdfjs .dialog > .row > * {
-  display: table-cell;
-}
-.pdfjs .dialog .toolbarField {
-  margin: 5px 0;
-}
-.pdfjs .dialog .separator {
-  display: block;
-  margin: 4px 0;
-  height: 1px;
-  width: 100%;
-  background-color: rgba(0, 0, 0, 0.5);
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
-}
-.pdfjs .dialog .buttonRow {
-  text-align: center;
-  vertical-align: middle;
-}
-.pdfjs #passwordOverlay > .dialog {
-  text-align: center;
-}
-.pdfjs #passwordOverlay .toolbarField {
-  width: 200px;
-}
-.pdfjs #documentPropertiesOverlay > .dialog {
-  text-align: left;
-}
-.pdfjs #documentPropertiesOverlay .row > * {
-  min-width: 100px;
-}
-html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * {
-  text-align: left;
-}
-html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * {
-  text-align: right;
-}
-.pdfjs #documentPropertiesOverlay .row > span {
-  width: 125px;
-  word-wrap: break-word;
-}
-.pdfjs #documentPropertiesOverlay .row > p {
-  max-width: 225px;
-  word-wrap: break-word;
-}
-.pdfjs #documentPropertiesOverlay .buttonRow {
-  margin-top: 10px;
-}
-.pdfjs .clearBoth {
-  clear: both;
-}
-.pdfjs .fileInput {
-  background: #fff;
-  color: #000;
-  margin-top: 5px;
-  visibility: hidden;
-  position: fixed;
-  right: 0;
-  top: 0;
-}
-.pdfjs #PDFBug {
-  background: none repeat scroll 0 0 #fff;
-  border: 1px solid #666;
-  position: fixed;
-  top: 32px;
-  right: 0;
-  bottom: 0;
-  font-size: 10px;
-  padding: 0;
-  width: 300px;
-}
-.pdfjs #PDFBug .controls {
-  background: #EEE;
-  border-bottom: 1px solid #666;
-  padding: 3px;
-}
-.pdfjs #PDFBug .panels {
-  bottom: 0;
-  left: 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  position: absolute;
-  right: 0;
-  top: 27px;
-}
-.pdfjs #PDFBug button.active {
-  font-weight: 700;
-}
-.pdfjs .debuggerShowText {
-  background: none repeat scroll 0 0 #ff0;
-  color: blue;
-}
-.pdfjs .debuggerHideText:hover {
-  background: none repeat scroll 0 0 #ff0;
-}
-.pdfjs #PDFBug .stats {
-  font-family: courier;
-  font-size: 10px;
-  white-space: pre;
-}
-.pdfjs #PDFBug .stats .title {
-  font-weight: 700;
-}
-.pdfjs #PDFBug table {
-  font-size: 10px;
-}
-.pdfjs #viewer.textLayer-visible .textLayer > div,
-.pdfjs #viewer.textLayer-hover .textLayer > div:hover {
-  background-color: #fff;
-  color: #000;
-}
-.pdfjs #viewer.textLayer-shadow .textLayer > div {
-  background-color: rgba(255, 255, 255, 0.6);
-  color: #000;
-}
-.pdfjs .grab-to-pan-grab {
-  cursor: url('../images/pdf.js-viewer/grab.cur'), move !important;
-  cursor: -webkit-grab !important;
-  cursor: -moz-grab !important;
-  cursor: grab !important;
-}
-.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) {
-  cursor: inherit !important;
-}
-.pdfjs .grab-to-pan-grab:active,
-.pdfjs .grab-to-pan-grabbing {
-  cursor: url('../images/pdf.js-viewer/grabbing.cur'), move !important;
-  cursor: -webkit-grabbing !important;
-  cursor: -moz-grabbing !important;
-  cursor: grabbing!important;
-  position: fixed;
-  background: transparent;
-  display: block;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  z-index: 50000;
-}
-@page {
-  margin: 0;
-}
-.pdfjs #printContainer {
-  display: none;
-}
-@media screen and (min-resolution: 2dppx) {
-  .pdfjs .toolbarButton::before {
-    -webkit-transform: scale(0.5);
-    transform: scale(0.5);
-    top: -5px;
-  }
-  .pdfjs .secondaryToolbarButton::before {
-    -webkit-transform: scale(0.5);
-    transform: scale(0.5);
-    top: -4px;
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton::before,
-  html[dir='rtl'] .pdfjs .toolbarButton::before {
-    left: -1px;
-  }
-  html[dir='ltr'] .pdfjs .secondaryToolbarButton::before {
-    left: -2px;
-  }
-  html[dir='rtl'] .pdfjs .secondaryToolbarButton::before {
-    left: 186px;
-  }
-  .pdfjs .toolbarField.pageNumber.visiblePageIsLoading,
-  .pdfjs #findInput[data-status="pending"] {
-    background-image: url('../images/pdf.js-viewer/loading-small@2x.png');
-    background-size: 16px 17px;
-  }
-  .pdfjs .dropdownToolbarButton {
-    background: url('../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png') no-repeat;
-    background-size: 7px 16px;
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png');
-  }
-  html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png');
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png');
-  }
-  html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png');
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
-    content: url('../images/pdf.js-viewer/findbarButton-previous@2x.png');
-  }
-  html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
-    content: url('../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png');
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
-    content: url('../images/pdf.js-viewer/findbarButton-next@2x.png');
-  }
-  html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
-    content: url('../images/pdf.js-viewer/findbarButton-next-rtl@2x.png');
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-pageUp@2x.png');
-  }
-  html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png');
-  }
-  html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-pageDown@2x.png');
-  }
-  html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png');
-  }
-  .pdfjs .toolbarButton.zoomIn::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png');
-  }
-  .pdfjs .toolbarButton.zoomOut::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png');
-  }
-  .pdfjs .toolbarButton.presentationMode::before,
-  .pdfjs .secondaryToolbarButton.presentationMode::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png');
-  }
-  .pdfjs .toolbarButton.print::before,
-  .pdfjs .secondaryToolbarButton.print::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-print@2x.png');
-  }
-  .pdfjs .toolbarButton.openFile::before,
-  .pdfjs .secondaryToolbarButton.openFile::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-openFile@2x.png');
-  }
-  .pdfjs .toolbarButton.download::before,
-  .pdfjs .secondaryToolbarButton.download::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-download@2x.png');
-  }
-  .pdfjs .toolbarButton.bookmark::before,
-  .pdfjs .secondaryToolbarButton.bookmark::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-bookmark@2x.png');
-  }
-  .pdfjs #viewThumbnail.toolbarButton::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png');
-  }
-  html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png');
-  }
-  html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png');
-  }
-  .pdfjs #viewAttachments.toolbarButton::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png');
-  }
-  .pdfjs #viewFind.toolbarButton::before {
-    content: url('../images/pdf.js-viewer/toolbarButton-search@2x.png');
-  }
-  .pdfjs .secondaryToolbarButton.firstPage::before {
-    content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png');
-  }
-  .pdfjs .secondaryToolbarButton.lastPage::before {
-    content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png');
-  }
-  .pdfjs .secondaryToolbarButton.rotateCcw::before {
-    content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png');
-  }
-  .pdfjs .secondaryToolbarButton.rotateCw::before {
-    content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png');
-  }
-  .pdfjs .secondaryToolbarButton.handTool::before {
-    content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png');
-  }
-  .pdfjs .secondaryToolbarButton.documentProperties::before {
-    content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png');
-  }
-}
-@media print {
-  body {
-    background: transparent none;
-  }
-  .pdfjs #sidebarContainer,
-  .pdfjs #secondaryToolbar,
-  .pdfjs .toolbar,
-  .pdfjs #loadingBox,
-  .pdfjs #errorWrapper,
-  .pdfjs .textLayer {
-    display: none;
-  }
-  .pdfjs #viewerContainer {
-    overflow: visible;
-  }
-  .pdfjs #mainContainer,
-  .pdfjs #viewerContainer,
-  .pdfjs .page,
-  .pdfjs .page canvas {
-    position: static;
-    padding: 0;
-    margin: 0;
-  }
-  .pdfjs .page {
-    float: left;
-    display: none;
-    border: none;
-    box-shadow: none;
-    background-clip: content-box;
-    background-color: #fff;
-  }
-  .pdfjs .page[data-loaded] {
-    display: block;
-  }
-  .pdfjs .fileInput {
-    display: none;
-  }
-  body[data-mozPrintCallback] .pdfjs #outerContainer {
-    display: none;
-  }
-  body[data-mozPrintCallback] .pdfjs #printContainer {
-    display: block;
-  }
-  .pdfjs #printContainer > div {
-    position: relative;
-    top: 0;
-    left: 0;
-    overflow: hidden;
-  }
-  .pdfjs #printContainer canvas {
-    display: block;
-  }
-}
-.pdfjs .visibleLargeView,
-.pdfjs .visibleMediumView,
-.pdfjs .visibleSmallView {
-  display: none;
-}
-@media all and (max-width: 960px) {
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
-    float: left;
-    left: 205px;
-  }
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
-    float: right;
-    right: 205px;
-  }
-}
-@media all and (max-width: 900px) {
-  .pdfjs .sidebarOpen .hiddenLargeView {
-    display: none;
-  }
-  .pdfjs .sidebarOpen .visibleLargeView {
-    display: inherit;
-  }
-}
-@media all and (max-width: 860px) {
-  .pdfjs .sidebarOpen .hiddenMediumView {
-    display: none;
-  }
-  .pdfjs .sidebarOpen .visibleMediumView {
-    display: inherit;
-  }
-}
-@media all and (max-width: 770px) {
-  .pdfjs #sidebarContainer {
-    top: 32px;
-    z-index: 100;
-  }
-  .pdfjs .loadingInProgress #sidebarContainer {
-    top: 37px;
-  }
-  .pdfjs #sidebarContent {
-    top: 32px;
-    background-color: rgba(0, 0, 0, 0.7);
-  }
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-    left: 0;
-  }
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-    right: 0;
-  }
-  html[dir='ltr'] .pdfjs .outerCenter {
-    float: left;
-    left: 205px;
-  }
-  html[dir='rtl'] .pdfjs .outerCenter {
-    float: right;
-    right: 205px;
-  }
-  .pdfjs #outerContainer .hiddenLargeView,
-  .pdfjs #outerContainer .hiddenMediumView {
-    display: inherit;
-  }
-  .pdfjs #outerContainer .visibleLargeView,
-  .pdfjs #outerContainer .visibleMediumView {
-    display: none;
-  }
-}
-@media all and (max-width: 700px) {
-  .pdfjs #outerContainer .hiddenLargeView {
-    display: none;
-  }
-  .pdfjs #outerContainer .visibleLargeView {
-    display: inherit;
-  }
-}
-@media all and (max-width: 660px) {
-  .pdfjs #outerContainer .hiddenMediumView {
-    display: none;
-  }
-  .pdfjs #outerContainer .visibleMediumView {
-    display: inherit;
-  }
-}
-@media all and (max-width: 600px) {
-  .pdfjs .hiddenSmallView {
-    display: none;
-  }
-  .pdfjs .visibleSmallView {
-    display: inherit;
-  }
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
-  html[dir='ltr'] .pdfjs .outerCenter {
-    left: 156px;
-  }
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
-  html[dir='rtl'] .pdfjs .outerCenter {
-    right: 156px;
-  }
-  .pdfjs .toolbarButtonSpacer {
-    width: 0;
-  }
-}
-@media all and (max-width: 510px) {
-  .pdfjs #scaleSelectContainer,
-  .pdfjs #pageNumberLabel {
-    display: none;
-  }
-}
-/* should be hidden differently */
-#fileInput.fileInput {
-  display: none;
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-splitpane.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-$gray: gray;
-$blue: hsla(204, 70%, 53%, 0.7);
-$red: rgba(206, 0, 0, 0.7);
-$green: rgba(0, 160, 27, .7);
-$yellow: rgba(220, 200  , 0, .9);
-$violet: rgba(255, 31, 255, .7);
-
-.wvSplitpane {
-    height: 100%;
-    padding: 7px 2px 2px 2px;
-
-    // Anchor
-    position: relative;
-}
-.wvSplitpane__cell {
-    display: inline-block;
-    float: left;
-    height: 100%;
-    width: 100%;
-
-    // Anchor
-    position: relative;
-}
-.wvSplitpane__cellBorder,
-%wvSplitpane__cellBorder {
-    display: inline-block;
-    float: left;
-    height: calc(100% - 2px);
-    width: calc(100% - 2px);
-
-    border: 2px dashed transparent;
-
-    padding: 2px;
-    margin: 1px;
-}
-.wvSplitpane__cellBorder--selected {
-    @extend .wvSplitpane__cellBorder;
-
-    // Add border
-    border: 2px solid $blue;
-}
-
-// Color modifiers
-.wvSplitpane__cellBorder--blue {
-    @extend .wvSplitpane__cellBorder;
-    border-color: $blue;
-}
-
-.wvSplitpane__cellBorder--red {
-    @extend .wvSplitpane__cellBorder;
-    border-color: $red;
-}
-
-.wvSplitpane__cellBorder--green {
-    @extend .wvSplitpane__cellBorder;
-    border-color: $green;
-}
-
-.wvSplitpane__cellBorder--yellow {
-    @extend .wvSplitpane__cellBorder;
-    border-color: $yellow;
-}
-
-.wvSplitpane__cellBorder--violet {
-    @extend .wvSplitpane__cellBorder;
-    border-color: $violet;
-}
-
-// Make sure the pane keeps its size
-wv-pane-policy {
-    display: block;
-    width: 100%;
-    height: 100%;
-
-    > div[ng-transclude] {
-        display: block;
-        width: 100%;
-        height: 100%;
-    }
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-timeline-controls.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/* wv-timeline-controls directive */
-.wv-timeline-controls {
-    padding: 0.5em 0.5em 0.5em 0.5em;
-    line-height: 1em;
-    background-color: rgba(0, 0, 0, 0.66);
-
-    text-align: center;
-
-    transition: color 500ms, background-color 500ms;
-}
-
-.wv-timeline-controls:hover {
-    background-color: rgba(0, 0, 0, 0.9);
-}
-
-// Used to make sure buttons doesn't break the style
-.wv-timeline-controls-vertical-sizing {
-    display: inline-block; 
-    line-height: 1em;
-    font-size: 1em;
-}
-
-.wv-timeline-controls-vflip {
-    // flip only the icon
-    &:before, &:after{
-        transform: scaleX(-1);
-        display: inline-block;
-    }
-}
-
-.wv-timeline-controls-button {
-    display: inline-block;
-    height: 1em;
-    width: 1em;
-    line-height: 1em;
-    font-size: 1em;
-    margin: 0;
-
-    user-select: none;
-    cursor: pointer;
-}
-
-.wv-timeline-controls-input {
-    height: 1em;
-    width: 3em;
-    padding: 0;
-    padding-bottom: 1px;
-    box-sizing: content-box;
-
-    border: none;
-    border-bottom: 1px solid hsla(35, 100%, 75%, 0.24);
-    background-color: transparent;
-    
-    text-align: right;
-}
-
-// Display play button on the right side
-.wv-timeline-controls-play-button-wrapper {
-    float: right;
-}
-
-/* wv-play-button directive */
-.wv-play-button {
-    display: inline-block;
-    position: relative;
-    line-height: 1em;
-
-    // This is for the boxing box
-    height: 3em;
-    width: 6em;
-    padding-bottom: 1em;
-    padding-left: 0.25em;
-    padding-right: 0.25em;
-}
-
-.wv-play-button:hover .wv-play-button-config-position-handler {
-    visibility: visible;
-}
-
-// This is a 0x0 div to set the position
-.wv-play-button-config-position-handler {
-    visibility: hidden;
-    position: absolute;
-    bottom: 3em;
-    left: 1em;
-    right: 0.5em;
-    // z-index: 2;
-}
-
-// The layout of play configuration
-.wv-play-button-config {
-    position: absolute;
-    bottom: 0;
-    left: -6em;
-    width: 12em;
-    padding: 1em;
-    background-color: hsla(0,1,0, 0.5);
-}
-
-/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */
-
-.wv-play-button-config-framerate-wrapper {
-    display: inline-block;
-    margin: 0.25em 0 0.5em 0;
-}
-input[type="range"].wv-play-button-config-framerate {
-    /*removes default webkit styles*/
-    -webkit-appearance: none;
-    
-    /*fix for FF unable to apply focus style bug */
-    border: 1px solid white;
-    
-    /*required for proper track sizing in FF*/
-    width: 10em;
-}
-input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track {
-    width: 10em;
-    height: 5px;
-    background: #ddd;
-    border: none;
-    border-radius: 3px;
-}
-input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb {
-    -webkit-appearance: none;
-    border: none;
-    height: 16px;
-    width: 16px;
-    border-radius: 50%;
-    background: goldenrod;
-    margin-top: -4px;
-}
-input[type="range"].wv-play-button-config-framerate:focus {
-    outline: none;
-}
-input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track {
-    background: #ccc;
-}
-
-input[type="range"].wv-play-button-config-framerate::-moz-range-track {
-    width: 10em;
-    height: 5px;
-    background: #ddd;
-    border: none;
-    border-radius: 3px;
-}
-input[type="range"].wv-play-button-config-framerate::-moz-range-thumb {
-    border: none;
-    height: 16px;
-    width: 16px;
-    border-radius: 50%;
-    background: goldenrod;
-}
-
-/*hide the outline behind the border*/
-input[type="range"].wv-play-button-config-framerate:-moz-focusring{
-    outline: 1px solid white;
-    outline-offset: -1px;
-}
-
-input[type="range"].wv-play-button-config-framerate::-ms-track {
-    width: 10em;
-    height: 5px;
-    
-    /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
-    background: transparent;
-    
-    /*leave room for the larger thumb to overflow with a transparent border */
-    border-color: transparent;
-    border-width: 6px 0;
-
-    /*remove default tick marks*/
-    color: transparent;
-}
-input[type="range"].wv-play-button-config-framerate::-ms-fill-lower {
-    background: #777;
-    border-radius: 10px;
-}
-input[type="range"].wv-play-button-config-framerate::-ms-fill-upper {
-    background: #ddd;
-    border-radius: 10px;
-}
-input[type="range"].wv-play-button-config-framerate::-ms-thumb {
-    border: none;
-    height: 16px;
-    width: 16px;
-    border-radius: 50%;
-    background: goldenrod;
-}
-input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower {
-    background: #888;
-}
-input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper {
-    background: #ccc;
-}
\ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-timeline.scss	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-.wv-timeline {
-    position: relative;
-    height: 2em; // save 2px to display the "upper part of the currently selected image on the timeline"
-    &.reduced{
-        height: 5px;
-        .wv-timeline-loading-bar-wrapper{
-            width: 100%;
-            height: 100%;
-        }
-    }
-}
-
-.wv-timeline-controls-wrapper {
-    position: absolute;
-    left: 0;
-    bottom: 0;
-    width: 16em;
-    height: 100%; 
-    color: white;
-}
-
-.wv-timeline-loading-bar-wrapper {
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    width: calc(100% - 16em);
-    height: calc(100% + 2px); 
-    
-    svg{
-        position:absolute;
-        left:0;
-        top:0;
-    }
-}
\ No newline at end of file
--- a/StoneWebViewer/WebApplication/app.css	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4752 +0,0 @@
-.browsehappy {
-  margin: 0.2em 0;
-  background: #ccc;
-  color: #000;
-  padding: 0.2em 0; }
-
-.wv-html, .wv-body {
-  height: 100%;
-  width: 100%;
-  margin: 0;
-  padding: 0;
-  overflow: hidden; }
-
-.wv-body {
-  background-color: black;
-  color: white;
-  position: relative;
-  overflow: hidden;
-  font-family: "Open Sans", Helvetica, Arial, sans-serif;
-  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-  font-size: 13px;
-  font-weight: 400;
-  line-height: 1.49;
-  font-size-adjust: 100%;
-  -moz-osx-font-smoothing: grayscale !important;
-  font-smoothing: antialiased !important;
-  -webkit-font-smoothing: antialiased !important; }
-
-.wvLoadingScreen {
-  width: 100%;
-  height: 100%;
-  background-color: black;
-  position: fixed;
-  top: 0;
-  left: 0;
-  z-index: 9999;
-  display: flex;
-  align-items: center;
-  justify-content: center; }
-
-.wvLoadingSpinner {
-  margin: 100px auto 0;
-  width: 70px;
-  text-align: center; }
-
-.wvLoadingSpinner > div {
-  width: 18px;
-  height: 18px;
-  background-color: #FFF;
-  border-radius: 100%;
-  display: inline-block;
-  -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
-  animation: sk-bouncedelay 1.4s infinite ease-in-out both; }
-
-.wvLoadingSpinner .bounce1 {
-  -webkit-animation-delay: -0.32s;
-  animation-delay: -0.32s; }
-
-.wvLoadingSpinner .bounce2 {
-  -webkit-animation-delay: -0.16s;
-  animation-delay: -0.16s; }
-
-@-webkit-keyframes sk-bouncedelay {
-  0%, 80%, 100% {
-    -webkit-transform: scale(0); }
-  40% {
-    -webkit-transform: scale(1); } }
-
-@keyframes sk-bouncedelay {
-  0%, 80%, 100% {
-    -webkit-transform: scale(0);
-    transform: scale(0); }
-  40% {
-    -webkit-transform: scale(1);
-    transform: scale(1); } }
-
-/* wvp-ui stuffs */
-wv-webviewer {
-  display: block;
-  height: 100%;
-  overflow: hidden; }
-
-.wvButton, .wvButton--rotate, .wvButton--vflip, .wvButton--underline, .fa.wvButton--underline, .wvButton--border, .wvButton--borderAndWhite {
-  outline: none;
-  background-color: transparent;
-  border: none;
-  border-radius: 0;
-  position: relative;
-  display: inline-block;
-  cursor: pointer;
-  font-variant: small-caps;
-  text-transform: lowercase;
-  text-align: center;
-  font-size: 1.3rem;
-  font-weight: 400;
-  color: #d9d9d9;
-  transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
-  margin: 0;
-  min-width: 3rem;
-  padding: 0 10px;
-  line-height: 3.6rem; }
-  .wvButton:hover, .wvButton--rotate:hover, .wvButton--vflip:hover, .wvButton--underline:hover, .wvButton--border:hover, .wvButton--borderAndWhite:hover {
-    text-decoration: none;
-    color: white; }
-  .wvButton.wvLargeButton, .wvLargeButton.wvButton--rotate, .wvLargeButton.wvButton--vflip, .wvLargeButton.wvButton--underline, .wvLargeButton.wvButton--border, .wvLargeButton.wvButton--borderAndWhite {
-    font-size: 2rem;
-    line-height: 6.2rem;
-    padding: 0 20px; }
-
-.wvButton--rotate:before, .wvButton--rotate:after {
-  transform: rotate(90deg);
-  display: inline-block; }
-
-.wvButton--vflip:before, .wvButton--vflip:after {
-  transform: scaleX(-1);
-  display: inline-block; }
-
-.wvButton--underline, .fa.wvButton--underline {
-  position: relative;
-  background-color: inherit;
-  text-decoration: none;
-  text-align: left;
-  font-size: 1.2rem;
-  width: 3.2rem;
-  vertical-align: middle;
-  color: white;
-  opacity: 0.75;
-  border: none;
-  border-bottom: 2px solid rgba(255, 255, 255, 0.1);
-  top: 0px; }
-  .wvButton--underline.wvLargeButton, .fa.wvButton--underline.wvLargeButton {
-    font-size: 2rem;
-    width: 6.4rem; }
-  .wvButton--underline *, .fa.wvButton--underline * {
-    pointer-events: none; }
-  .wvButton--underline:hover, .wvButton--underline:active, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:active, .fa.wvButton--underline:focus {
-    outline: 0; }
-  .wvButton--underline:hover, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:focus {
-    border-color: white;
-    opacity: 1; }
-    .wvButton--underline:hover .wvButton__bottomTriangle, .wvButton--underline:focus .wvButton__bottomTriangle, .fa.wvButton--underline:hover .wvButton__bottomTriangle, .fa.wvButton--underline:focus .wvButton__bottomTriangle {
-      border-left-color: white; }
-  .wvButton--underline.active, .fa.wvButton--underline.active {
-    opacity: 1;
-    border-color: #3498db; }
-  .wvButton--underline::before, .fa.wvButton--underline::before {
-    position: relative;
-    top: -1px; }
-  .wvButton--underline.fa, .fa.wvButton--underline.fa {
-    top: 0px;
-    font-weight: 800; }
-
-.wvButton__bottomTriangle {
-  transition: 0.3s border ease, 0.3s opacity ease;
-  display: block;
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  width: 0;
-  height: 0;
-  border-style: solid;
-  border-width: 10px 0 0 10px;
-  border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); }
-  .wvButton__bottomTriangle.active {
-    border-color: transparent transparent transparent #3498db !important; }
-    .wvButton__bottomTriangle.active.toggled {
-      border-left-color: #3498db !important; }
-
-.wvButton--border, .wvButton--borderAndWhite {
-  max-height: calc(2.8rem - 3px);
-  max-width: 100%;
-  overflow: hidden;
-  margin: 0.6rem;
-  margin-left: 0rem;
-  margin-right: 0rem;
-  line-height: 2rem;
-  padding-top: 0.1rem;
-  padding-bottom: 0.5rem;
-  font-size: 1.4rem;
-  border: 1px solid #454545;
-  font-family: Arial;
-  background-color: black; }
-  .wvButton--border + .wvButton--border, .wvButton--borderAndWhite + .wvButton--border, .wvButton--border + .wvButton--borderAndWhite, .wvButton--borderAndWhite + .wvButton--borderAndWhite {
-    margin-left: 0.7rem; }
-  .wvButton--border:hover, .wvButton--borderAndWhite:hover {
-    background-color: #1a1a1a; }
-  .wvButton--border > .glyphicon, .wvButton--borderAndWhite > .glyphicon {
-    position: relative;
-    display: inline-block;
-    top: 3px;
-    margin-right: 4px; }
-
-.wvButton--borderAndWhite {
-  color: #1a1a1a;
-  border: 1px solid #bababa;
-  background-color: white; }
-  .wvButton--borderAndWhite:hover {
-    color: #1a1a1a;
-    background-color: #e6e6e6; }
-
-.wvExitButton {
-  margin-left: 1rem;
-  margin-top: .25rem;
-  font-size: 1.25em;
-  color: white;
-  opacity: .66;
-  transition: .3s opacity ease;
-  background-color: inherit;
-  border: none;
-  text-decoration: none;
-  text-align: left;
-  padding: 0;
-  cursor: pointer;
-  font-family: inherit;
-  line-height: inherit; }
-  .wvExitButton:hover, .wvExitButton:focus {
-    opacity: 1;
-    outline: 0; }
-
-.wvExitButton__text {
-  position: relative;
-  top: -1px; }
-
-.wvStudyIsland--blue, .wvStudyIsland--red, .wvStudyIsland--green, .wvStudyIsland--yellow, .wvStudyIsland--violet {
-  margin: 1rem 1rem 1rem 1rem;
-  border: 0.3rem solid gray; }
-
-.wvStudyIsland__header--blue, .wvStudyIsland__header--red, .wvStudyIsland__header--green, .wvStudyIsland__header--yellow, .wvStudyIsland__header--violet {
-  background-color: gray;
-  padding: 0.5rem 0.5rem 0.8rem 0.5rem;
-  line-height: 1.35rem;
-  width: 100%; }
-
-.wvStudyIsland__actions {
-  float: right;
-  margin-top: -0.8rem;
-  margin-right: -0.8rem; }
-
-.wvStudyIsland__actions--oneCol {
-  float: none;
-  text-align: center; }
-
-.wvStudyIsland__main {
-  padding: 0.4rem;
-  color: white;
-  width: 100%; }
-
-.wvStudyIsland--blue {
-  border-color: rgba(51, 152, 219, 0.7); }
-
-.wvStudyIsland__header--blue {
-  background-color: rgba(51, 152, 219, 0.7); }
-
-.wvStudyIsland--red {
-  border-color: rgba(206, 0, 0, 0.7); }
-
-.wvStudyIsland__header--red {
-  background-color: rgba(206, 0, 0, 0.7); }
-
-.wvStudyIsland--green {
-  border-color: rgba(0, 160, 27, 0.7); }
-
-.wvStudyIsland__header--green {
-  background-color: rgba(0, 160, 27, 0.7); }
-
-.wvStudyIsland--yellow {
-  border-color: rgba(220, 200, 0, 0.9); }
-
-.wvStudyIsland__header--yellow {
-  background-color: rgba(220, 200, 0, 0.9); }
-
-.wvStudyIsland--violet {
-  border-color: rgba(255, 31, 255, 0.7); }
-
-.wvStudyIsland__header--violet {
-  background-color: rgba(255, 31, 255, 0.7); }
-
-/*
- *  Source code taken from private Osimis' frontend toolbox 3.2.1.
- */
-/**
-    _overlay.scss
- */
-.overlay__transparent {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  z-index: 1; }
-
-/** _transition.scss **/
-.transition {
-  transition: 0.3s all ease; }
-
-.transition--long {
-  transition: 0.6s all ease; }
-
-/** _list.scss **/
-dd + dt {
-  clear: both; }
-
-.listDefinition {
-  width: 100%;
-  line-height: 1.3; }
-
-.listDefinition__term {
-  clear: both;
-  float: left;
-  text-align: right;
-  padding-right: 10px;
-  width: 50%; }
-
-.listDefinition__data {
-  text-align: left;
-  padding-left: 10px;
-  float: right;
-  width: 50%; }
-
-/** _animation.scss **/
-@keyframes blink__primary {
-  0% {
-    color: #3498db; }
-  100% {
-    color: #666666; } }
-
-.blink__primary {
-  animation: blink__primary 0.8s linear infinite; }
-
-[translate-cloak] {
-  transition: 0.3s all ease;
-  opacity: 1; }
-  [translate-cloak].translate-cloak {
-    opacity: 0; }
-
-/** _button.scss **/
-.button__unstyled, .button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close {
-  background-color: inherit;
-  border: none;
-  text-decoration: none;
-  text-align: left;
-  padding: 0;
-  cursor: pointer; }
-  .button__unstyled *, .button__base *, .button__state--active *, .button__state--inactive *, .button__iconed *, .button__switch--base *, .button__switch *, .button__switch--first *, .button__switch--last *, .button__lightgrey--hover *, .button__text-danger--hover *, .button__text-primary--hover *, .button__danger--hover *, .button__text *, .button__text--underlined *, .button__bordered *, .button__bordered--inverted *, .button__close * {
-    pointer-events: none; }
-  .button__unstyled:hover, .button__base:hover, .button__state--active:hover, .button__state--inactive:hover, .button__iconed:hover, .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__lightgrey--hover:hover, .button__text-danger--hover:hover, .button__text-primary--hover:hover, .button__danger--hover:hover, .button__text:hover, .button__text--underlined:hover, .button__bordered:hover, .button__bordered--inverted:hover, .button__close:hover, .button__unstyled:active, .button__base:active, .button__state--active:active, .button__state--inactive:active, .button__iconed:active, .button__switch--base:active, .button__switch:active, .button__switch--first:active, .button__switch--last:active, .button__lightgrey--hover:active, .button__text-danger--hover:active, .button__text-primary--hover:active, .button__danger--hover:active, .button__text:active, .button__text--underlined:active, .button__bordered:active, .button__bordered--inverted:active, .button__close:active, .button__unstyled:focus, .button__base:focus, .button__state--active:focus, .button__state--inactive:focus, .button__iconed:focus, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus, .button__lightgrey--hover:focus, .button__text-danger--hover:focus, .button__text-primary--hover:focus, .button__danger--hover:focus, .button__text:focus, .button__text--underlined:focus, .button__bordered:focus, .button__bordered--inverted:focus, .button__close:focus {
-    outline: 0; }
-
-.button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close {
-  transition: 0.3s all ease; }
-
-.button__state--active {
-  opacity: 1; }
-  .button__state--active:hover {
-    opacity: 0.9;
-    color: #3498db; }
-
-.button__state--inactive {
-  opacity: 0.333; }
-  .button__state--inactive:hover {
-    opacity: 0.4333;
-    color: #3498db; }
-
-.button__iconed {
-  opacity: 1; }
-  .button__iconed:hover, .button__iconed:focus, .button__iconed.active {
-    opacity: 0.75;
-    color: #3498db; }
-
-.button__switch--base, .button__switch, .button__switch--first, .button__switch--last {
-  padding: 5px 0px;
-  display: inline-block;
-  text-align: center;
-  border-top: 1px solid;
-  border-bottom: 1px solid;
-  border-color: #3498db;
-  background-color: white;
-  overflow: hidden; }
-  .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus {
-    background-color: #5faee3;
-    color: white; }
-  .button__switch--base.active, .active.button__switch, .active.button__switch--first, .active.button__switch--last {
-    background-color: #3498db;
-    color: white; }
-
-.button__switch--first {
-  border-left: 1px solid #3498db;
-  border-radius: 5px 0 0 5px; }
-
-.button__switch--last {
-  border-right: 1px solid #3498db;
-  border-radius: 0 5px 5px 0; }
-
-.button__lightgrey--hover:hover, .button__lightgrey--hover:focus, .button__lightgrey--hover.active {
-  background-color: #cccccc; }
-
-.button__text-danger--hover:hover, .button__text-danger--hover:focus, .button__text-danger--hover.active {
-  color: #E63F24; }
-
-.button__text-primary--hover:hover, .button__text-primary--hover:focus, .button__text-primary--hover.active {
-  color: #3498db; }
-
-.button__danger--hover:hover, .button__danger--hover:focus, .button__danger--hover.active {
-  background-color: #E63F24;
-  color: white; }
-
-.button__text {
-  opacity: 0.66; }
-  .button__text:hover, .button__text.active, .button__text:focus {
-    opacity: 1; }
-
-.button__text--underlined {
-  text-decoration: underline; }
-  .button__text--underlined:hover, .button__text--underlined.active, .button__text--underlined:focus {
-    text-decoration: none; }
-
-.button__bordered {
-  border-bottom: 2px solid #666666; }
-  .button__bordered:hover, .button__bordered:focus, .button__bordered.active {
-    border-color: #3498db; }
-
-.button__bordered--inverted {
-  border-bottom: 2px solid white; }
-
-.button__close {
-  position: absolute;
-  top: 0;
-  right: 0;
-  opacity: 0.6;
-  z-index: 10; }
-  .button__close:hover, .button__close:focus {
-    opacity: 1; }
-
-/** _block.scss **/
-.block {
-  display: block !important; }
-
-/** _boxsizing.scss **/
-.boxsizing__borderbox {
-  box-sizing: border-box; }
-
-.boxsizing__contentbox {
-  box-sizing: content-box; }
-
-/** _scrollable.scss **/
-.scrollable {
-  overflow-y: auto; }
-
-.scrollable--x {
-  overflow-x: auto; }
-
-.no-scroll {
-  overflow: hidden; }
-
-/** _float.scss **/
-.float__right {
-  float: right; }
-
-.float__left {
-  float: left; }
-
-/** _fonts.scss **/
-.font__bold {
-  font-weight: 600; }
-
-.font__normal, .listDefinition__data {
-  font-weight: 400; }
-
-.font__light, .listDefinition__term {
-  font-weight: 200; }
-
-.fontColor__primary {
-  color: #3498db; }
-
-.fontColor__lightGrey {
-  color: #cccccc; }
-
-.fontColor__normal {
-  color: #666666; }
-
-.fontColor__darker {
-  color: #333333; }
-
-.fontColor__white {
-  color: white; }
-
-/** _forms.scss **/
-.textarea__unstyled {
-  border: none;
-  outline: none;
-  background-color: transparent;
-  color: inherit;
-  resize: none;
-  padding: 0;
-  margin: 0;
-  width: 100%; }
-
-/** _position.scss **/
-.position__relative {
-  position: relative; }
-
-/** _margin.scss **/
-.margin__auto {
-  margin: auto; }
-
-/** _helpers.scss **/
-/*************HELPERS**************/
-/**********************************/
-/*** identical width and height ***/
-.wh__0 {
-  width: 0px !important;
-  height: 0px !important; }
-
-.lh__0 {
-  line-height: 0px !important; }
-
-.wh__5 {
-  width: 5px !important;
-  height: 5px !important; }
-
-.lh__5 {
-  line-height: 5px !important; }
-
-.wh__8 {
-  width: 8px !important;
-  height: 8px !important; }
-
-.lh__8 {
-  line-height: 8px !important; }
-
-.wh__10 {
-  width: 10px !important;
-  height: 10px !important; }
-
-.lh__10 {
-  line-height: 10px !important; }
-
-.wh__11 {
-  width: 11px !important;
-  height: 11px !important; }
-
-.lh__11 {
-  line-height: 11px !important; }
-
-.wh__12 {
-  width: 12px !important;
-  height: 12px !important; }
-
-.lh__12 {
-  line-height: 12px !important; }
-
-.wh__13 {
-  width: 13px !important;
-  height: 13px !important; }
-
-.lh__13 {
-  line-height: 13px !important; }
-
-.wh__14 {
-  width: 14px !important;
-  height: 14px !important; }
-
-.lh__14 {
-  line-height: 14px !important; }
-
-.wh__15 {
-  width: 15px !important;
-  height: 15px !important; }
-
-.lh__15 {
-  line-height: 15px !important; }
-
-.wh__16 {
-  width: 16px !important;
-  height: 16px !important; }
-
-.lh__16 {
-  line-height: 16px !important; }
-
-.wh__17 {
-  width: 17px !important;
-  height: 17px !important; }
-
-.lh__17 {
-  line-height: 17px !important; }
-
-.wh__18 {
-  width: 18px !important;
-  height: 18px !important; }
-
-.lh__18 {
-  line-height: 18px !important; }
-
-.wh__19 {
-  width: 19px !important;
-  height: 19px !important; }
-
-.lh__19 {
-  line-height: 19px !important; }
-
-.wh__20 {
-  width: 20px !important;
-  height: 20px !important; }
-
-.lh__20 {
-  line-height: 20px !important; }
-
-.wh__21 {
-  width: 21px !important;
-  height: 21px !important; }
-
-.lh__21 {
-  line-height: 21px !important; }
-
-.wh__22 {
-  width: 22px !important;
-  height: 22px !important; }
-
-.lh__22 {
-  line-height: 22px !important; }
-
-.wh__23 {
-  width: 23px !important;
-  height: 23px !important; }
-
-.lh__23 {
-  line-height: 23px !important; }
-
-.wh__24 {
-  width: 24px !important;
-  height: 24px !important; }
-
-.lh__24 {
-  line-height: 24px !important; }
-
-.wh__25 {
-  width: 25px !important;
-  height: 25px !important; }
-
-.lh__25 {
-  line-height: 25px !important; }
-
-.wh__26 {
-  width: 26px !important;
-  height: 26px !important; }
-
-.lh__26 {
-  line-height: 26px !important; }
-
-.wh__27 {
-  width: 27px !important;
-  height: 27px !important; }
-
-.lh__27 {
-  line-height: 27px !important; }
-
-.wh__28 {
-  width: 28px !important;
-  height: 28px !important; }
-
-.lh__28 {
-  line-height: 28px !important; }
-
-.wh__29 {
-  width: 29px !important;
-  height: 29px !important; }
-
-.lh__29 {
-  line-height: 29px !important; }
-
-.wh__30 {
-  width: 30px !important;
-  height: 30px !important; }
-
-.lh__30 {
-  line-height: 30px !important; }
-
-.wh__31 {
-  width: 31px !important;
-  height: 31px !important; }
-
-.lh__31 {
-  line-height: 31px !important; }
-
-.wh__32 {
-  width: 32px !important;
-  height: 32px !important; }
-
-.lh__32 {
-  line-height: 32px !important; }
-
-.wh__33 {
-  width: 33px !important;
-  height: 33px !important; }
-
-.lh__33 {
-  line-height: 33px !important; }
-
-.wh__34 {
-  width: 34px !important;
-  height: 34px !important; }
-
-.lh__34 {
-  line-height: 34px !important; }
-
-.wh__35 {
-  width: 35px !important;
-  height: 35px !important; }
-
-.lh__35 {
-  line-height: 35px !important; }
-
-.wh__36 {
-  width: 36px !important;
-  height: 36px !important; }
-
-.lh__36 {
-  line-height: 36px !important; }
-
-.wh__37 {
-  width: 37px !important;
-  height: 37px !important; }
-
-.lh__37 {
-  line-height: 37px !important; }
-
-.wh__38 {
-  width: 38px !important;
-  height: 38px !important; }
-
-.lh__38 {
-  line-height: 38px !important; }
-
-.wh__39 {
-  width: 39px !important;
-  height: 39px !important; }
-
-.lh__39 {
-  line-height: 39px !important; }
-
-.wh__40 {
-  width: 40px !important;
-  height: 40px !important; }
-
-.lh__40 {
-  line-height: 40px !important; }
-
-.wh__41 {
-  width: 41px !important;
-  height: 41px !important; }
-
-.lh__41 {
-  line-height: 41px !important; }
-
-.wh__42 {
-  width: 42px !important;
-  height: 42px !important; }
-
-.lh__42 {
-  line-height: 42px !important; }
-
-.wh__43 {
-  width: 43px !important;
-  height: 43px !important; }
-
-.lh__43 {
-  line-height: 43px !important; }
-
-.wh__44 {
-  width: 44px !important;
-  height: 44px !important; }
-
-.lh__44 {
-  line-height: 44px !important; }
-
-.wh__45 {
-  width: 45px !important;
-  height: 45px !important; }
-
-.lh__45 {
-  line-height: 45px !important; }
-
-.wh__46 {
-  width: 46px !important;
-  height: 46px !important; }
-
-.lh__46 {
-  line-height: 46px !important; }
-
-.wh__47 {
-  width: 47px !important;
-  height: 47px !important; }
-
-.lh__47 {
-  line-height: 47px !important; }
-
-.wh__48 {
-  width: 48px !important;
-  height: 48px !important; }
-
-.lh__48 {
-  line-height: 48px !important; }
-
-.wh__49 {
-  width: 49px !important;
-  height: 49px !important; }
-
-.lh__49 {
-  line-height: 49px !important; }
-
-.wh__50 {
-  width: 50px !important;
-  height: 50px !important; }
-
-.lh__50 {
-  line-height: 50px !important; }
-
-.wh__55 {
-  width: 55px !important;
-  height: 55px !important; }
-
-.lh__55 {
-  line-height: 55px !important; }
-
-.wh__60 {
-  width: 60px !important;
-  height: 60px !important; }
-
-.lh__60 {
-  line-height: 60px !important; }
-
-.wh__64 {
-  width: 64px !important;
-  height: 64px !important; }
-
-.lh__64 {
-  line-height: 64px !important; }
-
-.wh__65 {
-  width: 65px !important;
-  height: 65px !important; }
-
-.lh__65 {
-  line-height: 65px !important; }
-
-.wh__70 {
-  width: 70px !important;
-  height: 70px !important; }
-
-.lh__70 {
-  line-height: 70px !important; }
-
-.wh__72 {
-  width: 72px !important;
-  height: 72px !important; }
-
-.lh__72 {
-  line-height: 72px !important; }
-
-.wh__75 {
-  width: 75px !important;
-  height: 75px !important; }
-
-.lh__75 {
-  line-height: 75px !important; }
-
-.wh__80 {
-  width: 80px !important;
-  height: 80px !important; }
-
-.lh__80 {
-  line-height: 80px !important; }
-
-.wh__85 {
-  width: 85px !important;
-  height: 85px !important; }
-
-.lh__85 {
-  line-height: 85px !important; }
-
-.wh__90 {
-  width: 90px !important;
-  height: 90px !important; }
-
-.lh__90 {
-  line-height: 90px !important; }
-
-.wh__95 {
-  width: 95px !important;
-  height: 95px !important; }
-
-.lh__95 {
-  line-height: 95px !important; }
-
-.wh__96 {
-  width: 96px !important;
-  height: 96px !important; }
-
-.lh__96 {
-  line-height: 96px !important; }
-
-.wh__100 {
-  width: 100px !important;
-  height: 100px !important; }
-
-.lh__100 {
-  line-height: 100px !important; }
-
-.wh__110 {
-  width: 110px !important;
-  height: 110px !important; }
-
-.lh__110 {
-  line-height: 110px !important; }
-
-.wh__120 {
-  width: 120px !important;
-  height: 120px !important; }
-
-.lh__120 {
-  line-height: 120px !important; }
-
-.wh__130 {
-  width: 130px !important;
-  height: 130px !important; }
-
-.lh__130 {
-  line-height: 130px !important; }
-
-.wh__140 {
-  width: 140px !important;
-  height: 140px !important; }
-
-.lh__140 {
-  line-height: 140px !important; }
-
-.wh__150 {
-  width: 150px !important;
-  height: 150px !important; }
-
-.lh__150 {
-  line-height: 150px !important; }
-
-.wh__160 {
-  width: 160px !important;
-  height: 160px !important; }
-
-.lh__160 {
-  line-height: 160px !important; }
-
-.wh__170 {
-  width: 170px !important;
-  height: 170px !important; }
-
-.lh__170 {
-  line-height: 170px !important; }
-
-.wh__180 {
-  width: 180px !important;
-  height: 180px !important; }
-
-.lh__180 {
-  line-height: 180px !important; }
-
-.wh__190 {
-  width: 190px !important;
-  height: 190px !important; }
-
-.lh__190 {
-  line-height: 190px !important; }
-
-.wh__200 {
-  width: 200px !important;
-  height: 200px !important; }
-
-.lh__200 {
-  line-height: 200px !important; }
-
-.wh__210 {
-  width: 210px !important;
-  height: 210px !important; }
-
-.lh__210 {
-  line-height: 210px !important; }
-
-.wh__220 {
-  width: 220px !important;
-  height: 220px !important; }
-
-.lh__220 {
-  line-height: 220px !important; }
-
-.wh__230 {
-  width: 230px !important;
-  height: 230px !important; }
-
-.lh__230 {
-  line-height: 230px !important; }
-
-.wh__240 {
-  width: 240px !important;
-  height: 240px !important; }
-
-.lh__240 {
-  line-height: 240px !important; }
-
-.wh__250 {
-  width: 250px !important;
-  height: 250px !important; }
-
-.lh__250 {
-  line-height: 250px !important; }
-
-.wh__260 {
-  width: 260px !important;
-  height: 260px !important; }
-
-.lh__260 {
-  line-height: 260px !important; }
-
-.wh__270 {
-  width: 270px !important;
-  height: 270px !important; }
-
-.lh__270 {
-  line-height: 270px !important; }
-
-.wh__280 {
-  width: 280px !important;
-  height: 280px !important; }
-
-.lh__280 {
-  line-height: 280px !important; }
-
-.wh__290 {
-  width: 290px !important;
-  height: 290px !important; }
-
-.lh__290 {
-  line-height: 290px !important; }
-
-.wh__300 {
-  width: 300px !important;
-  height: 300px !important; }
-
-.lh__300 {
-  line-height: 300px !important; }
-
-.wh__310 {
-  width: 310px !important;
-  height: 310px !important; }
-
-.lh__310 {
-  line-height: 310px !important; }
-
-.wh__320 {
-  width: 320px !important;
-  height: 320px !important; }
-
-.lh__320 {
-  line-height: 320px !important; }
-
-.wh__330 {
-  width: 330px !important;
-  height: 330px !important; }
-
-.lh__330 {
-  line-height: 330px !important; }
-
-.wh__340 {
-  width: 340px !important;
-  height: 340px !important; }
-
-.lh__340 {
-  line-height: 340px !important; }
-
-.wh__350 {
-  width: 350px !important;
-  height: 350px !important; }
-
-.lh__350 {
-  line-height: 350px !important; }
-
-.wh__360 {
-  width: 360px !important;
-  height: 360px !important; }
-
-.lh__360 {
-  line-height: 360px !important; }
-
-.wh__370 {
-  width: 370px !important;
-  height: 370px !important; }
-
-.lh__370 {
-  line-height: 370px !important; }
-
-.wh__380 {
-  width: 380px !important;
-  height: 380px !important; }
-
-.lh__380 {
-  line-height: 380px !important; }
-
-.wh__390 {
-  width: 390px !important;
-  height: 390px !important; }
-
-.lh__390 {
-  line-height: 390px !important; }
-
-.wh__400 {
-  width: 400px !important;
-  height: 400px !important; }
-
-.lh__400 {
-  line-height: 400px !important; }
-
-.wh__410 {
-  width: 410px !important;
-  height: 410px !important; }
-
-.lh__410 {
-  line-height: 410px !important; }
-
-.wh__420 {
-  width: 420px !important;
-  height: 420px !important; }
-
-.lh__420 {
-  line-height: 420px !important; }
-
-.wh__430 {
-  width: 430px !important;
-  height: 430px !important; }
-
-.lh__430 {
-  line-height: 430px !important; }
-
-.wh__440 {
-  width: 440px !important;
-  height: 440px !important; }
-
-.lh__440 {
-  line-height: 440px !important; }
-
-.wh__450 {
-  width: 450px !important;
-  height: 450px !important; }
-
-.lh__450 {
-  line-height: 450px !important; }
-
-.wh__460 {
-  width: 460px !important;
-  height: 460px !important; }
-
-.lh__460 {
-  line-height: 460px !important; }
-
-.wh__470 {
-  width: 470px !important;
-  height: 470px !important; }
-
-.lh__470 {
-  line-height: 470px !important; }
-
-.wh__480 {
-  width: 480px !important;
-  height: 480px !important; }
-
-.lh__480 {
-  line-height: 480px !important; }
-
-.wh__490 {
-  width: 490px !important;
-  height: 490px !important; }
-
-.lh__490 {
-  line-height: 490px !important; }
-
-.wh__500 {
-  width: 500px !important;
-  height: 500px !important; }
-
-.lh__500 {
-  line-height: 500px !important; }
-
-.lh__1 {
-  line-height: 1 !important; }
-
-.no-wrap {
-  white-space: nowrap; }
-
-.ov-h {
-  overflow: hidden; }
-
-.va-m {
-  vertical-align: middle; }
-
-.bg-inherit {
-  background-color: inherit; }
-
-.bg-black {
-  background-color: black; }
-
-.v-center:before {
-  content: '';
-  display: inline-block;
-  height: 100%;
-  vertical-align: middle;
-  margin-top: -0.25em;
-  /* Adjusts for spacing */ }
-
-.fluid-height {
-  height: 100%; }
-
-.visibility__hidden {
-  visibility: hidden; }
-
-.pointerEvents__none {
-  pointer-events: none; }
-
-/* Padding Helpers */
-.pn {
-  padding: 0 !important; }
-
-.p1 {
-  padding: 1px !important; }
-
-.p2 {
-  padding: 2px !important; }
-
-.p3 {
-  padding: 3px !important; }
-
-.p4 {
-  padding: 4px !important; }
-
-.p5 {
-  padding: 5px !important; }
-
-.p6 {
-  padding: 6px !important; }
-
-.p7 {
-  padding: 7px !important; }
-
-.p8 {
-  padding: 8px !important; }
-
-.p10 {
-  padding: 10px !important; }
-
-.p12 {
-  padding: 12px !important; }
-
-.p15 {
-  padding: 15px !important; }
-
-.p20 {
-  padding: 20px !important; }
-
-.p25 {
-  padding: 25px !important; }
-
-.p30 {
-  padding: 30px !important; }
-
-.p35 {
-  padding: 35px !important; }
-
-.p40 {
-  padding: 40px !important; }
-
-.p50 {
-  padding: 50px !important; }
-
-.ptn {
-  padding-top: 0 !important; }
-
-.pt5 {
-  padding-top: 5px !important; }
-
-.pt10 {
-  padding-top: 10px !important; }
-
-.pt15 {
-  padding-top: 15px !important; }
-
-.pt20 {
-  padding-top: 20px !important; }
-
-.pt25 {
-  padding-top: 25px !important; }
-
-.pt30 {
-  padding-top: 30px !important; }
-
-.pt35 {
-  padding-top: 35px !important; }
-
-.pt40 {
-  padding-top: 40px !important; }
-
-.pt50 {
-  padding-top: 50px !important; }
-
-.prn {
-  padding-right: 0 !important; }
-
-.pr5 {
-  padding-right: 5px !important; }
-
-.pr10 {
-  padding-right: 10px !important; }
-
-.pr15 {
-  padding-right: 15px !important; }
-
-.pr20 {
-  padding-right: 20px !important; }
-
-.pr25 {
-  padding-right: 25px !important; }
-
-.pr30 {
-  padding-right: 30px !important; }
-
-.pr35 {
-  padding-right: 35px !important; }
-
-.pr40 {
-  padding-right: 40px !important; }
-
-.pr50 {
-  padding-right: 50px !important; }
-
-.pbn {
-  padding-bottom: 0 !important; }
-
-.pb5 {
-  padding-bottom: 5px !important; }
-
-.pb10 {
-  padding-bottom: 10px !important; }
-
-.pb15 {
-  padding-bottom: 15px !important; }
-
-.pb20 {
-  padding-bottom: 20px !important; }
-
-.pb25 {
-  padding-bottom: 25px !important; }
-
-.pb30 {
-  padding-bottom: 30px !important; }
-
-.pb35 {
-  padding-bottom: 35px !important; }
-
-.pb40 {
-  padding-bottom: 40px !important; }
-
-.pb50 {
-  padding-bottom: 50px !important; }
-
-.pln {
-  padding-left: 0 !important; }
-
-.pl5 {
-  padding-left: 5px !important; }
-
-.pl10 {
-  padding-left: 10px !important; }
-
-.pl15 {
-  padding-left: 15px !important; }
-
-.pl20 {
-  padding-left: 20px !important; }
-
-.pl25 {
-  padding-left: 25px !important; }
-
-.pl30 {
-  padding-left: 30px !important; }
-
-.pl35 {
-  padding-left: 35px !important; }
-
-.pl40 {
-  padding-left: 40px !important; }
-
-.pl50 {
-  padding-left: 50px !important; }
-
-/* Axis Padding (both top/bottom or left/right) */
-.pv5 {
-  padding-top: 5px !important;
-  padding-bottom: 5px !important; }
-
-.pv8 {
-  padding-top: 8px !important;
-  padding-bottom: 8px !important; }
-
-.pv10 {
-  padding-top: 10px !important;
-  padding-bottom: 10px !important; }
-
-.pv15 {
-  padding-top: 15px !important;
-  padding-bottom: 15px !important; }
-
-.pv20 {
-  padding-top: 20px !important;
-  padding-bottom: 20px !important; }
-
-.pv25 {
-  padding-top: 25px !important;
-  padding-bottom: 25px !important; }
-
-.pv30 {
-  padding-top: 30px !important;
-  padding-bottom: 30px !important; }
-
-.pv40 {
-  padding-top: 40px !important;
-  padding-bottom: 40px !important; }
-
-.pv50 {
-  padding-top: 50px !important;
-  padding-bottom: 50px !important; }
-
-.ph5 {
-  padding-left: 5px !important;
-  padding-right: 5px !important; }
-
-.ph8 {
-  padding-left: 8px !important;
-  padding-right: 8px !important; }
-
-.ph10 {
-  padding-left: 10px !important;
-  padding-right: 10px !important; }
-
-.ph15 {
-  padding-left: 15px !important;
-  padding-right: 15px !important; }
-
-.ph20 {
-  padding-left: 20px !important;
-  padding-right: 20px !important; }
-
-.ph25 {
-  padding-left: 25px !important;
-  padding-right: 25px !important; }
-
-.ph30 {
-  padding-left: 30px !important;
-  padding-right: 30px !important; }
-
-.ph40 {
-  padding-left: 40px !important;
-  padding-right: 40px !important; }
-
-.ph50 {
-  padding-left: 50px !important;
-  padding-right: 50px !important; }
-
-/* margin center helper */
-.mauto {
-  margin-left: auto;
-  margin-right: auto; }
-
-.mn {
-  margin: 0 !important; }
-
-.m1 {
-  margin: 1px !important; }
-
-.m2 {
-  margin: 2px !important; }
-
-.m3 {
-  margin: 3px !important; }
-
-.m4 {
-  margin: 4px !important; }
-
-.m5 {
-  margin: 5px !important; }
-
-.m8 {
-  margin: 8px !important; }
-
-.m10 {
-  margin: 10px !important; }
-
-.m15 {
-  margin: 15px !important; }
-
-.m20 {
-  margin: 20px !important; }
-
-.m25 {
-  margin: 25px !important; }
-
-.m30 {
-  margin: 30px !important; }
-
-.m35 {
-  margin: 35px !important; }
-
-.m40 {
-  margin: 40px !important; }
-
-.m50 {
-  margin: 50px !important; }
-
-.mtn {
-  margin-top: 0 !important; }
-
-.mt5 {
-  margin-top: 5px !important; }
-
-.mt10 {
-  margin-top: 10px !important; }
-
-.mt15 {
-  margin-top: 15px !important; }
-
-.mt20 {
-  margin-top: 20px !important; }
-
-.mt25 {
-  margin-top: 25px !important; }
-
-.mt30 {
-  margin-top: 30px !important; }
-
-.mt35 {
-  margin-top: 35px !important; }
-
-.mt40 {
-  margin-top: 40px !important; }
-
-.mt50 {
-  margin-top: 50px !important; }
-
-.mt70 {
-  margin-top: 70px !important; }
-
-.mrn {
-  margin-right: 0 !important; }
-
-.mr5 {
-  margin-right: 5px !important; }
-
-.mr10 {
-  margin-right: 10px !important; }
-
-.mr15 {
-  margin-right: 15px !important; }
-
-.mr20 {
-  margin-right: 20px !important; }
-
-.mr25 {
-  margin-right: 25px !important; }
-
-.mr30 {
-  margin-right: 30px !important; }
-
-.mr35 {
-  margin-right: 35px !important; }
-
-.mr40 {
-  margin-right: 40px !important; }
-
-.mr50 {
-  margin-right: 50px !important; }
-
-.mbn {
-  margin-bottom: 0 !important; }
-
-.mb5 {
-  margin-bottom: 5px !important; }
-
-.mb10 {
-  margin-bottom: 10px !important; }
-
-.mb15 {
-  margin-bottom: 15px !important; }
-
-.mb20 {
-  margin-bottom: 20px !important; }
-
-.mb25 {
-  margin-bottom: 25px !important; }
-
-.mb30 {
-  margin-bottom: 30px !important; }
-
-.mb35 {
-  margin-bottom: 35px !important; }
-
-.mb40 {
-  margin-bottom: 40px !important; }
-
-.mb50 {
-  margin-bottom: 50px !important; }
-
-.mb70 {
-  margin-bottom: 70px !important; }
-
-.mln {
-  margin-left: 0 !important; }
-
-.ml5 {
-  margin-left: 5px !important; }
-
-.ml10 {
-  margin-left: 10px !important; }
-
-.ml15 {
-  margin-left: 15px !important; }
-
-.ml20 {
-  margin-left: 20px !important; }
-
-.ml25 {
-  margin-left: 25px !important; }
-
-.ml30 {
-  margin-left: 30px !important; }
-
-.ml35 {
-  margin-left: 35px !important; }
-
-.ml40 {
-  margin-left: 40px !important; }
-
-.ml50 {
-  margin-left: 50px !important; }
-
-/* Axis Margins (both top/bottom or left/right) */
-.mv5 {
-  margin-top: 5px !important;
-  margin-bottom: 5px !important; }
-
-.mv10 {
-  margin-top: 10px !important;
-  margin-bottom: 10px !important; }
-
-.mv15 {
-  margin-top: 15px !important;
-  margin-bottom: 15px !important; }
-
-.mv20 {
-  margin-top: 20px !important;
-  margin-bottom: 20px !important; }
-
-.mv25 {
-  margin-top: 25px !important;
-  margin-bottom: 25px !important; }
-
-.mv30 {
-  margin-top: 30px !important;
-  margin-bottom: 30px !important; }
-
-.mv40 {
-  margin-top: 40px !important;
-  margin-bottom: 40px !important; }
-
-.mv50 {
-  margin-top: 50px !important;
-  margin-bottom: 50px !important; }
-
-.mv70 {
-  margin-top: 70px !important;
-  margin-bottom: 70px !important; }
-
-.mh5 {
-  margin-left: 5px !important;
-  margin-right: 5px !important; }
-
-.mh10 {
-  margin-left: 10px !important;
-  margin-right: 10px !important; }
-
-.mh15 {
-  margin-left: 15px !important;
-  margin-right: 15px !important; }
-
-.mh20 {
-  margin-left: 20px !important;
-  margin-right: 20px !important; }
-
-.mh25 {
-  margin-left: 25px !important;
-  margin-right: 25px !important; }
-
-.mh30 {
-  margin-left: 30px !important;
-  margin-right: 30px !important; }
-
-.mh40 {
-  margin-left: 40px !important;
-  margin-right: 40px !important; }
-
-.mh50 {
-  margin-left: 50px !important;
-  margin-right: 50px !important; }
-
-.mh70 {
-  margin-left: 70px !important;
-  margin-right: 70px !important; }
-
-/* Negative Margin Helpers */
-.mtn5 {
-  margin-top: -5px !important; }
-
-.mtn10 {
-  margin-top: -10px !important; }
-
-.mtn15 {
-  margin-top: -15px !important; }
-
-.mtn20 {
-  margin-top: -20px !important; }
-
-.mtn30 {
-  margin-top: -30px !important; }
-
-.mrn5 {
-  margin-right: -5px !important; }
-
-.mrn10 {
-  margin-right: -10px !important; }
-
-.mrn15 {
-  margin-right: -15px !important; }
-
-.mrn20 {
-  margin-right: -20px !important; }
-
-.mrn30 {
-  margin-right: -30px !important; }
-
-.mbn5 {
-  margin-bottom: -5px !important; }
-
-.mbn10 {
-  margin-bottom: -10px !important; }
-
-.mbn15 {
-  margin-bottom: -15px !important; }
-
-.mbn20 {
-  margin-bottom: -20px !important; }
-
-.mbn30 {
-  margin-bottom: -30px !important; }
-
-.mln5 {
-  margin-left: -5px !important; }
-
-.mln10 {
-  margin-left: -10px !important; }
-
-.mln15 {
-  margin-left: -15px !important; }
-
-.mln20 {
-  margin-left: -20px !important; }
-
-.mln30 {
-  margin-left: -30px !important; }
-
-/* Vertical Negative Margin "mv" + "n" + "x" */
-.mvn5 {
-  margin-top: -5px !important;
-  margin-bottom: -5px !important; }
-
-.mvn10 {
-  margin-top: -10px !important;
-  margin-bottom: -10px !important; }
-
-.mvn15 {
-  margin-top: -15px !important;
-  margin-bottom: -15px !important; }
-
-.mvn20 {
-  margin-top: -20px !important;
-  margin-bottom: -20px !important; }
-
-.mvn30 {
-  margin-top: -30px !important;
-  margin-bottom: -30px !important; }
-
-/* Horizontal Negative Margin "mh" + "n" + "x" */
-.mhn5 {
-  margin-left: -5px !important;
-  margin-right: -5px !important; }
-
-.mhn10 {
-  margin-left: -10px !important;
-  margin-right: -10px !important; }
-
-.mhn15 {
-  margin-left: -15px !important;
-  margin-right: -15px !important; }
-
-.mhn20 {
-  margin-left: -20px !important;
-  margin-right: -20px !important; }
-
-.mhn30 {
-  margin-left: -30px !important;
-  margin-right: -30px !important; }
-
-/* Vertical Align Helpers */
-.va-t {
-  vertical-align: top !important; }
-
-.va-m {
-  vertical-align: middle !important; }
-
-.va-b {
-  vertical-align: bottom !important; }
-
-.va-s {
-  vertical-align: super !important; }
-
-/* Text Helpers */
-.text-left {
-  text-align: left !important; }
-
-.text-right {
-  text-align: right !important; }
-
-.text-center {
-  text-align: center !important; }
-
-.text-justify {
-  text-align: justify !important; }
-
-.text-nowrap {
-  white-space: nowrap !important; }
-
-/* Inline Block Helper */
-.ib,
-.inline-object {
-  display: inline-block !important; }
-
-.clear {
-  clear: both; }
-
-.wvWarning {
-  position: relative;
-  width: 320px;
-  min-height: 130px;
-  z-index: 999;
-  left: calc(50% - 160px);
-  border: #000 solid 1px;
-  -webkit-border-radius: 7px;
-  -moz-border-radius: 7px;
-  border-radius: 7px;
-  color: #FF5722;
-  box-shadow: 0px 3px 23px #ff980078;
-  -webkit-animation-name: example;
-  /* Safari 4.0 - 8.0 */
-  -webkit-animation-duration: 3s;
-  /* Safari 4.0 - 8.0 */
-  -webkit-animation-fill-mode: both;
-  /* Safari 4.0 - 8.0 */
-  animation-name: example;
-  animation-duration: 2s;
-  animation-fill-mode: both;
-  animation-timing-function: ease-out; }
-
-@-webkit-keyframes example {
-  from {
-    top: 0vh;
-    opacity: 0;
-    background: #868686; }
-  to {
-    top: 10vh;
-    opacity: 1;
-    background: #ffffff; } }
-
-@keyframes example {
-  from {
-    top: 0vh;
-    opacity: 0;
-    background: #868686; }
-  to {
-    top: 10vh;
-    opacity: 1;
-    background: #ffffff; } }
-
-.wvWarning-content {
-  position: relative;
-  width: 190px;
-  min-height: 88px;
-  max-height: 80vh;
-  margin: auto; }
-
-.wvWarning-icon {
-  font-size: 32px; }
-
-.wvWarning-text {
-  position: relative; }
-
-.wvWarning-button {
-  background-color: #f1ededcc;
-  color: #607D8B;
-  width: 50px;
-  font-weight: 600;
-  margin-top: 2px;
-  margin-right: 30px; }
-
-.wvScreenToSmallWarning {
-  position: fixed;
-  display: block;
-  top: 0;
-  left: 0;
-  background-color: white;
-  color: #333;
-  width: 100%;
-  height: 100%;
-  z-index: 1000; }
-
-.wvScreenToSmallWarning-content {
-  padding: 10px;
-  text-align: center; }
-
-/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
-@media only screen and (min-width: 550px) and (min-height: 280px) {
-  .wvScreenToSmallWarning {
-    display: none; } }
-
-/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */
-@media only screen and (min-width: 280px) and (min-height: 550px) {
-  .wvScreenToSmallWarning {
-    display: none; } }
-
-wv-notice {
-  display: block;
-  height: 100%;
-  width: 100%; }
-
-.wvNotice {
-  padding: 0.5rem 0.25rem;
-  height: 100%; }
-
-.wvNotice__text {
-  position: relative;
-  top: 50%;
-  transform: translateY(-50%);
-  text-align: center;
-  margin-left: 1rem;
-  font-weight: 400;
-  color: #b3b3b3;
-  float: left;
-  width: calc(100% - 7rem); }
-
-.wvNotice__closeButton {
-  float: right;
-  margin-right: 0.5em;
-  position: relative;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 3.5rem;
-  height: 2.5rem;
-  text-align: center;
-  font-size: 1em;
-  font-weight: 100;
-  line-height: 2.2rem;
-  cursor: pointer;
-  border: 1px solid #454545; }
-
-/* layout: left section */
-.wvLayoutLeft {
-  position: absolute;
-  z-index: 2;
-  background-color: black;
-  width: 32rem;
-  left: 0; }
-  .wvLayoutLeft.wvLayoutLeft--toppadding {
-    top: 42px; }
-  .wvLayoutLeft:not(.wvLayoutLeft--toppadding) {
-    top: 0; }
-  @media screen and (max-device-width: 374px) {
-    .wvLayoutLeft.wvLayoutLeft--bottompadding {
-      bottom: 7rem; } }
-  @media screen and (min-device-width: 375px) {
-    .wvLayoutLeft.wvLayoutLeft--bottompadding {
-      bottom: 5rem; } }
-  .wvLayoutLeft:not(.wvLayoutLeft--bottompadding) {
-    bottom: 0; }
-  .wvLayoutLeft.wvLayoutLeft--closed {
-    transform: translateX(-32rem); }
-    .wvLayoutLeft.wvLayoutLeft--closed.wvLayoutLeft--small {
-      transform: translateX(-12rem); }
-  .wvLayoutLeft.wvLayoutLeft--small {
-    width: 12rem; }
-    .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentTop, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentMiddle, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentBottom {
-      width: 100%; }
-
-.wvLayoutLeft__content {
-  border-right: 1px solid #AAA;
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto;
-  height: 100%; }
-
-.wvLayoutLeft__contentTop {
-  padding: 0rem 1rem 0rem 1rem;
-  width: 31.9rem; }
-  .wvLayoutLeft__contentTop:after {
-    content: "";
-    display: block;
-    height: 0;
-    width: 0;
-    clear: both; }
-
-.wvLayoutLeft__contentMiddle {
-  flex: 1 0 auto;
-  width: 31.9rem; }
-
-.wvLayoutLeft__contentBottom {
-  width: 31.9rem; }
-
-.wvLayout__leftBottom.wvLayout__leftBottom--enabled {
-  border-top: 1px solid rgba(255, 255, 255, 0.2);
-  margin-top: 1rem;
-  padding: 1rem; }
-
-.wvLayoutLeft__actions, .wvLayoutLeft__actions--outside {
-  display: block;
-  position: absolute;
-  right: 1px;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 25px; }
-
-.wvLayoutLeft__actions--outside {
-  right: -25px; }
-
-.wvLayoutLeft__action {
-  background-color: #3498db;
-  opacity: 0.5;
-  color: white;
-  transition: none; }
-  .wvLayoutLeft__action:hover, .wvLayoutLeft__action:focus {
-    opacity: 1; }
-
-/* layout: right section */
-.wvLayout__right {
-  display: block;
-  position: absolute;
-  z-index: 2;
-  background-color: black;
-  width: 85px;
-  right: 0; }
-  .wvLayout__right.wvLayout__right--toppadding {
-    top: 42px; }
-  .wvLayout__right:not(.wvLayout__right--toppadding) {
-    top: 0; }
-  @media screen and (max-device-width: 374px) {
-    .wvLayout__right.wvLayout__right--bottompadding {
-      bottom: 7rem; } }
-  @media screen and (min-device-width: 375px) {
-    .wvLayout__right.wvLayout__right--bottompadding {
-      bottom: 5rem; } }
-  .wvLayout__right:not(.wvLayout__right--bottompadding) {
-    bottom: 0; }
-  .wvLayout__right.wvLayout__right--closed {
-    transform: translateX(85px); }
-  .wvLayout__right > wv-layout-right,
-  .wvLayout__right > wv-layout-right > .wvViewer__asideRight {
-    display: block;
-    height: 100%;
-    width: 100%; }
-
-.wvAsideRight__content {
-  height: 100%;
-  float: left;
-  border-left: 1px solid #AAA;
-  padding: 0 5px;
-  width: 32rem; }
-
-.wvAsideRight__actions, .wvAsideRight__actions--outside {
-  display: block;
-  position: absolute;
-  left: 1px;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 25px;
-  z-index: 3; }
-
-.wvAsideRight__actions--outside {
-  left: -25px; }
-
-.wvAsideRight__action {
-  background-color: #3498db;
-  opacity: 0.5;
-  color: white;
-  transition: none; }
-  .wvAsideRight__action:hover, .wvAsideRight__action:focus {
-    opacity: 1; }
-
-.wvAsideRight__fixOpenFullyTooltip + .tooltip {
-  left: -6.633em !important;
-  top: 1px !important; }
-
-/* layout: bottom section */
-.wvLayout__bottom {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  right: 0;
-  background-color: #1a1a1a; }
-  @media screen and (max-device-width: 374px) {
-    .wvLayout__bottom {
-      height: 7rem; } }
-  @media screen and (min-device-width: 375px) {
-    .wvLayout__bottom {
-      height: 5rem; } }
-
-/* layout: main section */
-.wvLayout__main {
-  position: absolute;
-  text-align: center;
-  right: 0;
-  left: 0; }
-  .wvLayout__main .wvLayout__splitpane--toolbarAtTop {
-    display: block;
-    height: calc(100% - 42px);
-    width: 100%;
-    position: relative;
-    top: 42px; }
-  .wvLayout__main .wvLayout__splitpane--toolbarAtRight {
-    display: block;
-    height: 100%;
-    width: calc(100% - 42px); }
-  .wvLayout__main .wvLayout__splitpane--bigToolbarAtTop {
-    display: block;
-    height: calc(100% - 68px);
-    width: 100%;
-    position: relative;
-    top: 68px; }
-  .wvLayout__main .wvLayout__splitpane--bigToolbarAtRight {
-    display: block;
-    height: 100%;
-    width: calc(100% - 68px); }
-  .wvLayout__main.wvLayout__main--toppadding {
-    top: 42px; }
-  .wvLayout__main:not(.wvLayout__main--toppadding) {
-    top: 0; }
-  .wvLayout__main.wvLayout__main--bottompadding {
-    bottom: 440px; }
-    @media screen and (max-device-width: 374px) {
-      .wvLayout__main.wvLayout__main--bottompadding {
-        bottom: 7rem; } }
-    @media screen and (min-device-width: 375px) {
-      .wvLayout__main.wvLayout__main--bottompadding {
-        bottom: 5rem; } }
-  .wvLayout__main:not(.wvLayout__main--bottompadding) {
-    bottom: 0; }
-  .wvLayout__main.wvLayout__main--leftpadding {
-    left: 32rem; }
-  .wvLayout__main {
-    left: 0px; }
-  .wvLayout__main.wvLayout__main--smallleftpadding {
-    left: 12rem; }
-  .wvLayout__main.wvLayout__main--rightpadding {
-    right: 85px; }
-  .wvLayout__main:not(.wvLayout__main--rightpadding) {
-    right: 0px; }
-
-/* global */
-.popover {
-  color: black; }
-
-.wvViewer__editor--full {
-  position: absolute;
-  top: 0;
-  right: 0;
-  z-index: 10;
-  opacity: 0;
-  transform: translateX(100%);
-  width: 100%;
-  height: 100%;
-  background-color: white;
-  color: #666666; }
-  .wvViewer__editor--full.opened {
-    opacity: 1;
-    transform: translateX(0); }
-
-.wvViewer__topBar {
-  width: 100%;
-  overflow-y: auto;
-  white-space: nowrap;
-  max-width: 100%; }
-
-.wvViewer__buttonGroup {
-  display: inline-block; }
-
-.wvViewer__buttonGroup--asideWidth {
-  width: 32rem;
-  padding-right: 1rem; }
-
-.wvViewer__buttonGroup--contentWidth {
-  width: calc(100% - 32rem);
-  padding-left: 1rem;
-  max-height: 4.2rem; }
-
-.wvViewer__iframe {
-  position: absolute;
-  left: 0;
-  top: 0; }
-
-/* bottom bar */
-.wvViewer__bottomBar, .wvViewer__bottomBar--expanded, .wvViewer__bottomBar--minimized {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  width: 100%;
-  background-color: #111111; }
-
-.wvViewer__bottomBar--expanded {
-  height: 80px;
-  color: white; }
-  .wvViewer__bottomBar--expanded .wvViewer__timeline {
-    width: calc(100% - 80px); }
-  .wvViewer__bottomBar--expanded .wvTimeline__hotspots {
-    bottom: -40px; }
-
-.wvViewer__bottomBar--minimized {
-  color: white;
-  padding-top: 0.5rem;
-  padding-bottom: 0.5rem;
-  padding-left: 2.5rem; }
-  .wvViewer__bottomBar--minimized .wvTimeline__hotspot {
-    top: -40px;
-    opacity: 0;
-    visibility: hidden;
-    z-index: -1; }
-  .wvViewer__bottomBar--minimized:hover .wvTimeline__hotspot {
-    opacity: 1;
-    visibility: visible;
-    z-index: 5;
-    transition-delay: 0s; }
-
-.wvViewer__timeline {
-  height: 24px;
-  line-height: 24px;
-  vertical-align: middle;
-  width: 100%; }
-
-.wvViewer__trademark {
-  display: inline-block;
-  float: right;
-  width: 80px;
-  height: 80px;
-  float: right;
-  line-height: 80px;
-  vertical-align: middle;
-  text-align: center; }
-
-.wvTimeline__input {
-  border-radius: 3px;
-  margin-top: 2px;
-  border: 1px solid #e7e7e7; }
-  .wvTimeline__input:focus {
-    outline: none; }
-
-.wvTimeline__actions {
-  display: inline-block;
-  border-right: 1px solid #e7e7e7; }
-
-.wvSerieslist {
-  margin: 0;
-  padding: 0;
-  list-style: none; }
-
-.wvSerieslist__seriesItem--selectable {
-  cursor: pointer !important; }
-  .wvSerieslist__seriesItem--selectable:hover {
-    color: white; }
-
-.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa {
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  font-size: 3.25rem;
-  line-height: 6.5rem;
-  text-align: center; }
-
-.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa {
-  color: #c3c3c3; }
-  .wvSerieslist__placeholderIcon--strikeout::after, .wvSerieslist__placeholderIcon--strikeout.fa::after {
-    position: absolute;
-    left: 0;
-    top: 50%;
-    right: 0;
-    transform: rotate(-45deg) scaleX(0.9);
-    border-top: 5px solid;
-    border-color: inherit;
-    content: ""; }
-
-.wvSerieslist__picture {
-  display: inline-block;
-  font-size: 14px;
-  width: 6.5rem;
-  height: 6.5rem;
-  position: relative;
-  z-index: -1; }
-
-.wvSerieslist__badge, .wvSerieslist__badge--blue, .wvSerieslist__badge--red, .wvSerieslist__badge--green, .wvSerieslist__badge--yellow, .wvSerieslist__badge--violet {
-  position: absolute;
-  bottom: 5px;
-  right: 5px;
-  font-size: 10px;
-  line-height: 15px;
-  width: 15px;
-  height: 15px;
-  border-radius: 100%;
-  background-color: gray;
-  vertical-align: middle;
-  text-align: center;
-  font-weight: bold; }
-
-.wvSerieslist__information {
-  font-size: 14px;
-  float: right;
-  padding-left: 1rem;
-  width: calc(100% - 6.5rem);
-  height: 6.5rem; }
-
-.wvSerieslist__label {
-  white-space: nowrap;
-  width: calc(100% - 10px);
-  overflow: hidden;
-  height: 3.25rem;
-  line-height: 3.25rem;
-  vertical-align: middle; }
-
-.wvSerieslist__timeline {
-  height: 3.25rem;
-  line-height: 3.25rem;
-  vertical-align: middle; }
-
-.wvSerieslist__seriesItem {
-  position: relative;
-  padding-left: 0;
-  list-style: none;
-  font-size: 0;
-  border-right: 0.2rem solid transparent;
-  border-left: 0.2rem solid transparent;
-  border-top: 0.2rem solid transparent;
-  border-bottom: 0.2rem solid transparent;
-  border-corner-shape: notch;
-  line-height: 0px;
-  margin: 0.1rem; }
-  .wvSerieslist__seriesItem.active {
-    border-color: rgba(255, 255, 255, 0.6);
-    border-style: solid; }
-  .wvSerieslist__seriesItem.highlighted {
-    border-color: white;
-    border-style: solid; }
-  .wvSerieslist__seriesItem:hover, .wvSerieslist__seriesItem:focus, .wvSerieslist__seriesItem.focused {
-    border-style: dashed;
-    border-color: rgba(255, 255, 255, 0.8); }
-
-.wvSerieslist__seriesItem--list {
-  display: block; }
-
-.wvSerieslist__seriesItem--grid {
-  display: inline-block; }
-
-.wvSerieslist__seriesItem--oneCol {
-  text-align: center; }
-
-.wvSerieslist__seriesItem--activated,
-.wvSerieslist__videoItem--activated,
-.wvSerieslist__pdfItem--activated {
-  border: 0.2rem solid #3398db !important; }
-
-.wvSerieslist__badge--blue {
-  background-color: rgba(51, 152, 219, 0.7); }
-
-.wvSerieslist__badge--red {
-  background-color: rgba(206, 0, 0, 0.7); }
-
-.wvSerieslist__badge--green {
-  background-color: rgba(0, 160, 27, 0.7); }
-
-.wvSerieslist__badge--yellow {
-  background-color: rgba(220, 200, 0, 0.9); }
-
-.wvSerieslist__badge--violet {
-  background-color: rgba(255, 31, 255, 0.7); }
-
-.wvToolbar {
-  position: absolute; }
-
-.wvToolbar--top {
-  top: 0;
-  height: 42px;
-  right: 0;
-  text-align: right;
-  white-space: nowrap;
-  max-width: 100%; }
-
-.wvToolbar--right {
-  right: 0;
-  width: 42px;
-  height: 100%;
-  z-index: 2; }
-  .wvToolbar--right.wvToolbar--big {
-    width: 68px; }
-
-/* Splitpane Grid Configuration */
-.wvToolbar__splitpaneConfigPopover {
-  font-size: 0; }
-
-.wvToolbar__splitpaneConfigNotice {
-  font-size: 1.25rem;
-  font-style: italic;
-  text-align: center;
-  color: #333; }
-
-input[type="radio"].wvToolbar__splitpaneConfigButtonInput {
-  position: absolute;
-  width: 0;
-  height: 0;
-  left: 0;
-  top: 0;
-  bottom: 2px;
-  right: 0;
-  opacity: 0; }
-
-/* Windowing Preset */
-.wvToolbar__windowingPresetConfigNotice {
-  font-size: 1.25rem;
-  font-style: italic;
-  text-align: center;
-  color: #333; }
-
-.wvToolbar__windowingPresetList {
-  list-style: none;
-  margin: 0;
-  padding: 0;
-  font-size: 1.5rem; }
-
-.wvToolbar__windowingPresetListItem {
-  outline: none;
-  background-color: transparent;
-  border: none;
-  position: relative;
-  display: inline-block;
-  cursor: pointer;
-  font-variant: small-caps;
-  text-transform: lowercase;
-  text-align: center;
-  font-size: 1.3rem;
-  font-weight: 400;
-  line-height: 2.2rem;
-  color: #d9d9d9;
-  transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease;
-  margin: 0;
-  min-width: 3rem;
-  padding: 0 10px;
-  line-height: 3.6rem;
-  max-height: 2.8rem;
-  max-width: 100%;
-  overflow: hidden;
-  margin: 0.6rem;
-  margin-left: 0rem;
-  margin-right: 0rem;
-  line-height: 2rem;
-  padding-top: 0.1rem;
-  padding-bottom: 0.5rem;
-  font-size: 1.4rem;
-  border: 1px solid #454545;
-  font-family: Arial;
-  background-color: black;
-  color: #1a1a1a;
-  border: 1px solid #bababa;
-  background-color: white;
-  width: 100%;
-  margin: 0;
-  margin-left: 0 !important;
-  border-top: none;
-  border-bottom: none; }
-  .wvToolbar__windowingPresetListItem:hover {
-    text-decoration: none;
-    color: white; }
-  .wvToolbar__windowingPresetListItem + .wvToolbar__windowingPresetListItem {
-    margin-left: 0.7rem; }
-  .wvToolbar__windowingPresetListItem:hover {
-    background-color: #1a1a1a; }
-  .wvToolbar__windowingPresetListItem > .glyphicon {
-    position: relative;
-    display: inline-block;
-    top: 3px;
-    margin-right: 4px; }
-  .wvToolbar__windowingPresetListItem:hover {
-    color: #1a1a1a;
-    background-color: #e6e6e6; }
-
-.wvVideo {
-  position: absolute;
-  top: 50%;
-  left: 0;
-  width: 100%;
-  height: auto;
-  transform: translateY(-50%); }
-
-.wvStudyInformationBreadcrumb__patient {
-  display: inline-block;
-  background-color: rgba(255, 255, 255, 0.15);
-  padding: 0.2rem 1rem 0.3rem 1rem;
-  text-align: center;
-  font-size: 1em;
-  margin: 0.6rem;
-  font-weight: 400;
-  line-height: 2.3rem;
-  margin-right: 0; }
-
-.wvStudyInformationBreadcrumb__study {
-  display: inline-block;
-  background-color: rgba(255, 255, 255, 0.15);
-  padding: 0.2rem 1rem 0.3rem 1rem;
-  text-align: center;
-  font-size: 1em;
-  margin: 0.6rem;
-  font-weight: 400;
-  line-height: 2.3rem; }
-
-.wvSelectionActionlist {
-  display: block;
-  text-align: center; }
-
-/* wvb-ui stuffs */
-.wv-overlay {
-  color: orange; }
-
-.wv-overlay-icon {
-  width: 64px; }
-
-.wvOverlay__studyBadge, .wvOverlay__studyBadge--blue, .wvOverlay__studyBadge--red, .wvOverlay__studyBadge--green, .wvOverlay__studyBadge--yellow, .wvOverlay__studyBadge--violet {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 1.5rem;
-  height: 1.5rem;
-  background-color: gray;
-  z-index: 1; }
-
-.wv-overlay-topleft {
-  position: absolute;
-  top: 0rem;
-  left: 0rem;
-  text-align: left; }
-
-.wv-overlay-topright {
-  position: absolute;
-  top: 0rem;
-  right: 0rem;
-  text-align: right; }
-
-.wv-overlay-bottomright {
-  position: absolute;
-  bottom: 2em;
-  right: 0rem;
-  text-align: right; }
-
-.wv-overlay-bottomleft {
-  position: absolute;
-  bottom: 2em;
-  left: 0rem;
-  text-align: left; }
-
-.wv-overlay-timeline-wrapper {
-  position: absolute;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: 1; }
-
-.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft {
-  padding: 2rem;
-  transition: color 500ms, background-color 500ms;
-  background-color: rgba(0, 0, 0, 0.66); }
-
-.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover {
-  background-color: rgba(0, 0, 0, 0.9); }
-
-.wvPaneOverlay {
-  position: absolute;
-  top: 50%;
-  width: 100%;
-  transform: translateY(-50%);
-  font-weight: 100;
-  text-align: center;
-  color: white;
-  font-size: 2rem; }
-
-.wv-overlay-scrollbar-loaded {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  height: 5px;
-  background-color: red;
-  will-change: right;
-  transform-origin: 0% 50%; }
-
-.wv-overlay-scrollbar-loading {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  height: 5px;
-  background-color: #660000;
-  will-change: right;
-  transform-origin: 0% 50%; }
-
-.wv-overlay-scrollbar-text {
-  position: absolute;
-  bottom: calc(1em + 5px);
-  left: 5px;
-  height: 1em;
-  color: red;
-  font-size: 0.8em;
-  font-family: helvetica; }
-
-.wvOverlay__studyBadge--blue {
-  background-color: rgba(51, 152, 219, 0.7); }
-
-.wvOverlay__studyBadge--red {
-  background-color: rgba(206, 0, 0, 0.7); }
-
-.wvOverlay__studyBadge--green {
-  background-color: rgba(0, 160, 27, 0.7); }
-
-.wvOverlay__studyBadge--yellow {
-  background-color: rgba(220, 200, 0, 0.9); }
-
-.wvOverlay__studyBadge--violet {
-  background-color: rgba(255, 31, 255, 0.7); }
-
-wv-pdf-viewer {
-  display: block;
-  width: 100%;
-  height: 100%; }
-
-#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton {
-  background-color: inherit;
-  color: white;
-  border: none;
-  padding: 2px;
-  margin-left: 4px;
-  margin-right: 2px; }
-  #toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton:hover {
-    color: black; }
-
-.fa.fa-window-close.wv-pdf-viewer-closebuttonicon {
-  font-size: 2rem;
-  line-height: 28px; }
-
-.pdfjs .textLayer {
-  position: absolute;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  opacity: 0.2; }
-
-.pdfjs .textLayer > div {
-  color: transparent;
-  position: absolute;
-  white-space: pre;
-  cursor: text;
-  -webkit-transform-origin: 0 0;
-  -moz-transform-origin: 0 0;
-  -o-transform-origin: 0 0;
-  -ms-transform-origin: 0 0;
-  transform-origin: 0 0; }
-
-.pdfjs .textLayer .highlight {
-  margin: -1px;
-  padding: 1px;
-  background-color: #b400aa;
-  border-radius: 4px; }
-
-.pdfjs .textLayer .highlight.begin {
-  border-radius: 4px 0 0 4px; }
-
-.pdfjs .textLayer .highlight.end {
-  border-radius: 0 4px 4px 0; }
-
-.pdfjs .textLayer .highlight.middle {
-  border-radius: 0; }
-
-.pdfjs .textLayer .highlight.selected {
-  background-color: #006400; }
-
-.pdfjs .textLayer ::selection {
-  background: #00f; }
-
-.pdfjs .textLayer ::-moz-selection {
-  background: #00f; }
-
-.pdfjs .pdfViewer .canvasWrapper {
-  overflow: hidden; }
-
-.pdfjs .pdfViewer .page {
-  direction: ltr;
-  width: 816px;
-  height: 1056px;
-  margin: 1px auto -8px;
-  position: relative;
-  overflow: visible;
-  border: 9px solid transparent;
-  background-clip: content-box;
-  border-image: url("../images/pdf.js-viewer/shadow.png") 9 9 repeat;
-  background-color: #fff; }
-
-body {
-  height: 100%; }
-
-.pdfjs .pdfViewer.removePageBorders .page {
-  margin: 0 auto 10px;
-  border: none; }
-
-.pdfjs .pdfViewer .page canvas {
-  margin: 0;
-  display: block; }
-
-.pdfjs .pdfViewer .page .loadingIcon {
-  position: absolute;
-  display: block;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  background: url("../images/pdf.js-viewer/loading-icon.gif") center no-repeat; }
-
-.pdfjs .pdfViewer .page .annotLink > a:hover {
-  opacity: .2;
-  background: #ff0;
-  box-shadow: 0 2px 10px #ff0; }
-
-.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0; }
-
-.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0; }
-
-.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
-  margin-bottom: 100% !important;
-  border: 0; }
-
-.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0; }
-
-.pdfjs .pdfViewer .page .annotText > img {
-  position: absolute;
-  cursor: pointer; }
-
-.pdfjs .pdfViewer .page .annotTextContentWrapper {
-  position: absolute;
-  width: 20em; }
-
-.pdfjs .pdfViewer .page .annotTextContent {
-  z-index: 200;
-  float: left;
-  max-width: 20em;
-  background-color: #FF9;
-  box-shadow: 0 2px 5px #333;
-  border-radius: 2px;
-  padding: .6em;
-  cursor: pointer; }
-
-.pdfjs .pdfViewer .page .annotTextContent > h1 {
-  font-size: 1em;
-  border-bottom: 1px solid #000;
-  padding-bottom: 0.2em; }
-
-.pdfjs .pdfViewer .page .annotTextContent > p {
-  padding-top: 0.2em; }
-
-.pdfjs .pdfViewer .page .annotLink > a {
-  position: absolute;
-  font-size: 1em;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%; }
-
-.pdfjs .pdfViewer .page .annotLink > a {
-  background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA LAAAAAABAAEAAAIBRAA7") 0 0 repeat; }
-
-.pdfjs * {
-  padding: 0;
-  margin: 0; }
-
-html {
-  height: 100%;
-  font-size: 10px; }
-
-.pdfjs input,
-.pdfjs button,
-.pdfjs select {
-  font: message-box;
-  outline: none; }
-
-.pdfjs .hidden {
-  display: none !important; }
-
-.pdfjs [hidden] {
-  display: none !important; }
-
-.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen {
-  top: 0;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -webkit-user-select: none; }
-
-.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen {
-  top: 0;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -moz-user-select: none; }
-
-.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen {
-  top: 0 !important;
-  border-top: 2px solid transparent;
-  width: 100%;
-  height: 100%;
-  overflow: hidden !important;
-  cursor: none;
-  -ms-user-select: none; }
-
-.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop {
-  background-color: #000; }
-
-.pdfjs #viewerContainer.pdfPresentationMode:fullscreen {
-  top: 0;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none; }
-
-.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) {
-  display: none; }
-
-.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) {
-  display: none; }
-
-.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) {
-  display: none !important; }
-
-.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) {
-  display: none; }
-
-.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div {
-  cursor: none; }
-
-.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div {
-  cursor: none; }
-
-.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div {
-  cursor: none; }
-
-.pdfjs .pdfPresentationMode:fullscreen .textLayer > div {
-  cursor: none; }
-
-.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *,
-.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
-  cursor: default; }
-
-.pdfjs .outerCenter {
-  pointer-events: none;
-  position: relative; }
-
-html[dir='ltr'] .pdfjs .outerCenter {
-  float: right;
-  right: 50%; }
-
-html[dir='rtl'] .pdfjs .outerCenter {
-  float: left;
-  left: 50%; }
-
-.pdfjs .innerCenter {
-  pointer-events: auto;
-  position: relative; }
-
-html[dir='ltr'] .pdfjs .innerCenter {
-  float: right;
-  right: -50%; }
-
-html[dir='rtl'] .pdfjs .innerCenter {
-  float: left;
-  left: -50%; }
-
-.pdfjs #outerContainer {
-  width: 100%;
-  height: 100%;
-  position: relative;
-  background-color: #404040;
-  background-image: url("../images/pdf.js-viewer/texture.png"); }
-
-.pdfjs #sidebarContainer {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  width: 200px;
-  visibility: hidden;
-  -webkit-transition-duration: 200ms;
-  -webkit-transition-timing-function: ease;
-  transition-duration: 200ms;
-  transition-timing-function: ease; }
-
-html[dir='ltr'] .pdfjs #sidebarContainer {
-  -webkit-transition-property: left;
-  transition-property: left;
-  left: -200px; }
-
-html[dir='rtl'] .pdfjs #sidebarContainer {
-  -webkit-transition-property: right;
-  transition-property: right;
-  right: -200px; }
-
-.pdfjs #outerContainer.sidebarMoving > #sidebarContainer,
-.pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
-  visibility: visible; }
-
-html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
-  left: 0; }
-
-html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer {
-  right: 0; }
-
-.pdfjs #mainContainer {
-  position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  min-width: 320px;
-  -webkit-transition-duration: 200ms;
-  -webkit-transition-timing-function: ease;
-  transition-duration: 200ms;
-  transition-timing-function: ease; }
-
-html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-  -webkit-transition-property: left;
-  transition-property: left;
-  left: 200px; }
-
-html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-  -webkit-transition-property: right;
-  transition-property: right;
-  right: 200px; }
-
-.pdfjs #sidebarContent {
-  top: 32px;
-  bottom: 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  position: absolute;
-  width: 200px;
-  background-color: rgba(0, 0, 0, 0.1); }
-
-html[dir='ltr'] .pdfjs #sidebarContent {
-  left: 0;
-  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25); }
-
-html[dir='rtl'] .pdfjs #sidebarContent {
-  right: 0;
-  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25); }
-
-.pdfjs #viewerContainer {
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  position: absolute;
-  top: 32px;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  outline: none; }
-
-html[dir='ltr'] .pdfjs #viewerContainer {
-  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05); }
-
-html[dir='rtl'] .pdfjs #viewerContainer {
-  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05); }
-
-.pdfjs .toolbar {
-  position: relative;
-  left: 0;
-  right: 0;
-  cursor: default; }
-
-.pdfjs #toolbarContainer {
-  width: 100%; }
-
-.pdfjs #toolbarSidebar {
-  width: 200px;
-  height: 32px;
-  background-color: #424242;
-  background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95)); }
-
-html[dir='ltr'] .pdfjs #toolbarSidebar {
-  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); }
-
-html[dir='rtl'] .pdfjs #toolbarSidebar {
-  box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); }
-
-.pdfjs #toolbarContainer,
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  position: relative;
-  height: 32px;
-  background-color: #474747;
-  background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); }
-
-html[dir='ltr'] .pdfjs #toolbarContainer,
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); }
-
-html[dir='rtl'] .pdfjs #toolbarContainer,
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); }
-
-.pdfjs #toolbarViewer {
-  height: 32px; }
-
-.pdfjs #loadingBar {
-  position: relative;
-  width: 100%;
-  height: 4px;
-  background-color: #333;
-  border-bottom: 1px solid #333; }
-
-.pdfjs #loadingBar .progress {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 0;
-  height: 100%;
-  background-color: #ddd;
-  overflow: hidden;
-  -webkit-transition: width 200ms;
-  transition: width 200ms; }
-
-@-webkit-keyframes progressIndeterminate {
-  0% {
-    left: 0; }
-  50% {
-    left: 100%; }
-  100% {
-    left: 100%; } }
-
-@keyframes progressIndeterminate {
-  0% {
-    left: 0; }
-  50% {
-    left: 100%; }
-  100% {
-    left: 100%; } }
-
-.pdfjs #loadingBar .progress.indeterminate {
-  background-color: #999;
-  -webkit-transition: none;
-  transition: none; }
-
-.pdfjs #loadingBar .indeterminate .glimmer {
-  position: absolute;
-  top: 0;
-  left: 0;
-  height: 100%;
-  width: 50px;
-  background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
-  background-size: 100% 100%;
-  background-repeat: no-repeat;
-  -webkit-animation: progressIndeterminate 2s linear infinite;
-  animation: progressIndeterminate 2s linear infinite; }
-
-.pdfjs .findbar,
-.pdfjs .secondaryToolbar {
-  top: 32px;
-  position: absolute;
-  z-index: 10000;
-  height: 32px;
-  min-width: 16px;
-  padding: 0 6px;
-  margin: 4px 2px;
-  color: #d9d9d9;
-  font-size: 12px;
-  line-height: 14px;
-  text-align: left;
-  cursor: default; }
-
-html[dir='ltr'] .pdfjs .findbar {
-  left: 68px; }
-
-html[dir='rtl'] .pdfjs .findbar {
-  right: 68px; }
-
-.pdfjs .findbar label {
-  -webkit-user-select: none;
-  -moz-user-select: none; }
-
-.pdfjs #findInput[data-status="pending"] {
-  background-image: url("../images/pdf.js-viewer/loading-small.png");
-  background-repeat: no-repeat;
-  background-position: right; }
-
-html[dir='rtl'] .pdfjs #findInput[data-status="pending"] {
-  background-position: left; }
-
-.pdfjs .secondaryToolbar {
-  padding: 6px;
-  height: auto;
-  z-index: 30000; }
-
-html[dir='ltr'] .pdfjs .secondaryToolbar {
-  right: 4px; }
-
-html[dir='rtl'] .pdfjs .secondaryToolbar {
-  left: 4px; }
-
-.pdfjs #secondaryToolbarButtonContainer {
-  max-width: 200px;
-  max-height: 400px;
-  overflow-y: auto;
-  -webkit-overflow-scrolling: touch;
-  margin-bottom: -4px; }
-
-.pdfjs .doorHanger,
-.pdfjs .doorHangerRight {
-  border: 1px solid rgba(0, 0, 0, 0.5);
-  border-radius: 2px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); }
-
-.pdfjs .doorHanger:after,
-.pdfjs .doorHanger:before,
-.pdfjs .doorHangerRight:after,
-.pdfjs .doorHangerRight:before {
-  bottom: 100%;
-  border: solid transparent;
-  content: " ";
-  height: 0;
-  width: 0;
-  position: absolute;
-  pointer-events: none; }
-
-.pdfjs .doorHanger:after,
-.pdfjs .doorHangerRight:after {
-  border-bottom-color: rgba(82, 82, 82, 0.99);
-  border-width: 8px; }
-
-.pdfjs .doorHanger:before,
-.pdfjs .doorHangerRight:before {
-  border-bottom-color: rgba(0, 0, 0, 0.5);
-  border-width: 9px; }
-
-html[dir='ltr'] .pdfjs .doorHanger:after,
-html[dir='rtl'] .pdfjs .doorHangerRight:after {
-  left: 13px;
-  margin-left: -8px; }
-
-html[dir='ltr'] .pdfjs .doorHanger:before,
-html[dir='rtl'] .pdfjs .doorHangerRight:before {
-  left: 13px;
-  margin-left: -9px; }
-
-html[dir='rtl'] .pdfjs .doorHanger:after,
-html[dir='ltr'] .pdfjs .doorHangerRight:after {
-  right: 13px;
-  margin-right: -8px; }
-
-html[dir='rtl'] .pdfjs .doorHanger:before,
-html[dir='ltr'] .pdfjs .doorHangerRight:before {
-  right: 13px;
-  margin-right: -9px; }
-
-.pdfjs #findMsg {
-  font-style: italic;
-  color: #A6B7D0; }
-
-.pdfjs #findInput.notFound {
-  background-color: #f66; }
-
-html[dir='ltr'] .pdfjs #toolbarViewerLeft {
-  margin-left: -1px; }
-
-html[dir='rtl'] .pdfjs #toolbarViewerRight {
-  margin-right: -1px; }
-
-html[dir='ltr'] .pdfjs #toolbarViewerLeft,
-html[dir='rtl'] .pdfjs #toolbarViewerRight {
-  position: absolute;
-  top: 0;
-  left: 0; }
-
-html[dir='ltr'] .pdfjs #toolbarViewerRight,
-html[dir='rtl'] .pdfjs #toolbarViewerLeft {
-  position: absolute;
-  top: 0;
-  right: 0; }
-
-html[dir='ltr'] .pdfjs #toolbarViewerLeft > *,
-html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *,
-html[dir='ltr'] .pdfjs #toolbarViewerRight > *,
-html[dir='ltr'] .pdfjs .findbar > * {
-  position: relative;
-  float: left; }
-
-html[dir='rtl'] .pdfjs #toolbarViewerLeft > *,
-html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *,
-html[dir='rtl'] .pdfjs #toolbarViewerRight > *,
-html[dir='rtl'] .pdfjs .findbar > * {
-  position: relative;
-  float: right; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButton {
-  margin: 3px 2px 4px 0;
-  display: inline-block; }
-
-html[dir='rtl'] .pdfjs .splitToolbarButton {
-  margin: 3px 0 4px 2px;
-  display: inline-block; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton {
-  border-radius: 0;
-  float: left; }
-
-html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton {
-  border-radius: 0;
-  float: right; }
-
-.pdfjs .toolbarButton,
-.pdfjs .secondaryToolbarButton,
-.pdfjs .overlayButton {
-  border: 0 none;
-  background: none;
-  width: 32px;
-  height: 25px; }
-
-.pdfjs .toolbarButton > span {
-  display: inline-block;
-  width: 0;
-  height: 0;
-  overflow: hidden; }
-
-.pdfjs .toolbarButton[disabled],
-.pdfjs .secondaryToolbarButton[disabled],
-.pdfjs .overlayButton[disabled] {
-  opacity: 0.5; }
-
-.pdfjs .toolbarButton.group {
-  margin-right: 0; }
-
-.pdfjs .splitToolbarButton.toggled .toolbarButton {
-  margin: 0; }
-
-.pdfjs .splitToolbarButton:hover > .toolbarButton,
-.pdfjs .splitToolbarButton:focus > .toolbarButton,
-.pdfjs .splitToolbarButton.toggled > .toolbarButton,
-.pdfjs .toolbarButton.textButton {
-  background-color: rgba(0, 0, 0, 0.12);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  border: 1px solid rgba(0, 0, 0, 0.35);
-  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease; }
-
-.pdfjs .splitToolbarButton > .toolbarButton:hover,
-.pdfjs .splitToolbarButton > .toolbarButton:focus,
-.pdfjs .dropdownToolbarButton:hover,
-.pdfjs .overlayButton:hover,
-.pdfjs .toolbarButton.textButton:hover,
-.pdfjs .toolbarButton.textButton:focus {
-  background-color: rgba(0, 0, 0, 0.2);
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05);
-  z-index: 199; }
-
-.pdfjs .splitToolbarButton > .toolbarButton {
-  position: relative; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child {
-  position: relative;
-  margin: 0;
-  margin-right: -1px;
-  border-top-left-radius: 2px;
-  border-bottom-left-radius: 2px;
-  border-right-color: transparent; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child {
-  position: relative;
-  margin: 0;
-  margin-left: -1px;
-  border-top-right-radius: 2px;
-  border-bottom-right-radius: 2px;
-  border-left-color: transparent; }
-
-.pdfjs .splitToolbarButtonSeparator {
-  padding: 8px 0;
-  width: 1px;
-  background-color: rgba(0, 0, 0, 0.5);
-  z-index: 99;
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
-  display: inline-block;
-  margin: 5px 0; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator {
-  float: left; }
-
-html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator {
-  float: right; }
-
-.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator,
-.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator {
-  padding: 12px 0;
-  margin: 1px 0;
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03);
-  -webkit-transition-property: padding;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: ease;
-  transition-property: padding;
-  transition-duration: 10ms;
-  transition-timing-function: ease; }
-
-.pdfjs .toolbarButton,
-.pdfjs .dropdownToolbarButton,
-.pdfjs .secondaryToolbarButton,
-.pdfjs .overlayButton {
-  min-width: 16px;
-  padding: 2px 6px 0;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  color: rgba(255, 255, 255, 0.8);
-  font-size: 12px;
-  line-height: 14px;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-  cursor: default;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease; }
-
-html[dir='ltr'] .pdfjs .toolbarButton,
-html[dir='ltr'] .pdfjs .overlayButton,
-html[dir='ltr'] .pdfjs .dropdownToolbarButton {
-  margin: 3px 2px 4px 0; }
-
-html[dir='rtl'] .pdfjs .toolbarButton,
-html[dir='rtl'] .pdfjs .overlayButton,
-html[dir='rtl'] .pdfjs .dropdownToolbarButton {
-  margin: 3px 0 4px 2px; }
-
-.pdfjs .toolbarButton:hover,
-.pdfjs .toolbarButton:focus,
-.pdfjs .dropdownToolbarButton,
-.pdfjs .overlayButton,
-.pdfjs .secondaryToolbarButton:hover,
-.pdfjs .secondaryToolbarButton:focus {
-  background-color: rgba(0, 0, 0, 0.12);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  border: 1px solid rgba(0, 0, 0, 0.35);
-  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); }
-
-.pdfjs .toolbarButton:hover:active,
-.pdfjs .overlayButton:hover:active,
-.pdfjs .dropdownToolbarButton:hover:active,
-.pdfjs .secondaryToolbarButton:hover:active {
-  background-color: rgba(0, 0, 0, 0.2);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45);
-  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 10ms;
-  transition-timing-function: linear; }
-
-.pdfjs .toolbarButton.toggled,
-.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled,
-.pdfjs .secondaryToolbarButton.toggled {
-  background-color: rgba(0, 0, 0, 0.3);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5);
-  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 10ms;
-  transition-timing-function: linear; }
-
-.pdfjs .toolbarButton.toggled:hover:active,
-.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active,
-.pdfjs .secondaryToolbarButton.toggled:hover:active {
-  background-color: rgba(0, 0, 0, 0.4);
-  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55);
-  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05); }
-
-.pdfjs .dropdownToolbarButton {
-  width: 120px;
-  max-width: 120px;
-  padding: 0;
-  overflow: hidden;
-  background: url("../images/pdf.js-viewer/toolbarButton-menuArrows.png") no-repeat; }
-
-html[dir='ltr'] .pdfjs .dropdownToolbarButton {
-  background-position: 95%; }
-
-html[dir='rtl'] .pdfjs .dropdownToolbarButton {
-  background-position: 5%; }
-
-.pdfjs .dropdownToolbarButton > select {
-  min-width: 140px;
-  font-size: 12px;
-  color: #f2f2f2;
-  margin: 0;
-  padding: 3px 2px 2px;
-  border: none;
-  background: rgba(0, 0, 0, 0); }
-
-.pdfjs .dropdownToolbarButton > select > option {
-  background: #3d3d3d; }
-
-.pdfjs #customScaleOption {
-  display: none; }
-
-.pdfjs #pageWidthOption {
-  border-bottom: 1px rgba(255, 255, 255, 0.5) solid; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButton:first-child,
-html[dir='ltr'] .pdfjs .toolbarButton:first-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton:last-child,
-html[dir='rtl'] .pdfjs .toolbarButton:last-child {
-  margin-left: 4px; }
-
-html[dir='ltr'] .pdfjs .splitToolbarButton:last-child,
-html[dir='ltr'] .pdfjs .toolbarButton:last-child,
-html[dir='rtl'] .pdfjs .splitToolbarButton:first-child,
-html[dir='rtl'] .pdfjs .toolbarButton:first-child {
-  margin-right: 4px; }
-
-.pdfjs .toolbarButtonSpacer {
-  width: 30px;
-  display: inline-block;
-  height: 1px; }
-
-.pdfjs .toolbarButtonFlexibleSpacer {
-  -webkit-box-flex: 1;
-  -moz-box-flex: 1;
-  min-width: 30px; }
-
-html[dir='ltr'] .pdfjs #findPrevious {
-  margin-left: 3px; }
-
-html[dir='ltr'] .pdfjs #findNext {
-  margin-right: 3px; }
-
-html[dir='rtl'] .pdfjs #findPrevious {
-  margin-right: 3px; }
-
-html[dir='rtl'] .pdfjs #findNext {
-  margin-left: 3px; }
-
-.pdfjs .toolbarButton::before,
-.pdfjs .secondaryToolbarButton::before {
-  position: absolute;
-  display: inline-block;
-  top: 4px;
-  left: 7px; }
-
-html[dir="ltr"] .pdfjs .secondaryToolbarButton::before {
-  left: 4px; }
-
-html[dir="rtl"] .pdfjs .secondaryToolbarButton::before {
-  right: 4px; }
-
-html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle.png"); }
-
-html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png"); }
-
-html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png"); }
-
-html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png"); }
-
-html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
-  content: url("../images/pdf.js-viewer/findbarButton-previous.png"); }
-
-html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
-  content: url("../images/pdf.js-viewer/findbarButton-previous-rtl.png"); }
-
-html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
-  content: url("../images/pdf.js-viewer/findbarButton-next.png"); }
-
-html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
-  content: url("../images/pdf.js-viewer/findbarButton-next-rtl.png"); }
-
-html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-pageUp.png"); }
-
-html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png"); }
-
-html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-pageDown.png"); }
-
-html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png"); }
-
-.pdfjs .toolbarButton.zoomOut::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-zoomOut.png"); }
-
-.pdfjs .toolbarButton.zoomIn::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-zoomIn.png"); }
-
-.pdfjs .toolbarButton.presentationMode::before,
-.pdfjs .secondaryToolbarButton.presentationMode::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-presentationMode.png"); }
-
-.pdfjs .toolbarButton.print::before,
-.pdfjs .secondaryToolbarButton.print::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-print.png"); }
-
-.pdfjs .toolbarButton.openFile::before,
-.pdfjs .secondaryToolbarButton.openFile::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-openFile.png"); }
-
-.pdfjs .toolbarButton.download::before,
-.pdfjs .secondaryToolbarButton.download::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-download.png"); }
-
-.pdfjs .toolbarButton.bookmark,
-.pdfjs .secondaryToolbarButton.bookmark {
-  -webkit-box-sizing: border-box;
-  -moz-box-sizing: border-box;
-  box-sizing: border-box;
-  outline: none;
-  padding-top: 4px;
-  text-decoration: none; }
-
-.pdfjs .secondaryToolbarButton.bookmark {
-  padding-top: 5px; }
-
-.pdfjs .bookmark[href='#'] {
-  opacity: .5;
-  pointer-events: none; }
-
-.pdfjs .toolbarButton.bookmark::before,
-.pdfjs .secondaryToolbarButton.bookmark::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-bookmark.png"); }
-
-.pdfjs #viewThumbnail.toolbarButton::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail.png"); }
-
-html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-viewOutline.png"); }
-
-html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png"); }
-
-.pdfjs #viewAttachments.toolbarButton::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments.png"); }
-
-.pdfjs #viewFind.toolbarButton::before {
-  content: url("../images/pdf.js-viewer/toolbarButton-search.png"); }
-
-.pdfjs .secondaryToolbarButton {
-  position: relative;
-  margin: 0 0 4px;
-  padding: 3px 0 1px;
-  height: auto;
-  min-height: 25px;
-  width: auto;
-  min-width: 100%;
-  white-space: normal; }
-
-html[dir="ltr"] .pdfjs .secondaryToolbarButton {
-  padding-left: 24px;
-  text-align: left; }
-
-html[dir="rtl"] .pdfjs .secondaryToolbarButton {
-  padding-right: 24px;
-  text-align: right; }
-
-html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark {
-  padding-left: 27px; }
-
-html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark {
-  padding-right: 27px; }
-
-html[dir="ltr"] .pdfjs .secondaryToolbarButton > span {
-  padding-right: 4px; }
-
-html[dir="rtl"] .pdfjs .secondaryToolbarButton > span {
-  padding-left: 4px; }
-
-.pdfjs .secondaryToolbarButton.firstPage::before {
-  content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png"); }
-
-.pdfjs .secondaryToolbarButton.lastPage::before {
-  content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png"); }
-
-.pdfjs .secondaryToolbarButton.rotateCcw::before {
-  content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png"); }
-
-.pdfjs .secondaryToolbarButton.rotateCw::before {
-  content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png"); }
-
-.pdfjs .secondaryToolbarButton.handTool::before {
-  content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool.png"); }
-
-.pdfjs .secondaryToolbarButton.documentProperties::before {
-  content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png"); }
-
-.pdfjs .verticalToolbarSeparator {
-  display: block;
-  padding: 8px 0;
-  margin: 8px 4px;
-  width: 1px;
-  background-color: rgba(0, 0, 0, 0.5);
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }
-
-html[dir='ltr'] .pdfjs .verticalToolbarSeparator {
-  margin-left: 2px; }
-
-html[dir='rtl'] .pdfjs .verticalToolbarSeparator {
-  margin-right: 2px; }
-
-.pdfjs .horizontalToolbarSeparator {
-  display: block;
-  margin: 0 0 4px;
-  height: 1px;
-  width: 100%;
-  background-color: rgba(0, 0, 0, 0.5);
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }
-
-.pdfjs .toolbarField {
-  padding: 3px 6px;
-  margin: 4px 0;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  background-color: rgba(255, 255, 255, 0.09);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  border: 1px solid rgba(0, 0, 0, 0.35);
-  border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42);
-  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05);
-  color: #f2f2f2;
-  font-size: 12px;
-  line-height: 14px;
-  outline-style: none;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease; }
-
-.pdfjs .toolbarField[type=checkbox] {
-  display: inline-block;
-  margin: 8px 0; }
-
-.pdfjs .toolbarField.pageNumber {
-  -moz-appearance: textfield;
-  min-width: 16px;
-  text-align: right;
-  width: 40px; }
-
-.pdfjs .toolbarField.pageNumber.visiblePageIsLoading {
-  background-image: url("../images/pdf.js-viewer/loading-small.png");
-  background-repeat: no-repeat;
-  background-position: 1px; }
-
-.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button,
-.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button {
-  -webkit-appearance: none;
-  margin: 0; }
-
-.pdfjs .toolbarField:hover {
-  background-color: rgba(255, 255, 255, 0.11);
-  border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45); }
-
-.pdfjs .toolbarField:focus {
-  background-color: rgba(255, 255, 255, 0.15);
-  border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9); }
-
-.pdfjs .toolbarLabel {
-  min-width: 16px;
-  padding: 3px 6px 3px 2px;
-  margin: 4px 2px 4px 0;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  color: #d9d9d9;
-  font-size: 12px;
-  line-height: 14px;
-  text-align: left;
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  cursor: default; }
-
-.pdfjs #thumbnailView {
-  position: absolute;
-  width: 120px;
-  top: 0;
-  bottom: 0;
-  padding: 10px 40px 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch; }
-
-.pdfjs .thumbnail {
-  float: left;
-  margin-bottom: 5px; }
-
-.pdfjs #thumbnailView > a:last-of-type > .thumbnail {
-  margin-bottom: 10px; }
-
-.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) {
-  margin-bottom: 9px; }
-
-.pdfjs .thumbnail:not([data-loaded]) {
-  border: 1px dashed rgba(255, 255, 255, 0.5);
-  margin: -1px -1px 4px; }
-
-.pdfjs .thumbnailImage {
-  border: 1px solid transparent;
-  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
-  opacity: .8;
-  z-index: 99;
-  background-color: #fff;
-  background-clip: content-box; }
-
-.pdfjs .thumbnailSelectionRing {
-  border-radius: 2px;
-  padding: 7px; }
-
-.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
-.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
-  opacity: 0.9; }
-
-.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing,
-.pdfjs .thumbnail:hover > .thumbnailSelectionRing {
-  background-color: rgba(255, 255, 255, 0.15);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: rgba(255, 255, 255, 0.9); }
-
-.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
-  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
-  opacity: 1; }
-
-.pdfjs .thumbnail.selected > .thumbnailSelectionRing {
-  background-color: rgba(255, 255, 255, 0.3);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: #ffffff; }
-
-.pdfjs #outlineView,
-.pdfjs #attachmentsView {
-  position: absolute;
-  width: 192px;
-  top: 0;
-  bottom: 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  -webkit-user-select: none;
-  -moz-user-select: none; }
-
-.pdfjs #outlineView {
-  padding: 4px 4px 0; }
-
-.pdfjs #attachmentsView {
-  padding: 3px 4px 0; }
-
-html[dir='ltr'] .pdfjs .outlineItem > .outlineItems {
-  margin-left: 20px; }
-
-html[dir='rtl'] .pdfjs .outlineItem > .outlineItems {
-  margin-right: 20px; }
-
-.pdfjs .outlineItem > a,
-.pdfjs .attachmentsItem > button {
-  text-decoration: none;
-  display: inline-block;
-  min-width: 95%;
-  height: auto;
-  margin-bottom: 1px;
-  border-radius: 2px;
-  color: rgba(255, 255, 255, 0.8);
-  font-size: 13px;
-  line-height: 15px;
-  -moz-user-select: none;
-  white-space: normal; }
-
-.pdfjs .attachmentsItem > button {
-  border: 0 none;
-  background: none;
-  cursor: pointer;
-  width: 100%; }
-
-html[dir='ltr'] .pdfjs .outlineItem > a {
-  padding: 2px 0 5px 10px; }
-
-html[dir='ltr'] .pdfjs .attachmentsItem > button {
-  padding: 2px 0 3px 7px;
-  text-align: left; }
-
-html[dir='rtl'] .pdfjs .outlineItem > a {
-  padding: 2px 10px 5px 0; }
-
-html[dir='rtl'] .pdfjs .attachmentsItem > button {
-  padding: 2px 7px 3px 0;
-  text-align: right; }
-
-.pdfjs .outlineItem > a:hover,
-.pdfjs .attachmentsItem > button:hover {
-  background-color: rgba(255, 255, 255, 0.02);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: rgba(255, 255, 255, 0.9); }
-
-.pdfjs .outlineItem.selected {
-  background-color: rgba(255, 255, 255, 0.08);
-  background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
-  background-clip: padding-box;
-  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2);
-  color: #ffffff; }
-
-.pdfjs .noResults {
-  font-size: 12px;
-  color: rgba(255, 255, 255, 0.8);
-  font-style: italic;
-  cursor: default; }
-
-.pdfjs ::selection {
-  background: rgba(0, 0, 255, 0.3); }
-
-.pdfjs ::-moz-selection {
-  background: rgba(0, 0, 255, 0.3); }
-
-.pdfjs #errorWrapper {
-  background: none repeat scroll 0 0 #F55;
-  color: #fff;
-  left: 0;
-  position: absolute;
-  right: 0;
-  z-index: 1000;
-  padding: 3px;
-  font-size: 0.8em; }
-
-.pdfjs .loadingInProgress #errorWrapper {
-  top: 37px; }
-
-.pdfjs #errorMessageLeft {
-  float: left; }
-
-.pdfjs #errorMessageRight {
-  float: right; }
-
-.pdfjs #errorMoreInfo {
-  background-color: #FFF;
-  color: #000;
-  padding: 3px;
-  margin: 3px;
-  width: 98%; }
-
-.pdfjs .overlayButton {
-  width: auto;
-  margin: 3px 4px 2px !important;
-  padding: 2px 6px 3px; }
-
-.pdfjs #overlayContainer {
-  display: table;
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, 0.2);
-  z-index: 40000; }
-
-.pdfjs #overlayContainer > * {
-  overflow: auto;
-  -webkit-overflow-scrolling: touch; }
-
-.pdfjs #overlayContainer > .container {
-  display: table-cell;
-  vertical-align: middle;
-  text-align: center; }
-
-.pdfjs #overlayContainer > .container > .dialog {
-  display: inline-block;
-  padding: 15px;
-  border-spacing: 4px;
-  color: #d9d9d9;
-  font-size: 12px;
-  line-height: 14px;
-  background-color: #474747;
-  background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95));
-  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1);
-  border: 1px solid rgba(0, 0, 0, 0.5);
-  border-radius: 4px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); }
-
-.pdfjs .dialog > .row {
-  display: table-row; }
-
-.pdfjs .dialog > .row > * {
-  display: table-cell; }
-
-.pdfjs .dialog .toolbarField {
-  margin: 5px 0; }
-
-.pdfjs .dialog .separator {
-  display: block;
-  margin: 4px 0;
-  height: 1px;
-  width: 100%;
-  background-color: rgba(0, 0, 0, 0.5);
-  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }
-
-.pdfjs .dialog .buttonRow {
-  text-align: center;
-  vertical-align: middle; }
-
-.pdfjs #passwordOverlay > .dialog {
-  text-align: center; }
-
-.pdfjs #passwordOverlay .toolbarField {
-  width: 200px; }
-
-.pdfjs #documentPropertiesOverlay > .dialog {
-  text-align: left; }
-
-.pdfjs #documentPropertiesOverlay .row > * {
-  min-width: 100px; }
-
-html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * {
-  text-align: left; }
-
-html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * {
-  text-align: right; }
-
-.pdfjs #documentPropertiesOverlay .row > span {
-  width: 125px;
-  word-wrap: break-word; }
-
-.pdfjs #documentPropertiesOverlay .row > p {
-  max-width: 225px;
-  word-wrap: break-word; }
-
-.pdfjs #documentPropertiesOverlay .buttonRow {
-  margin-top: 10px; }
-
-.pdfjs .clearBoth {
-  clear: both; }
-
-.pdfjs .fileInput {
-  background: #fff;
-  color: #000;
-  margin-top: 5px;
-  visibility: hidden;
-  position: fixed;
-  right: 0;
-  top: 0; }
-
-.pdfjs #PDFBug {
-  background: none repeat scroll 0 0 #fff;
-  border: 1px solid #666;
-  position: fixed;
-  top: 32px;
-  right: 0;
-  bottom: 0;
-  font-size: 10px;
-  padding: 0;
-  width: 300px; }
-
-.pdfjs #PDFBug .controls {
-  background: #EEE;
-  border-bottom: 1px solid #666;
-  padding: 3px; }
-
-.pdfjs #PDFBug .panels {
-  bottom: 0;
-  left: 0;
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
-  position: absolute;
-  right: 0;
-  top: 27px; }
-
-.pdfjs #PDFBug button.active {
-  font-weight: 700; }
-
-.pdfjs .debuggerShowText {
-  background: none repeat scroll 0 0 #ff0;
-  color: blue; }
-
-.pdfjs .debuggerHideText:hover {
-  background: none repeat scroll 0 0 #ff0; }
-
-.pdfjs #PDFBug .stats {
-  font-family: courier;
-  font-size: 10px;
-  white-space: pre; }
-
-.pdfjs #PDFBug .stats .title {
-  font-weight: 700; }
-
-.pdfjs #PDFBug table {
-  font-size: 10px; }
-
-.pdfjs #viewer.textLayer-visible .textLayer > div,
-.pdfjs #viewer.textLayer-hover .textLayer > div:hover {
-  background-color: #fff;
-  color: #000; }
-
-.pdfjs #viewer.textLayer-shadow .textLayer > div {
-  background-color: rgba(255, 255, 255, 0.6);
-  color: #000; }
-
-.pdfjs .grab-to-pan-grab {
-  cursor: url("../images/pdf.js-viewer/grab.cur"), move !important;
-  cursor: -webkit-grab !important;
-  cursor: -moz-grab !important;
-  cursor: grab !important; }
-
-.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) {
-  cursor: inherit !important; }
-
-.pdfjs .grab-to-pan-grab:active,
-.pdfjs .grab-to-pan-grabbing {
-  cursor: url("../images/pdf.js-viewer/grabbing.cur"), move !important;
-  cursor: -webkit-grabbing !important;
-  cursor: -moz-grabbing !important;
-  cursor: grabbing !important;
-  position: fixed;
-  background: transparent;
-  display: block;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  z-index: 50000; }
-
-@page {
-  margin: 0; }
-
-.pdfjs #printContainer {
-  display: none; }
-
-@media screen and (min-resolution: 2dppx) {
-  .pdfjs .toolbarButton::before {
-    -webkit-transform: scale(0.5);
-    transform: scale(0.5);
-    top: -5px; }
-  .pdfjs .secondaryToolbarButton::before {
-    -webkit-transform: scale(0.5);
-    transform: scale(0.5);
-    top: -4px; }
-  html[dir='ltr'] .pdfjs .toolbarButton::before,
-  html[dir='rtl'] .pdfjs .toolbarButton::before {
-    left: -1px; }
-  html[dir="ltr"] .pdfjs .secondaryToolbarButton::before {
-    left: -2px; }
-  html[dir="rtl"] .pdfjs .secondaryToolbarButton::before {
-    left: 186px; }
-  .pdfjs .toolbarField.pageNumber.visiblePageIsLoading,
-  .pdfjs #findInput[data-status="pending"] {
-    background-image: url("../images/pdf.js-viewer/loading-small@2x.png");
-    background-size: 16px 17px; }
-  .pdfjs .dropdownToolbarButton {
-    background: url("../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png") no-repeat;
-    background-size: 7px 16px; }
-  html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png"); }
-  html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png"); }
-  html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png"); }
-  html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png"); }
-  html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before {
-    content: url("../images/pdf.js-viewer/findbarButton-previous@2x.png"); }
-  html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before {
-    content: url("../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png"); }
-  html[dir='ltr'] .pdfjs .toolbarButton.findNext::before {
-    content: url("../images/pdf.js-viewer/findbarButton-next@2x.png"); }
-  html[dir='rtl'] .pdfjs .toolbarButton.findNext::before {
-    content: url("../images/pdf.js-viewer/findbarButton-next-rtl@2x.png"); }
-  html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-pageUp@2x.png"); }
-  html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png"); }
-  html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-pageDown@2x.png"); }
-  html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png"); }
-  .pdfjs .toolbarButton.zoomIn::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png"); }
-  .pdfjs .toolbarButton.zoomOut::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png"); }
-  .pdfjs .toolbarButton.presentationMode::before,
-  .pdfjs .secondaryToolbarButton.presentationMode::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png"); }
-  .pdfjs .toolbarButton.print::before,
-  .pdfjs .secondaryToolbarButton.print::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-print@2x.png"); }
-  .pdfjs .toolbarButton.openFile::before,
-  .pdfjs .secondaryToolbarButton.openFile::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-openFile@2x.png"); }
-  .pdfjs .toolbarButton.download::before,
-  .pdfjs .secondaryToolbarButton.download::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-download@2x.png"); }
-  .pdfjs .toolbarButton.bookmark::before,
-  .pdfjs .secondaryToolbarButton.bookmark::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-bookmark@2x.png"); }
-  .pdfjs #viewThumbnail.toolbarButton::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png"); }
-  html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png"); }
-  html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png"); }
-  .pdfjs #viewAttachments.toolbarButton::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png"); }
-  .pdfjs #viewFind.toolbarButton::before {
-    content: url("../images/pdf.js-viewer/toolbarButton-search@2x.png"); }
-  .pdfjs .secondaryToolbarButton.firstPage::before {
-    content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png"); }
-  .pdfjs .secondaryToolbarButton.lastPage::before {
-    content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png"); }
-  .pdfjs .secondaryToolbarButton.rotateCcw::before {
-    content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png"); }
-  .pdfjs .secondaryToolbarButton.rotateCw::before {
-    content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png"); }
-  .pdfjs .secondaryToolbarButton.handTool::before {
-    content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png"); }
-  .pdfjs .secondaryToolbarButton.documentProperties::before {
-    content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png"); } }
-
-@media print {
-  body {
-    background: transparent none; }
-  .pdfjs #sidebarContainer,
-  .pdfjs #secondaryToolbar,
-  .pdfjs .toolbar,
-  .pdfjs #loadingBox,
-  .pdfjs #errorWrapper,
-  .pdfjs .textLayer {
-    display: none; }
-  .pdfjs #viewerContainer {
-    overflow: visible; }
-  .pdfjs #mainContainer,
-  .pdfjs #viewerContainer,
-  .pdfjs .page,
-  .pdfjs .page canvas {
-    position: static;
-    padding: 0;
-    margin: 0; }
-  .pdfjs .page {
-    float: left;
-    display: none;
-    border: none;
-    box-shadow: none;
-    background-clip: content-box;
-    background-color: #fff; }
-  .pdfjs .page[data-loaded] {
-    display: block; }
-  .pdfjs .fileInput {
-    display: none; }
-  body[data-mozPrintCallback] .pdfjs #outerContainer {
-    display: none; }
-  body[data-mozPrintCallback] .pdfjs #printContainer {
-    display: block; }
-  .pdfjs #printContainer > div {
-    position: relative;
-    top: 0;
-    left: 0;
-    overflow: hidden; }
-  .pdfjs #printContainer canvas {
-    display: block; } }
-
-.pdfjs .visibleLargeView,
-.pdfjs .visibleMediumView,
-.pdfjs .visibleSmallView {
-  display: none; }
-
-@media all and (max-width: 960px) {
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
-    float: left;
-    left: 205px; }
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter {
-    float: right;
-    right: 205px; } }
-
-@media all and (max-width: 900px) {
-  .pdfjs .sidebarOpen .hiddenLargeView {
-    display: none; }
-  .pdfjs .sidebarOpen .visibleLargeView {
-    display: inherit; } }
-
-@media all and (max-width: 860px) {
-  .pdfjs .sidebarOpen .hiddenMediumView {
-    display: none; }
-  .pdfjs .sidebarOpen .visibleMediumView {
-    display: inherit; } }
-
-@media all and (max-width: 770px) {
-  .pdfjs #sidebarContainer {
-    top: 32px;
-    z-index: 100; }
-  .pdfjs .loadingInProgress #sidebarContainer {
-    top: 37px; }
-  .pdfjs #sidebarContent {
-    top: 32px;
-    background-color: rgba(0, 0, 0, 0.7); }
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-    left: 0; }
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer {
-    right: 0; }
-  html[dir='ltr'] .pdfjs .outerCenter {
-    float: left;
-    left: 205px; }
-  html[dir='rtl'] .pdfjs .outerCenter {
-    float: right;
-    right: 205px; }
-  .pdfjs #outerContainer .hiddenLargeView,
-  .pdfjs #outerContainer .hiddenMediumView {
-    display: inherit; }
-  .pdfjs #outerContainer .visibleLargeView,
-  .pdfjs #outerContainer .visibleMediumView {
-    display: none; } }
-
-@media all and (max-width: 700px) {
-  .pdfjs #outerContainer .hiddenLargeView {
-    display: none; }
-  .pdfjs #outerContainer .visibleLargeView {
-    display: inherit; } }
-
-@media all and (max-width: 660px) {
-  .pdfjs #outerContainer .hiddenMediumView {
-    display: none; }
-  .pdfjs #outerContainer .visibleMediumView {
-    display: inherit; } }
-
-@media all and (max-width: 600px) {
-  .pdfjs .hiddenSmallView {
-    display: none; }
-  .pdfjs .visibleSmallView {
-    display: inherit; }
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
-  html[dir='ltr'] .pdfjs .outerCenter {
-    left: 156px; }
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter,
-  html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter,
-  html[dir='rtl'] .pdfjs .outerCenter {
-    right: 156px; }
-  .pdfjs .toolbarButtonSpacer {
-    width: 0; } }
-
-@media all and (max-width: 510px) {
-  .pdfjs #scaleSelectContainer,
-  .pdfjs #pageNumberLabel {
-    display: none; } }
-
-/* should be hidden differently */
-#fileInput.fileInput {
-  display: none; }
-
-.wvSplitpane {
-  height: 100%;
-  padding: 7px 2px 2px 2px;
-  position: relative; }
-
-.wvSplitpane__cell {
-  display: inline-block;
-  float: left;
-  height: 100%;
-  width: 100%;
-  position: relative; }
-
-.wvSplitpane__cellBorder, .wvSplitpane__cellBorder--selected, .wvSplitpane__cellBorder--blue, .wvSplitpane__cellBorder--red, .wvSplitpane__cellBorder--green, .wvSplitpane__cellBorder--yellow, .wvSplitpane__cellBorder--violet {
-  display: inline-block;
-  float: left;
-  height: calc(100% - 2px);
-  width: calc(100% - 2px);
-  border: 2px dashed transparent;
-  padding: 2px;
-  margin: 1px; }
-
-.wvSplitpane__cellBorder--selected {
-  border: 2px solid rgba(51, 152, 219, 0.7); }
-
-.wvSplitpane__cellBorder--blue {
-  border-color: rgba(51, 152, 219, 0.7); }
-
-.wvSplitpane__cellBorder--red {
-  border-color: rgba(206, 0, 0, 0.7); }
-
-.wvSplitpane__cellBorder--green {
-  border-color: rgba(0, 160, 27, 0.7); }
-
-.wvSplitpane__cellBorder--yellow {
-  border-color: rgba(220, 200, 0, 0.9); }
-
-.wvSplitpane__cellBorder--violet {
-  border-color: rgba(255, 31, 255, 0.7); }
-
-wv-pane-policy {
-  display: block;
-  width: 100%;
-  height: 100%; }
-  wv-pane-policy > div[ng-transclude] {
-    display: block;
-    width: 100%;
-    height: 100%; }
-
-.wv-timeline {
-  position: relative;
-  height: 2em; }
-  .wv-timeline.reduced {
-    height: 5px; }
-    .wv-timeline.reduced .wv-timeline-loading-bar-wrapper {
-      width: 100%;
-      height: 100%; }
-
-.wv-timeline-controls-wrapper {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  width: 16em;
-  height: 100%;
-  color: white; }
-
-.wv-timeline-loading-bar-wrapper {
-  position: absolute;
-  right: 0;
-  bottom: 0;
-  width: calc(100% - 16em);
-  height: calc(100% + 2px); }
-  .wv-timeline-loading-bar-wrapper svg {
-    position: absolute;
-    left: 0;
-    top: 0; }
-
-/* wv-timeline-controls directive */
-.wv-timeline-controls {
-  padding: 0.5em 0.5em 0.5em 0.5em;
-  line-height: 1em;
-  background-color: rgba(0, 0, 0, 0.66);
-  text-align: center;
-  transition: color 500ms, background-color 500ms; }
-
-.wv-timeline-controls:hover {
-  background-color: rgba(0, 0, 0, 0.9); }
-
-.wv-timeline-controls-vertical-sizing {
-  display: inline-block;
-  line-height: 1em;
-  font-size: 1em; }
-
-.wv-timeline-controls-vflip:before, .wv-timeline-controls-vflip:after {
-  transform: scaleX(-1);
-  display: inline-block; }
-
-.wv-timeline-controls-button {
-  display: inline-block;
-  height: 1em;
-  width: 1em;
-  line-height: 1em;
-  font-size: 1em;
-  margin: 0;
-  user-select: none;
-  cursor: pointer; }
-
-.wv-timeline-controls-input {
-  height: 1em;
-  width: 3em;
-  padding: 0;
-  padding-bottom: 1px;
-  box-sizing: content-box;
-  border: none;
-  border-bottom: 1px solid rgba(255, 202, 128, 0.24);
-  background-color: transparent;
-  text-align: right; }
-
-.wv-timeline-controls-play-button-wrapper {
-  float: right; }
-
-/* wv-play-button directive */
-.wv-play-button {
-  display: inline-block;
-  position: relative;
-  line-height: 1em;
-  height: 3em;
-  width: 6em;
-  padding-bottom: 1em;
-  padding-left: 0.25em;
-  padding-right: 0.25em; }
-
-.wv-play-button:hover .wv-play-button-config-position-handler {
-  visibility: visible; }
-
-.wv-play-button-config-position-handler {
-  visibility: hidden;
-  position: absolute;
-  bottom: 3em;
-  left: 1em;
-  right: 0.5em; }
-
-.wv-play-button-config {
-  position: absolute;
-  bottom: 0;
-  left: -6em;
-  width: 12em;
-  padding: 1em;
-  background-color: rgba(0, 0, 0, 0.5); }
-
-/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */
-.wv-play-button-config-framerate-wrapper {
-  display: inline-block;
-  margin: 0.25em 0 0.5em 0; }
-
-input[type="range"].wv-play-button-config-framerate {
-  /*removes default webkit styles*/
-  -webkit-appearance: none;
-  /*fix for FF unable to apply focus style bug */
-  border: 1px solid white;
-  /*required for proper track sizing in FF*/
-  width: 10em; }
-
-input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track {
-  width: 10em;
-  height: 5px;
-  background: #ddd;
-  border: none;
-  border-radius: 3px; }
-
-input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb {
-  -webkit-appearance: none;
-  border: none;
-  height: 16px;
-  width: 16px;
-  border-radius: 50%;
-  background: goldenrod;
-  margin-top: -4px; }
-
-input[type="range"].wv-play-button-config-framerate:focus {
-  outline: none; }
-
-input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track {
-  background: #ccc; }
-
-input[type="range"].wv-play-button-config-framerate::-moz-range-track {
-  width: 10em;
-  height: 5px;
-  background: #ddd;
-  border: none;
-  border-radius: 3px; }
-
-input[type="range"].wv-play-button-config-framerate::-moz-range-thumb {
-  border: none;
-  height: 16px;
-  width: 16px;
-  border-radius: 50%;
-  background: goldenrod; }
-
-/*hide the outline behind the border*/
-input[type="range"].wv-play-button-config-framerate:-moz-focusring {
-  outline: 1px solid white;
-  outline-offset: -1px; }
-
-input[type="range"].wv-play-button-config-framerate::-ms-track {
-  width: 10em;
-  height: 5px;
-  /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
-  background: transparent;
-  /*leave room for the larger thumb to overflow with a transparent border */
-  border-color: transparent;
-  border-width: 6px 0;
-  /*remove default tick marks*/
-  color: transparent; }
-
-input[type="range"].wv-play-button-config-framerate::-ms-fill-lower {
-  background: #777;
-  border-radius: 10px; }
-
-input[type="range"].wv-play-button-config-framerate::-ms-fill-upper {
-  background: #ddd;
-  border-radius: 10px; }
-
-input[type="range"].wv-play-button-config-framerate::-ms-thumb {
-  border: none;
-  height: 16px;
-  width: 16px;
-  border-radius: 50%;
-  background: goldenrod; }
-
-input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower {
-  background: #888; }
-
-input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper {
-  background: #ccc; }
-
-.wv-loadingbar-image-bar {
-  cursor: pointer; }
-
-.wv-loadingbar-not-loaded {
-  fill: rgba(255, 255, 255, 0.1); }
-
-.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality {
-  transition: none; }
-
-.wv-loadingbar-not-loaded:hover {
-  fill: rgba(255, 255, 255, 0.2); }
-
-.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality {
-  fill: rgba(0, 255, 0, 0.7); }
-
-.wv-loadingbar-LOSSLESS-quality:hover,
-.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active,
-.wv-loadingbar-PIXELDATA-quality:hover,
-.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active {
-  fill: lime; }
-
-.wv-loadingbar-LOW-quality {
-  fill: rgba(255, 0, 0, 0.7); }
-
-.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active {
-  fill: red; }
-
-.wv-loadingbar-MEDIUM-quality {
-  fill: rgba(255, 95, 0, 0.7); }
-
-.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active {
-  fill: #ff5f00; }
-
-.disclaimer {
-  color: #E63F24;
-  background-color: #303030;
-  padding: 5px;
-  text-align: center;
-  font-weight: bold; }
-
-.tbGroup {
-  position: relative; }
-
-.tbGroup__buttons--base, .tbGroup__buttons--bottom, .tbGroup__buttons--left {
-  z-index: 5;
-  background-color: black;
-  position: absolute; }
-
-.tbGroup__buttons--bottom {
-  right: 0;
-  display: block; }
-
-.tbGroup__buttons--left {
-  right: 100%;
-  top: 0;
-  display: block; }
-
-.tbGroup__icon {
-  display: block;
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  width: 0;
-  height: 0;
-  border-style: solid;
-  border-width: 10px 0 0 10px;
-  border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); }
-  .tbGroup__icon.active {
-    border-color: transparent transparent transparent #3498db; }
-
-wv-viewport {
-  display: inline-block;
-  width: 100%;
-  height: 100%; }
-  wv-viewport > div {
-    position: relative;
-    width: 100%;
-    height: 100%; }
-  wv-viewport > div > .wv-cornerstone-enabled-image {
-    width: 100%;
-    height: 100%;
-    text-align: center; }
-
-.wv-draggable-clone {
-  width: 150px;
-  height: 150px;
-  background-color: rgba(255, 255, 255, 0.25); }
-
-@media print {
-  .wvPrintExclude {
-    display: none; }
-  .wvPrintFullPage {
-    width: 100% !important;
-    height: 100% !important;
-    position: absolute !important;
-    top: 0 !important;
-    left: 0 !important;
-    display: block !important; }
-  .wvLayout__main {
-    top: 0 !important;
-    right: 0 !important;
-    left: 0 !important;
-    bottom: 0 !important; }
-  .wvPrintViewer {
-    width: 100%;
-    height: 100%;
-    display: flex;
-    align-items: center;
-    justify-content: center; }
-  .wvPrintViewer canvas {
-    max-width: 100% !important;
-    max-height: 100% !important;
-    margin: auto; }
-  .wv-overlay-topleft, .wv-overlay-topleft *, .wv-overlay-topright, .wv-overlay-topright *, .wv-overlay-bottomright, .wv-overlay-bottomright *, .wv-overlay-bottomleft, .wv-overlay-bottomleft * {
-    background-color: black !important;
-    -webkit-print-color-adjust: exact !important;
-    color-adjust: exact !important;
-    color: orange !important; }
-  .tooltip {
-    display: none !important; }
-  body {
-    margin: 0;
-    padding: 0;
-    position: relative;
-    width: 8.5in;
-    height: 11in; }
-    body, body * {
-      background-color: black !important;
-      -webkit-print-color-adjust: exact !important; } }
-
-.closePrintButton {
-  display: none; }
-
-body.print .wvPrintExclude {
-  display: none; }
-
-body.print .wvPrintFullPage {
-  width: 100% !important;
-  height: 100% !important;
-  position: absolute !important;
-  top: 0 !important;
-  left: 0 !important;
-  display: block !important; }
-
-body.print .wvLayout__main {
-  top: 0 !important;
-  right: 0 !important;
-  left: 0 !important;
-  bottom: 0 !important; }
-
-body.print .wvPrintViewer {
-  width: 100%;
-  height: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center; }
-
-body.print .wvPrintViewer canvas {
-  max-width: 100% !important;
-  max-height: 100% !important;
-  margin: auto; }
-
-body.print .wv-overlay-topleft, body.print .wv-overlay-topleft *, body.print .wv-overlay-topright, body.print .wv-overlay-topright *, body.print .wv-overlay-bottomright, body.print .wv-overlay-bottomright *, body.print .wv-overlay-bottomleft, body.print .wv-overlay-bottomleft * {
-  background-color: black !important;
-  -webkit-print-color-adjust: exact !important;
-  color-adjust: exact !important;
-  color: orange !important; }
-
-body.print .tooltip {
-  display: none !important; }
-
-body.print body {
-  margin: 0;
-  padding: 0;
-  position: relative;
-  width: 8.5in;
-  height: 11in; }
-  body.print body, body.print body * {
-    background-color: black !important;
-    -webkit-print-color-adjust: exact !important; }
-
-@media screen {
-  body.print .closePrintButton {
-    display: block;
-    position: fixed;
-    top: 0;
-    right: 0;
-    padding: 10px;
-    font-size: 24px;
-    background-color: black;
-    color: white;
-    border: none; } }
--- a/StoneWebViewer/WebApplication/app.js	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,614 +0,0 @@
-/**
- * Stone of Orthanc
- * 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/>.
- **/
-
-
-var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ];
-var SERIES_INSTANCE_UID = '0020,000e';
-var STUDY_INSTANCE_UID = '0020,000d';
-var STUDY_DESCRIPTION = '0008,1030';
-var STUDY_DATE = '0008,0020';
-
-
-function getParameterFromUrl(key) {
-  var url = window.location.search.substring(1);
-  var args = url.split('&');
-  for (var i = 0; i < args.length; i++) {
-    var arg = args[i].split('=');
-    if (arg[0] == key) {
-      return arg[1];
-    }
-  }
-}
-
-
-Vue.component('viewport', {
-  props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex',
-           'quality', 'framesCount', 'currentFrame', 'showInfo' ],
-  template: '#viewport-template',
-  data: function () {
-    return {
-      stone: stone,  // To access global object "stone" from "index.html"
-      status: 'waiting'
-    }
-  },
-  watch: { 
-    series: function(newVal, oldVal) {
-      this.status = 'loading';
-
-      var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID];
-      var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID];
-      stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid);
-      
-      if ((newVal.type == stone.ThumbnailType.IMAGE ||
-           newVal.type == stone.ThumbnailType.NO_PREVIEW) &&
-          newVal.complete) {
-        this.status = 'ready';
-
-        var that = this;
-        Vue.nextTick(function() {
-          stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid);
-        });
-      }
-      else if (newVal.type == stone.ThumbnailType.PDF ||
-               newVal.type == stone.ThumbnailType.VIDEO) {
-        // TODO
-      }
-    }
-  },
-  methods: {
-    SeriesDragAccept: function(event) {
-      event.preventDefault();
-    },
-    SeriesDragDrop: function(event) {
-      event.preventDefault();
-      this.$emit('updated-series', event.dataTransfer.getData('seriesIndex'));
-    },
-    MakeActive: function() {
-      this.$emit('selected-viewport');
-    },
-    DecrementFrame: function() {
-      stone.DecrementFrame(this.canvasId);
-    },
-    IncrementFrame: function() {
-      stone.IncrementFrame(this.canvasId);
-    }
-  }
-})
-
-
-var app = new Vue({
-  el: '#wv',
-  data: function() {
-    return {
-      stone: stone,  // To access global object "stone" from "index.html"
-      ready: false,
-      leftMode: 'grid',   // Can be 'small', 'grid' or 'full'
-      leftVisible: true,
-      showWarning: false,
-      viewportLayoutButtonsVisible: false,
-      activeViewport: 0,
-      showInfo: true,
-      showReferenceLines: true,
-      
-      viewport1Width: '100%',
-      viewport1Height: '100%',
-      viewport1Left: '0%',
-      viewport1Top: '0%',
-      viewport1Visible: true,
-      viewport1Series: {},
-      viewport1Quality: '',
-      viewport1FramesCount: 0,
-      viewport1CurrentFrame: 0,
-      
-      viewport2Width: '100%',
-      viewport2Height: '100%',
-      viewport2Left: '0%',
-      viewport2Top: '0%',
-      viewport2Visible: false,
-      viewport2Series: {},
-      viewport2Quality: '',
-      viewport2FramesCount: 0,
-      viewport2CurrentFrame: 0,
-
-      viewport3Width: '100%',
-      viewport3Height: '100%',
-      viewport3Left: '0%',
-      viewport3Top: '0%',
-      viewport3Visible: false,
-      viewport3Series: {},
-      viewport3Quality: '',
-      viewport3FramesCount: 0,
-      viewport3CurrentFrame: 0,
-
-      viewport4Width: '100%',
-      viewport4Height: '100%',
-      viewport4Left: '0%',
-      viewport4Top: '0%',
-      viewport4Visible: false,
-      viewport4Series: {},
-      viewport4Quality: '',
-      viewport4FramesCount: 0,
-      viewport4CurrentFrame: 0,
-
-      series: [],
-      studies: [],
-      seriesIndex: {}  // Maps "SeriesInstanceUID" to "index in this.series"
-    }
-  },
-  computed: {
-    getSelectedStudies() {
-      var s = '';
-      for (var i = 0; i < this.studies.length; i++) {
-        if (this.studies[i].selected) {
-          if (s.length > 0)
-            s += ', ';
-          s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' +
-                this.studies[i].tags[STUDY_DATE] + ']');
-        }
-      }
-      if (s.length == 0) 
-        return '...';
-      else
-        return s;
-    }
-  },
-  watch: { 
-    leftVisible: function(newVal, oldVal) {
-      this.FitContent();
-    },
-    showReferenceLines: function(newVal, oldVal) {
-      stone.ShowReferenceLines(newVal ? 1 : 0);
-    }
-  },
-  methods: {
-    FitContent() {
-      // This function can be used even if WebAssembly is not initialized yet
-      if (typeof stone._AllViewportsUpdateSize !== 'undefined') {
-        this.$nextTick(function () {
-          stone.AllViewportsUpdateSize(true /* fit content */);
-        });
-      }
-    },
-    
-    GetActiveSeries() {
-      var s = [];
-
-      if ('tags' in this.viewport1Series)
-        s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]);
-
-      if ('tags' in this.viewport2Series)
-        s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]);
-
-      if ('tags' in this.viewport3Series)
-        s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]);
-
-      if ('tags' in this.viewport4Series)
-        s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]);
-
-      return s;
-    },
-
-    GetActiveCanvas() {
-      if (this.activeViewport == 1) {
-        return 'canvas1';
-      }
-      else if (this.activeViewport == 2) {
-        return 'canvas2';
-      }
-      else if (this.activeViewport == 3) {
-        return 'canvas3';
-      }
-      else if (this.activeViewport == 4) {
-        return 'canvas4';
-      }
-      else {
-        return 'canvas1';
-      }
-    },
-
-    SetResources: function(sourceStudies, sourceSeries) {     
-      var indexStudies = {};
-
-      var studies = [];
-      var posColor = 0;
-
-      for (var i = 0; i < sourceStudies.length; i++) {
-        var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID];
-        if (studyInstanceUid !== undefined) {
-          if (studyInstanceUid in indexStudies) {
-            console.error('Twice the same study: ' + studyInstanceUid);
-          } else {
-            indexStudies[studyInstanceUid] = studies.length;
-            
-            studies.push({
-              'studyInstanceUid' : studyInstanceUid,
-              'series' : [ ],
-              'color' : COLORS[posColor],
-              'selected' : true,
-              'tags' : sourceStudies[i]
-            });
-
-            posColor = (posColor + 1) % COLORS.length;
-          }
-        }
-      }
-
-      var series = [];
-      var seriesIndex = {};
-
-      for (var i = 0; i < sourceSeries.length; i++) {
-        var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID];
-        var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID];
-        if (studyInstanceUid !== undefined &&
-            seriesInstanceUid !== undefined) {
-          if (studyInstanceUid in indexStudies) {
-            seriesIndex[seriesInstanceUid] = series.length;
-            var study = studies[indexStudies[studyInstanceUid]];
-            study.series.push(i);
-            series.push({
-              //'length' : 4,
-              'complete' : false,
-              'type' : stone.ThumbnailType.LOADING,
-              'color': study.color,
-              'tags': sourceSeries[i]
-            });
-          }
-        }
-      }
-      
-      this.studies = studies;
-      this.series = series;
-      this.seriesIndex = seriesIndex;
-      this.ready = true;
-    },
-    
-    SeriesDragStart: function(event, seriesIndex) {
-      event.dataTransfer.setData('seriesIndex', seriesIndex);
-    },
-    
-    SetViewportSeries: function(viewportIndex, seriesIndex) {
-      var series = this.series[seriesIndex];
-      
-      if (viewportIndex == 1) {
-        this.viewport1Series = series;
-      }
-      else if (viewportIndex == 2) {
-        this.viewport2Series = series;
-      }
-      else if (viewportIndex == 3) {
-        this.viewport3Series = series;
-      }
-      else if (viewportIndex == 4) {
-        this.viewport4Series = series;
-      }
-    },
-    
-    ClickSeries: function(seriesIndex) {
-      this.SetViewportSeries(this.activeViewport, seriesIndex);
-    },
-    
-    HideViewport: function(index) {
-      if (index == 1) {
-        this.viewport1Visible = false;
-      }
-      else if (index == 2) {
-        this.viewport2Visible = false;
-      }
-      else if (index == 3) {
-        this.viewport3Visible = false;
-      }
-      else if (index == 4) {
-        this.viewport4Visible = false;
-      }
-    },
-    
-    ShowViewport: function(index, left, top, width, height) {
-      if (index == 1) {
-        this.viewport1Visible = true;
-        this.viewport1Left = left;
-        this.viewport1Top = top;
-        this.viewport1Width = width;
-        this.viewport1Height = height;
-      }
-      else if (index == 2) {
-        this.viewport2Visible = true;
-        this.viewport2Left = left;
-        this.viewport2Top = top;
-        this.viewport2Width = width;
-        this.viewport2Height = height;
-      }
-      else if (index == 3) {
-        this.viewport3Visible = true;
-        this.viewport3Left = left;
-        this.viewport3Top = top;
-        this.viewport3Width = width;
-        this.viewport3Height = height;
-      }
-      else if (index == 4) {
-        this.viewport4Visible = true;
-        this.viewport4Left = left;
-        this.viewport4Top = top;
-        this.viewport4Width = width;
-        this.viewport4Height = height;
-      }
-    },
-    
-    SetViewportLayout: function(layout) {
-      this.viewportLayoutButtonsVisible = false;
-      if (layout == '1x1') {
-        this.ShowViewport(1, '0%', '0%', '100%', '100%');
-        this.HideViewport(2);
-        this.HideViewport(3);
-        this.HideViewport(4);
-      }
-      else if (layout == '2x2') {
-        this.ShowViewport(1, '0%', '0%', '50%', '50%');
-        this.ShowViewport(2, '50%', '0%', '50%', '50%');
-        this.ShowViewport(3, '0%', '50%', '50%', '50%');
-        this.ShowViewport(4, '50%', '50%', '50%', '50%');
-      }
-      else if (layout == '2x1') {
-        this.ShowViewport(1, '0%', '0%', '50%', '100%');
-        this.ShowViewport(2, '50%', '0%', '50%', '100%');
-        this.HideViewport(3);
-        this.HideViewport(4);
-      }
-      else if (layout == '1x2') {
-        this.ShowViewport(1, '0%', '0%', '100%', '50%');
-        this.ShowViewport(2, '0%', '50%', '100%', '50%');
-        this.HideViewport(3);
-        this.HideViewport(4);
-      }
-
-      this.FitContent();
-    },
-
-    UpdateSeriesThumbnail: function(seriesInstanceUid) {
-      if (seriesInstanceUid in this.seriesIndex) {
-        var index = this.seriesIndex[seriesInstanceUid];
-        var series = this.series[index];
-
-        var type = stone.LoadSeriesThumbnail(seriesInstanceUid);
-        series.type = type;
-
-        if (type == stone.ThumbnailType.IMAGE) {
-          series.thumbnail = stone.GetStringBuffer();
-        }
-
-        // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
-        this.$set(this.series, index, series);
-      }
-    },
-
-    UpdateIsSeriesComplete: function(seriesInstanceUid) {
-      if (seriesInstanceUid in this.seriesIndex) {
-        var index = this.seriesIndex[seriesInstanceUid];
-        var series = this.series[index];
-
-        series.complete = stone.IsSeriesComplete(seriesInstanceUid);
-
-        // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
-        this.$set(this.series, index, series);
-
-        if ('tags' in this.viewport1Series &&
-            this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
-          this.$set(this.viewport1Series, series);
-        }
-
-        if ('tags' in this.viewport2Series &&
-            this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
-          this.$set(this.viewport2Series, series);
-        }
-
-        if ('tags' in this.viewport3Series &&
-            this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
-          this.$set(this.viewport3Series, series);
-        }
-
-        if ('tags' in this.viewport4Series &&
-            this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
-          this.$set(this.viewport4Series, series);
-        }
-      }
-    },
-
-    SetWindowing(center, width) {
-      var canvas = this.GetActiveCanvas();
-      if (canvas != '') {
-        stone.SetWindowing(canvas, center, width);
-      }
-    },
-
-    SetDefaultWindowing() {
-      var canvas = this.GetActiveCanvas();
-      if (canvas != '') {
-        stone.SetDefaultWindowing(canvas);
-      }
-    },
-
-    InvertContrast() {
-      var canvas = this.GetActiveCanvas();
-      if (canvas != '') {
-        stone.InvertContrast(canvas);
-      }
-    }
-  },
-  
-  mounted: function() {
-    this.SetViewportLayout('1x1');
-  }
-});
-
-
-
-window.addEventListener('StoneInitialized', function() {
-  stone.Setup(Module);
-  stone.SetOrthancRoot('..', true);
-  console.warn('Native Stone properly intialized');
-
-  var study = getParameterFromUrl('study');
-  var series = getParameterFromUrl('series');
-
-  if (study === undefined) {
-    alert('No study was provided in the URL!');
-  } else {
-    if (series === undefined) {
-      console.warn('Loading study: ' + study);
-      stone.FetchStudy(study);
-    } else {
-      console.warn('Loading series: ' + series + ' from study ' + study);
-      stone.FetchSeries(study, series);
-      app.leftMode = 'full';
-    }
-  }
-});
-
-
-window.addEventListener('ResourcesLoaded', function() {
-  console.log('resources loaded');
-
-  var studies = [];
-  for (var i = 0; i < stone.GetStudiesCount(); i++) {
-    stone.LoadStudyTags(i);
-    studies.push(JSON.parse(stone.GetStringBuffer()));
-  }
-
-  var series = [];
-  for (var i = 0; i < stone.GetSeriesCount(); i++) {
-    stone.LoadSeriesTags(i);
-    series.push(JSON.parse(stone.GetStringBuffer()));
-  }
-
-  app.SetResources(studies, series);
-
-  for (var i = 0; i < app.series.length; i++) {
-    var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID];
-    app.UpdateSeriesThumbnail(seriesInstanceUid);
-    app.UpdateIsSeriesComplete(seriesInstanceUid);
-  }
-});
-
-
-window.addEventListener('ThumbnailLoaded', function(args) {
-  //var studyInstanceUid = args.detail.studyInstanceUid;
-  var seriesInstanceUid = args.detail.seriesInstanceUid;
-  app.UpdateSeriesThumbnail(seriesInstanceUid);
-});
-
-
-window.addEventListener('MetadataLoaded', function(args) {
-  //var studyInstanceUid = args.detail.studyInstanceUid;
-  var seriesInstanceUid = args.detail.seriesInstanceUid;
-  app.UpdateIsSeriesComplete(seriesInstanceUid);
-});
-
-
-window.addEventListener('FrameUpdated', function(args) {
-  var canvasId = args.detail.canvasId;
-  var framesCount = args.detail.framesCount;
-  var currentFrame = (args.detail.currentFrame + 1);
-  var quality = args.detail.quality;
-  
-  if (canvasId == 'canvas1') {
-    app.viewport1CurrentFrame = currentFrame;
-    app.viewport1FramesCount = framesCount;
-    app.viewport1Quality = quality;
-  }
-  else if (canvasId == 'canvas2') {
-    app.viewport2CurrentFrame = currentFrame;
-    app.viewport2FramesCount = framesCount;
-    app.viewport2Quality = quality;
-  }
-  else if (canvasId == 'canvas3') {
-    app.viewport3CurrentFrame = currentFrame;
-    app.viewport3FramesCount = framesCount;
-    app.viewport3Quality = quality;
-  }
-  else if (canvasId == 'canvas4') {
-    app.viewport4CurrentFrame = currentFrame;
-    app.viewport4FramesCount = framesCount;
-    app.viewport4Quality = quality;
-  }
-});
-
-
-window.addEventListener('StoneException', function() {
-  console.error('Exception catched in Stone');
-});
-
-
-
-
-
-
-$(document).ready(function() {
-  // Enable support for tooltips in Bootstrap
-  //$('[data-toggle="tooltip"]').tooltip();
-
-  //app.showWarning = true;
-
-
-  $('#windowing-popover').popover({
-    container: 'body',
-    content: $('#windowing-content').html(),
-    template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
-    placement: 'auto',
-    html: true,
-    sanitize: false,
-    trigger: 'focus'   // Close on click
-  });
-  
-  
-  var wasmSource = 'StoneWebViewer.js';
-  
-  // Option 1: Loading script using plain HTML
-  
-  /*
-    var script = document.createElement('script');
-    script.src = wasmSource;
-    script.type = 'text/javascript';
-    document.body.appendChild(script);
-  */
-
-  // Option 2: Loading script using AJAX (gives the opportunity to
-  // report explicit errors)
-
-  axios.get(wasmSource)
-    .then(function (response) {
-      var script = document.createElement('script');
-      script.innerHTML = response.data;
-      script.type = 'text/javascript';
-      document.body.appendChild(script);
-    })
-    .catch(function (error) {
-      alert('Cannot load the WebAssembly framework');
-    });
-});
-
-
-// "Prevent Bootstrap dropdown from closing on clicks" for the list of
-// studies: https://stackoverflow.com/questions/26639346
-$('.dropdown-menu').click(function(e) {
-  e.stopPropagation();
-});
-
-
-// Disable the selection of text using the mouse
-document.onselectstart = new Function ('return false');
Binary file StoneWebViewer/WebApplication/img/grid1x1.png has changed
Binary file StoneWebViewer/WebApplication/img/grid1x2.png has changed
Binary file StoneWebViewer/WebApplication/img/grid2x1.png has changed
Binary file StoneWebViewer/WebApplication/img/grid2x2.png has changed
Binary file StoneWebViewer/WebApplication/img/loading.gif has changed
--- a/StoneWebViewer/WebApplication/index.html	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,488 +0,0 @@
-<!doctype html>
-<html class="wv-html">
-  <head>
-    <title>Stone Web Viewer</title>
-    <meta charset="utf-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
-    <meta name="apple-mobile-web-app-capable" content="yes" />
-    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
-    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css">
-    <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
-    <link rel="stylesheet" href="app.css">
-
-    <!-- https://stackoverflow.com/a/16863182/881731 -->
-    <style>
-      .tooltip {
-      position: fixed;
-      }
-    </style>
-
-    <!-- Fix if Bootstrap CSS is not used -->
-    <!--style>
-        *,
-        *::before,
-        *::after {
-        box-sizing: border-box;
-        }
-        </style-->
-  </head>
-  <body class="wv-body">
-    <div id="wv">
-      <div class="wvLoadingScreen" v-show="!ready">
-        <span class="wvLoadingSpinner">
-          <div class="bounce1"></div>
-          <div class="bounce2"></div>
-          <div class="bounce3"></div>
-        </span>
-      </div>
-
-      <div class="fluid-height fluid-width" v-show="ready">
-
-        <div class="wvWarning wvPrintExclude" v-show="showWarning">
-          <div class="wvWarning-content clearfix">
-            <span class="wvWarning-text">
-              <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2>
-              <p class="mn mb10" style="color:#000">
-                You browser is not supported. You might expect
-                inconsistent behaviours and must not use the viewer to
-                produce a diagnostic.
-              </p>
-            </span> 
-          </div>
-          <div class="text-right mb10 mr10">
-            <button class="btn btn-primary" @click="showWarning=false">OK</button>
-          </div>
-        </div>
-
-
-        <div class="wvLayoutLeft wvLayoutLeft--closed" v-show="!leftVisible">
-          <div class="wvLayoutLeft__actions--outside" style="z-index:10">
-            <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center"
-                    @click="leftVisible = true">
-              <i class="fa fa-angle-double-right"></i>
-            </button>
-          </div>
-        </div>
-
-
-        <div class="wvLayoutLeft" v-show="leftVisible"
-             v-bind:class="{ 'wvLayoutLeft--small': leftMode == 'small' }" 
-             >
-          <div class="wvLayoutLeft__actions" style="z-index:10">
-            <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center"
-                    @click="leftVisible = false">
-              <i class="fa fa-angle-double-left"></i>
-            </button>
-          </div>
-          <div class="wvLayoutLeft__content">
-            <div class="wvLayoutLeft__contentTop">
-              <div class="float__left dropdown" style="max-width: calc(100% - 4.5rem); height:4.5rem !important" v-show="leftMode != 'small'">
-                <button type="button" class="wvButton--border" data-toggle="dropdown">
-                  {{ getSelectedStudies }}
-                  <span class="caret"></span>
-                </button>
-                <ul class="dropdown-menu checkbox-menu allow-focus">
-                  <li v-for="study in studies"
-                      v-bind:class="{ active: study.selected }" 
-                      @click="study.selected = !study.selected">
-                    <a>
-                      {{ study.tags['0008,1030'] }}
-                      <span v-if="study.selected">&nbsp;<i class="fa fa-check"></i></span>
-                    </a> 
-                  </li>
-                </ul>
-              </div>
-
-              <div class="float__right wvButton" v-if="leftMode == 'grid'" @click="leftMode = 'full'">
-                <i class="fa fa-th-list"></i>
-              </div>
-              <div class="float__right wvButton" v-if="leftMode == 'full'" @click="leftMode = 'small'">
-                <i class="fa fa-ellipsis-v"></i>
-              </div>
-              <div class="float__right wvButton" v-if="leftMode == 'small'" @click="leftMode = 'grid'">
-                <i class="fa fa-th"></i>
-              </div>
-
-              <p class="clear disclaimer mbn">For patients, teachers and researchers.</p>
-            </div>        
-            <div class="wvLayoutLeft__contentMiddle">
-
-              <div v-for="study in studies">
-                <div v-if="study.selected">
-                  <div v-bind:class="'wvStudyIsland--' + study.color">
-                    <div v-bind:class="'wvStudyIsland__header--' + study.color">
-                      <!-- Actions -->
-                      <div class="wvStudyIsland__actions"
-                           v-bind:class="{ 'wvStudyIsland__actions--oneCol': leftMode == 'small' }">
-                        <a class="wvButton">
-                          <!-- download --> 
-                          <i class="fa fa-download"></i>
-                        </a>
-                      </div>
-                      
-                      <!-- Title -->
-                      {{ study.tags['0008,1030'] }}
-                      <br/>
-                      <small>{{ study.tags['0008,0020'] }}</small>
-                    </div>
-
-                    <div class="wvStudyIsland__main">
-                      <ul class="wvSerieslist">
-                        <li class="wvSerieslist__seriesItem"
-                            v-bind:class="{ highlighted : GetActiveSeries().includes(series[seriesIndex].tags['0020,000e']), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }"
-                            v-on:dragstart="SeriesDragStart($event, seriesIndex)"
-                            v-on:click="ClickSeries(seriesIndex)"
-                            v-for="seriesIndex in study.series">
-                          <div class="wvSerieslist__picture" style="z-index:0"
-                               draggable="true"
-                               v-if="series[seriesIndex].type != stone.ThumbnailType.UNKNOWN"
-                               >
-                            <div v-if="series[seriesIndex].type == stone.ThumbnailType.LOADING">
-                              <img src="img/loading.gif"
-                                   style="vertical-align:baseline"
-                                   width="65px" height="65px"
-                                   />
-                            </div>
-
-                            <i v-if="series[seriesIndex].type == stone.ThumbnailType.PDF"
-                               class="wvSerieslist__placeholderIcon fa fa-file-text"></i>
-
-                            <i v-if="series[seriesIndex].type == stone.ThumbnailType.VIDEO"
-                               class="wvSerieslist__placeholderIcon fa fa-video-camera"></i>
-
-                            
-                            <div v-if="[stone.ThumbnailType.IMAGE, stone.ThumbnailType.NO_PREVIEW].includes(series[seriesIndex].type)"
-                                 class="wvSerieslist__placeholderIcon"
-                                 v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']">
-                              <i v-if="series[seriesIndex].type == stone.ThumbnailType.NO_PREVIEW"
-                                 class="fa fa-eye-slash"></i>
-
-                              <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE"
-                                   v-bind:src="series[seriesIndex].thumbnail"
-                                   style="vertical-align:baseline"
-                                   width="65px" height="65px"
-                                   v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']"
-                                   />
-                              
-                              <div v-bind:class="'wvSerieslist__badge--' + study.color"
-                                   v-if="'length' in series[seriesIndex]">{{ series[seriesIndex].length }}</div>
-                            </div>
-                          </div>
-
-                          <div v-if="leftMode == 'full'" class="wvSerieslist__information"
-                               draggable="true"
-                               v-on:dragstart="SeriesDragStart($event, seriesIndex)"
-                               v-on:click="ClickSeries(seriesIndex)">
-                            <p class="wvSerieslist__label">
-                              [{{ series[seriesIndex].tags['0008,0060'] }}]
-                              {{ series[seriesIndex].tags['0008,103e'] }}
-                            </p>
-                          </div>
-                        </li>
-                      </ul>
-                    </div>
-                  </div>
-                </div>
-              </div>
-
-            </div>        
-            <div class="wvLayoutLeft__contentBottom">
-            </div>        
-          </div>
-        </div>
-        <div class="wvLayout__main"
-             v-bind:class="{ 'wvLayout__main--smallleftpadding': leftVisible && leftMode == 'small', 'wvLayout__main--leftpadding': leftVisible && leftMode != 'small' }" 
-             >
-
-          <div class="wvToolbar wvToolbar--top">
-            <div class="ng-scope inline-object">
-              <div class="tbGroup">
-                <div class="tbGroup__toggl">
-                  <button class="wvButton"
-                          v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible }"
-                          @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible">
-                    <i class="fa fa-th"></i>
-                  </button>
-                </div>
-                
-                <div class="tbGroup__buttons--bottom" v-show="viewportLayoutButtonsVisible">
-                  <div class="inline-object">
-                    <button class="wvButton" @click="SetViewportLayout('1x1')">
-                      <img src="img/grid1x1.png" style="width:1em;height:1em" />
-                    </button>
-                  </div>
-                  <div class="inline-object">
-                    <button class="wvButton" @click="SetViewportLayout('2x1')">
-                      <img src="img/grid2x1.png" style="width:1em;height:1em" />
-                    </button>
-                  </div>
-                  <div class="inline-object">
-                    <button class="wvButton" @click="SetViewportLayout('1x2')">
-                      <img src="img/grid1x2.png" style="width:1em;height:1em" />
-                    </button>
-                  </div>
-                  <div class="inline-object">
-                    <button class="wvButton" @click="SetViewportLayout('2x2')">
-                      <img src="img/grid2x2.png" style="width:1em;height:1em" />
-                    </button>
-                  </div>
-                </div>
-              </div>
-            </div>
-
-            <!--div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center active">
-                <i class="fa fa-hand-pointer-o"></i>
-              </button>
-            </div>
-
-            <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center">
-                <i class="fa fa-search"></i>
-              </button>
-            </div>
-
-            <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center">
-                <i class="fa fa-arrows"></i>
-              </button>
-            </div-->
-
-            <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center"
-                      v-on:click="InvertContrast()">
-                <i class="fa fa-adjust"></i>
-              </button>
-            </div>
-            
-            <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center" id="windowing-popover">
-                <i class="fa fa-sun-o"></i>
-              </button>
-            </div>
-
-            <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center"
-                      v-bind:class="{ 'active' : showInfo }"
-                      v-on:click="showInfo = !showInfo">
-                <i class="fa fa-info-circle"></i>
-              </button>
-            </div>
-
-            <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center"
-                      v-bind:class="{ 'active' : showReferenceLines }"
-                      v-on:click="showReferenceLines = !showReferenceLines">
-                <i class="fa fa-bars"></i>
-              </button>
-            </div>
-          </div>
-
-          
-          <div class="wvLayout__splitpane--toolbarAtTop">
-            <div id="viewport" class="wvSplitpane">
-              <viewport v-on:updated-series="SetViewportSeries(1, $event)"
-                        v-on:selected-viewport="activeViewport=1"
-                        v-show="viewport1Visible"
-                        canvas-id="canvas1"
-                        v-bind:series="viewport1Series"
-                        v-bind:left="viewport1Left"
-                        v-bind:top="viewport1Top"
-                        v-bind:width="viewport1Width"
-                        v-bind:height="viewport1Height"
-                        v-bind:quality="viewport1Quality"
-                        v-bind:current-frame="viewport1CurrentFrame"
-                        v-bind:frames-count="viewport1FramesCount"
-                        v-bind:show-info="showInfo"
-                        v-bind:active="activeViewport==1"></viewport>
-              <viewport v-on:updated-series="SetViewportSeries(2, $event)"
-                        v-on:selected-viewport="activeViewport=2"
-                        v-show="viewport2Visible"
-                        canvas-id="canvas2"
-                        v-bind:series="viewport2Series"
-                        v-bind:left="viewport2Left"
-                        v-bind:top="viewport2Top"
-                        v-bind:width="viewport2Width"
-                        v-bind:height="viewport2Height"
-                        v-bind:quality="viewport2Quality"
-                        v-bind:current-frame="viewport2CurrentFrame"
-                        v-bind:frames-count="viewport2FramesCount"
-                        v-bind:show-info="showInfo"
-                        v-bind:active="activeViewport==2"></viewport>
-              <viewport v-on:updated-series="SetViewportSeries(3, $event)"
-                        v-on:selected-viewport="activeViewport=3"
-                        v-show="viewport3Visible"
-                        canvas-id="canvas3"
-                        v-bind:series="viewport3Series"
-                        v-bind:left="viewport3Left"
-                        v-bind:top="viewport3Top"
-                        v-bind:width="viewport3Width"
-                        v-bind:height="viewport3Height"
-                        v-bind:quality="viewport3Quality"
-                        v-bind:current-frame="viewport3CurrentFrame"
-                        v-bind:frames-count="viewport3FramesCount"
-                        v-bind:show-info="showInfo"
-                        v-bind:active="activeViewport==3"></viewport>
-              <viewport v-on:updated-series="SetViewportSeries(4, $event)"
-                        v-on:selected-viewport="activeViewport=4"
-                        v-show="viewport4Visible"
-                        canvas-id="canvas4"
-                        v-bind:series="viewport4Series"
-                        v-bind:left="viewport4Left"
-                        v-bind:top="viewport4Top"
-                        v-bind:width="viewport4Width"
-                        v-bind:height="viewport4Height"
-                        v-bind:quality="viewport4Quality"
-                        v-bind:current-frame="viewport4CurrentFrame"
-                        v-bind:frames-count="viewport4FramesCount"
-                        v-bind:show-info="showInfo"
-                        v-bind:active="activeViewport==4"></viewport>
-            </div>
-          </div>
-
-        </div>
-      </div>
-    </div>
-
-
-
-    <script type="text/x-template" id="windowing-content">
-      <p class="wvToolbar__windowingPresetConfigNotice">
-        Click on the button to toggle the windowing tool or apply a preset to the selected viewport.
-      </p>
-
-      <ul class="wvToolbar__windowingPresetList">
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetDefaultWindowing()">
-            Default
-          </a>
-        </li>
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetWindowing(-400, 1600)">
-            CT Lung <small>(L -400, W 1,600)</small>
-          </a>
-        </li>
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetWindowing(300, 1500)">
-            CT Abdomen <small>(L 300, W 1,500)</small>
-          </a>
-        </li>
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetWindowing(40, 80)">
-            CT Bone <small>(L 40, W 80)</small>
-          </a>
-        </li>
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetWindowing(40, 400)">
-            CT Brain <small>(L 40, W 400)</small>
-          </a>
-        </li>
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetWindowing(-400, 1600)">
-            CT Chest <small>(L -400, W 1,600)</small>
-          </a>
-        </li>
-        <li class="wvToolbar__windowingPresetListItem">
-          <a href="#" onclick="app.SetWindowing(300, 600)">
-            CT Angio <small>(L 300, W 600)</small>
-          </a>
-        </li>
-      </ul>
-    </script>
-    
-
-    <script type="text/x-template" id="viewport-template">
-      <div v-bind:style="{ padding:'2px', 
-                         position:'absolute', 
-                         left: left, 
-                         top: top,
-                         width: width, 
-                         height: height }">
-        <div v-bind:class="{ 'wvSplitpane__cellBorder--selected' : active, 
-                           'wvSplitpane__cellBorder' : series.color == '', 
-                           'wvSplitpane__cellBorder--blue' : series.color == 'blue', 
-                           'wvSplitpane__cellBorder--red' : series.color == 'red',
-                           'wvSplitpane__cellBorder--green' : series.color == 'green', 
-                           'wvSplitpane__cellBorder--yellow' : series.color == 'yellow', 
-                           'wvSplitpane__cellBorder--violet' : series.color == 'violet'
-                           }" 
-             v-on:dragover="SeriesDragAccept($event)"
-             v-on:drop="SeriesDragDrop($event)"
-             style="width:100%;height:100%">
-          <div class="wvSplitpane__cell"
-               v-on:click="MakeActive()">
-            <div v-show="status == 'ready'"
-                 style="position:absolute; left:0; top:0; width:100%; height:100%">
-              <!--div style="width: 100%; height: 100%; background-color: red"></div-->
-              <canvas v-bind:id="canvasId"
-                      style="position:absolute; left:0; top:0; width:100%; height:100%"
-                      oncontextmenu="return false"></canvas>
-
-              <div v-if="'tags' in series" v-show="showInfo">
-                <div class="wv-overlay">
-                  <div class="wv-overlay-topleft">
-                    {{ series.tags['0010,0010'] }}<br/>
-                    {{ series.tags['0010,0020'] }}
-                  </div>
-                  <div class="wv-overlay-topright">
-                    {{ series.tags['0008,1030'] }}<br/>
-                    {{ series.tags['0008,0020'] }}<br/>
-                    {{ series.tags['0020,0011'] }} | {{ series.tags['0008,103e'] }}
-                  </div>
-                  <div class="wv-overlay-bottomleft"
-                       v-show="framesCount != 0">
-                    <button class="btn btn-primary" @click="DecrementFrame()">
-                      <i class="fa fa-chevron-circle-left"></i>
-                    </button>
-                    &nbsp;&nbsp;{{ currentFrame }} / {{ framesCount }}&nbsp;&nbsp;
-                    <button class="btn btn-primary" @click="IncrementFrame()">
-                      <i class="fa fa-chevron-circle-right"></i>
-                    </button>
-                  </div>
-                  <div class="wv-overlay-bottomright">
-                    <div v-show="quality == stone.DisplayedFrameQuality.NONE"
-                         style="display:block;background-color:red;width:1em;height:1em" />
-                    <div v-show="quality == stone.DisplayedFrameQuality.LOW"
-                         style="display:block;background-color:orange;width:1em;height:1em" />
-                    <div v-show="quality == stone.DisplayedFrameQuality.HIGH"
-                         style="display:block;background-color:green;width:1em;height:1em" />
-                  </div>
-                </div>
-              </div>
-            </div>
-
-            <div v-if="status == 'waiting'" class="wvPaneOverlay">
-              [ drop a series here ]
-            </div>
-                
-            <!--div v-if="status == 'video'" class="wvPaneOverlay">
-              <video class="wvVideo" autoplay="" loop="" controls="" preload="auto" type="video/mp4"
-                     src="http://viewer-pro.osimis.io/instances/e465dd27-83c96343-96848735-7035a133-1facf1a0/frames/0/raw">
-              </video>
-            </div-->
-                
-            <div v-if="status == 'loading'" class="wvPaneOverlay">
-              <span class="wvLoadingSpinner">
-                <div class="bounce1"></div>
-                <div class="bounce2"></div>
-                <div class="bounce3"></div>
-              </span>
-            </div>
-          </div>
-        </div>
-      </div>
-    </script>
-
-
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
-    
-    <script src="stone.js"></script>
-    <script src="app.js"></script>
-  </body>
-</html>
--- a/StoneWebViewer/WebAssembly/CMakeLists.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-cmake_minimum_required(VERSION 2.8.3)
-
-project(OrthancStone)
-
-# Configuration of the Emscripten compiler for WebAssembly target
-# ---------------------------------------------------------------
-set(USE_WASM ON CACHE BOOL "")
-
-set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "")
-
-set(WASM_FLAGS "-s WASM=1 -s FETCH=1")
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
-endif()
-
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
-
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
-add_definitions(
-  -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
-)
-
-# Stone of Orthanc configuration
-# ---------------------------------------------------------------
-set(ALLOW_DOWNLOADS ON)
-
-include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake)
-
-SET(ENABLE_DCMTK ON)
-SET(ENABLE_DCMTK_NETWORKING OFF)
-SET(ENABLE_DCMTK_TRANSCODING OFF)
-SET(ENABLE_GOOGLE_TEST OFF)
-SET(ENABLE_LOCALE ON)  # Necessary for text rendering
-SET(ENABLE_WASM ON)
-SET(ORTHANC_SANDBOXED ON)
-
-# this will set up the build system for Stone of Orthanc and will
-# populate the ORTHANC_STONE_SOURCES CMake variable
-include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)
-
-if (CMAKE_BUILD_TYPE MATCHES Debug)
-  # specific flags go here
-elseif (CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
-  # specific flags go here
-elseif (CMAKE_BUILD_TYPE MATCHES Release)
-  # specific flags go here
-else()
-  message(FATAL_ERROR "CMAKE_BUILD_TYPE must match either Debug, RelWithDebInfo or Release" )
-endif()
-
-################################################################################
-
-project(StoneWebViewer)
-
-
-# Create the wrapper to call C++ from JavaScript
-# ---------------------------------------------------------------
-
-set(LIBCLANG "libclang-4.0.so.1" CACHE PATH "Version of clang to generate the code model")
-set(STONE_WRAPPER ${CMAKE_CURRENT_BINARY_DIR}/stone.js)
-
-add_custom_command(
-  COMMAND
-  ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py --libclang=${LIBCLANG} ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp > ${STONE_WRAPPER}
-  DEPENDS
-  ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp
-  ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py
-  OUTPUT
-  ${STONE_WRAPPER}
-  )
-
-add_custom_target(StoneWrapper
-  DEPENDS
-  ${STONE_WRAPPER}
-  )  
-
-
-# Define the WASM module
-# ---------------------------------------------------------------
-
-add_executable(StoneWebViewer
-  ${ORTHANC_STONE_SOURCES}
-  ${AUTOGENERATED_SOURCES}
-  StoneWebViewer.cpp
-  )
-
-# Make sure to have the wrapper generated
-add_dependencies(StoneWebViewer StoneWrapper)
-
-
-# Declare installation files for the module
-# ---------------------------------------------------------------
-
-install(
-  TARGETS StoneWebViewer
-  RUNTIME DESTINATION .
-  )
-
-
-# Declare installation files for the companion files (web scaffolding)
-# please note that ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.js
-# (the generated JS loader for the WASM module) is handled by the `install`
-# section above: it is considered to be the binary output of 
-# the linker.
-# ---------------------------------------------------------------
-install(
-  FILES
-  ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.wasm
-  ${CMAKE_SOURCE_DIR}/../WebApplication/app.css
-  ${CMAKE_SOURCE_DIR}/../WebApplication/app.js
-  ${CMAKE_SOURCE_DIR}/../WebApplication/index.html
-  ${STONE_WRAPPER}
-  DESTINATION .
-  )
-
-install(
-  FILES
-  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x1.png
-  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x2.png
-  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x1.png
-  ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x2.png
-  ${CMAKE_SOURCE_DIR}/../WebApplication/img/loading.gif
-  DESTINATION img
-  )
--- a/StoneWebViewer/WebAssembly/NOTES.txt	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-
-Building WebAssembly samples using Docker
-=========================================
-
-The script "./docker-build.sh" can be used to quickly build the
-WebAssembly samples on any GNU/Linux distribution equipped with
-Docker. This avoids newcomers to install Emscripten and learn the
-CMake options. Just type:
-
-$ ./docker-build.sh Release
-
-After successful build, the binaries will be installed in the
-following folder (i.e. in the folder "wasm-binaries" at the root of
-the source distribution):
-
-$ ls -l ../../wasm-binaries
-
-
-NB: The source code of the Docker build environment can be found at
-the following location:
-https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder
-
-
-Native compilation (without Docker)
-===================================
-
-Install Emscripten:
-https://emscripten.org/docs/getting_started/downloads.html
-
-Then, if the installation path is "~/Downloads/emsdk/":
-
-# source ~/Downloads/emsdk/emsdk_env.sh
-# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja
--- a/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-#!/usr/bin/env python
-
-# Stone of Orthanc
-# 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/>.
-
-
-# Ubuntu 20.04:
-# sudo apt-get install python-clang-6.0
-# ./ParseWebAssemblyExports.py --libclang=libclang-6.0.so.1 ./Test.cpp
-
-# Ubuntu 18.04:
-# sudo apt-get install python-clang-4.0
-# ./ParseWebAssemblyExports.py --libclang=libclang-4.0.so.1 ./Test.cpp
-
-# Ubuntu 14.04:
-# ./ParseWebAssemblyExports.py --libclang=libclang-3.6.so.1 ./Test.cpp
-
-
-import sys
-import clang.cindex
-import pystache
-import argparse
-
-##
-## Parse the command-line arguments
-##
-
-parser = argparse.ArgumentParser(description = 'Parse WebAssembly C++ source file, and create a basic JavaScript wrapper.')
-parser.add_argument('--libclang',
-                    default = '',
-                    help = 'manually provides the path to the libclang shared library')
-parser.add_argument('source', 
-                    help = 'Input C++ file')
-
-args = parser.parse_args()
-
-
-
-if len(args.libclang) != 0:
-    clang.cindex.Config.set_library_file(args.libclang)
-
-index = clang.cindex.Index.create()
-
-# PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of
-# undefined types, which prevents compilation of functions
-tu = index.parse(args.source,
-                 [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))' ],
-                 options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
-
-
-
-TEMPLATE = '''
-const Stone = function() {
-  {{#enumerations}}
-  this.{{name}} = {
-    {{#values}}
-    {{name}}: {{value}}{{separator}}
-    {{/values}}
-  };
-
-  {{/enumerations}}
-  {{#functions}}
-  this._{{name}} = undefined;
-  {{/functions}}
-};
-
-Stone.prototype.Setup = function(Module) {
-  {{#functions}}
-  this._{{name}} = Module.cwrap('{{name}}', {{{returnType}}}, [ {{#args}}{{{type}}}{{^last}}, {{/last}}{{/args}} ]);
-  {{/functions}}
-};
-
-{{#functions}}
-Stone.prototype.{{name}} = function({{#args}}{{name}}{{^last}}, {{/last}}{{/args}}) {
-  {{#hasReturn}}return {{/hasReturn}}this._{{name}}({{#args}}{{name}}{{^last}}, {{/last}}{{/args}});
-};
-
-{{/functions}}
-var stone = new Stone();
-'''
-
-
-
-
-# WARNING: Undefined types are mapped as "int"
-
-functions = []
-enumerations = []
-
-def ToUpperCase(source):
-    target = source[0]
-    for c in source[1:]:
-        if c.isupper():
-            target += '_'
-        target += c.upper()
-    return target
-    
-
-
-def IsExported(node):
-    for child in node.get_children():
-        if (child.kind == clang.cindex.CursorKind.ANNOTATE_ATTR and
-            child.displayname == 'WebAssembly'):
-            return True
-
-    return False
-
-
-def Explore(node):
-    if node.kind == clang.cindex.CursorKind.ENUM_DECL:
-        if IsExported(node):
-            name = node.spelling
-            values = []
-            for value in node.get_children():
-                if (value.spelling.startswith(name + '_')):
-                    s = value.spelling[len(name) + 1:]
-                    if len(values) > 0:
-                        values[-1]['separator'] = ','
-                    values.append({
-                        'name' : ToUpperCase(s),
-                        'value' : value.enum_value
-                    })
-                    
-            enumerations.append({
-                'name' : name,
-                'values' : values
-                })
-    
-    if node.kind == clang.cindex.CursorKind.FUNCTION_DECL:
-        if IsExported(node):
-            f = {
-                'name' : node.spelling,
-                'args' : [],
-            }
-
-            returnType = node.result_type.spelling
-            if returnType == 'void':
-                f['hasReturn'] = False
-                f['returnType'] = "null"
-            elif returnType == 'const char *':
-                f['hasReturn'] = True
-                f['returnType'] = "'string'"
-            elif returnType in [ 'int', 'unsigned int' ]:
-                f['hasReturn'] = True
-                f['returnType'] = "'int'"
-            else:
-                raise Exception('Unknown return type in function "%s()": %s' % (node.spelling, returnType))
-
-            for child in node.get_children():
-                if child.kind == clang.cindex.CursorKind.PARM_DECL:
-                    arg = {
-                        'name' : child.displayname,
-                    }
-                    
-                    argType = child.type.spelling
-                    if argType == 'int':
-                        arg['type'] = "'int'"
-                    elif argType == 'const char *':
-                        arg['type'] = "'string'"
-                    else:
-                        raise Exception('Unknown type for argument "%s" in function "%s()": %s' %
-                                        (child.displayname, node.spelling, argType))
-
-                    f['args'].append(arg)
-
-            if len(f['args']) != 0:
-                f['args'][-1]['last'] = True
-                    
-            functions.append(f)
-
-    for child in node.get_children():
-        Explore(child)
-
-Explore(tu.cursor)
-
-
-
-print(pystache.render(TEMPLATE, {
-    'functions' : functions,
-    'enumerations' : enumerations
-}))
--- a/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2266 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 <emscripten.h>
-
-
-#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
-  EM_ASM(                                                       \
-    const customEvent = document.createEvent("CustomEvent");    \
-    customEvent.initCustomEvent(name, false, false, undefined); \
-    window.dispatchEvent(customEvent);                          \
-    );
-
-
-#define EXTERN_CATCH_EXCEPTIONS                         \
-  catch (Orthanc::OrthancException& e)                  \
-  {                                                     \
-    LOG(ERROR) << "OrthancException: " << e.What();     \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (OrthancStone::StoneException& e)               \
-  {                                                     \
-    LOG(ERROR) << "StoneException: " << e.What();       \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (std::exception& e)                             \
-  {                                                     \
-    LOG(ERROR) << "Runtime error: " << e.what();        \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (...)                                           \
-  {                                                     \
-    LOG(ERROR) << "Native exception";                   \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }
-
-
-#include <Cache/MemoryObjectCache.h>
-#include <DicomFormat/DicomArray.h>
-#include <DicomParsing/Internals/DicomImageDecoder.h>
-#include <Images/Image.h>
-#include <Images/ImageProcessing.h>
-#include <Images/JpegReader.h>
-#include <Logging.h>
-
-#include "../../OrthancStone/Sources/Loaders/DicomResourcesLoader.h"
-#include "../../OrthancStone/Sources/Loaders/SeriesMetadataLoader.h"
-#include "../../OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h"
-#include "../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h"
-#include "../../OrthancStone/Sources/Messages/ObserverBase.h"
-#include "../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h"
-#include "../../OrthancStone/Sources/Scene2D/ColorTextureSceneLayer.h"
-#include "../../OrthancStone/Sources/Scene2D/FloatTextureSceneLayer.h"
-#include "../../OrthancStone/Sources/Scene2D/PolylineSceneLayer.h"
-#include "../../OrthancStone/Sources/StoneException.h"
-#include "../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h"
-#include "../../OrthancStone/Sources/Toolbox/GeometryToolbox.h"
-#include "../../OrthancStone/Sources/Toolbox/SortedFrames.h"
-#include "../../OrthancStone/Sources/Viewport/WebGLViewport.h"
-
-#include <boost/make_shared.hpp>
-#include <stdio.h>
-
-
-enum EMSCRIPTEN_KEEPALIVE ThumbnailType
-{
-  ThumbnailType_Image,
-    ThumbnailType_NoPreview,
-    ThumbnailType_Pdf,
-    ThumbnailType_Video,
-    ThumbnailType_Loading,
-    ThumbnailType_Unknown
-};
-
-
-enum EMSCRIPTEN_KEEPALIVE DisplayedFrameQuality
-{
-DisplayedFrameQuality_None,
-  DisplayedFrameQuality_Low,
-  DisplayedFrameQuality_High
-  };
-  
-
-
-static const int PRIORITY_HIGH = -100;
-static const int PRIORITY_LOW = 100;
-static const int PRIORITY_NORMAL = 0;
-
-static const unsigned int QUALITY_JPEG = 0;
-static const unsigned int QUALITY_FULL = 1;
-
-class ResourcesLoader : public OrthancStone::ObserverBase<ResourcesLoader>
-{
-public:
-  class IObserver : public boost::noncopyable
-  {
-  public:
-    virtual ~IObserver()
-    {
-    }
-
-    virtual void SignalResourcesLoaded() = 0;
-
-    virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid,
-                                             const std::string& seriesInstanceUid) = 0;
-
-    virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid,
-                                            const std::string& seriesInstanceUid) = 0;
-  };
-  
-private:
-  std::unique_ptr<IObserver>                               observer_;
-  OrthancStone::DicomSource                                source_;
-  size_t                                                   pending_;
-  boost::shared_ptr<OrthancStone::LoadedDicomResources>    studies_;
-  boost::shared_ptr<OrthancStone::LoadedDicomResources>    series_;
-  boost::shared_ptr<OrthancStone::DicomResourcesLoader>    resourcesLoader_;
-  boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader>  thumbnailsLoader_;
-  boost::shared_ptr<OrthancStone::SeriesMetadataLoader>    metadataLoader_;
-
-  ResourcesLoader(const OrthancStone::DicomSource& source) :
-    source_(source),
-    pending_(0),
-    studies_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID)),
-    series_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID))
-  {
-  }
-
-  void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message)
-  {
-    const Orthanc::SingleValueObject<Orthanc::ResourceType>& payload =
-      dynamic_cast<const Orthanc::SingleValueObject<Orthanc::ResourceType>&>(message.GetUserPayload());
-    
-    OrthancStone::LoadedDicomResources& dicom = *message.GetResources();
-    
-    LOG(INFO) << "resources loaded: " << dicom.GetSize()
-              << ", " << Orthanc::EnumerationToString(payload.GetValue());
-
-    if (payload.GetValue() == Orthanc::ResourceType_Series)
-    {
-      for (size_t i = 0; i < dicom.GetSize(); i++)
-      {
-        std::string studyInstanceUid, seriesInstanceUid;
-        if (dicom.GetResource(i).LookupStringValue(
-              studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
-            dicom.GetResource(i).LookupStringValue(
-              seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
-        {
-          thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid);
-          metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid);
-        }
-      }
-    }
-
-    if (pending_ == 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      pending_ --;
-      if (pending_ == 0 &&
-          observer_.get() != NULL)
-      {
-        observer_->SignalResourcesLoaded();
-      }
-    }
-  }
-
-  void Handle(const OrthancStone::SeriesThumbnailsLoader::SuccessMessage& message)
-  {
-    if (observer_.get() != NULL)
-    {
-      observer_->SignalSeriesThumbnailLoaded(
-        message.GetStudyInstanceUid(), message.GetSeriesInstanceUid());
-    }
-  }
-
-  void Handle(const OrthancStone::SeriesMetadataLoader::SuccessMessage& message)
-  {
-    if (observer_.get() != NULL)
-    {
-      observer_->SignalSeriesMetadataLoaded(
-        message.GetStudyInstanceUid(), message.GetSeriesInstanceUid());
-    }
-  }
-
-  void FetchInternal(const std::string& studyInstanceUid,
-                     const std::string& seriesInstanceUid)
-  {
-    // Firstly, load the study
-    Orthanc::DicomMap filter;
-    filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
-
-    std::set<Orthanc::DicomTag> tags;
-    tags.insert(Orthanc::DICOM_TAG_STUDY_DESCRIPTION);  // Necessary for Orthanc DICOMweb plugin
-
-    resourcesLoader_->ScheduleQido(
-      studies_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Study, filter, tags,
-      new Orthanc::SingleValueObject<Orthanc::ResourceType>(Orthanc::ResourceType_Study));
-
-    // Secondly, load the series
-    if (!seriesInstanceUid.empty())
-    {
-      filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false);
-    }
-    
-    tags.insert(Orthanc::DICOM_TAG_SERIES_NUMBER);  // Necessary for Google Cloud Platform
-    
-    resourcesLoader_->ScheduleQido(
-      series_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Series, filter, tags,
-      new Orthanc::SingleValueObject<Orthanc::ResourceType>(Orthanc::ResourceType_Series));
-
-    pending_ += 2;
-  }
-
-public:
-  static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::ILoadersContext::ILock& lock,
-                                                   const OrthancStone::DicomSource& source)
-  {
-    boost::shared_ptr<ResourcesLoader> loader(new ResourcesLoader(source));
-
-    loader->resourcesLoader_ = OrthancStone::DicomResourcesLoader::Create(lock);
-    loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(lock, PRIORITY_LOW);
-    loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(lock);
-    
-    loader->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>(
-      *loader->resourcesLoader_, &ResourcesLoader::Handle);
-
-    loader->Register<OrthancStone::SeriesThumbnailsLoader::SuccessMessage>(
-      *loader->thumbnailsLoader_, &ResourcesLoader::Handle);
-
-    loader->Register<OrthancStone::SeriesMetadataLoader::SuccessMessage>(
-      *loader->metadataLoader_, &ResourcesLoader::Handle);
-    
-    return loader;
-  }
-  
-  void FetchAllStudies()
-  {
-    FetchInternal("", "");
-  }
-  
-  void FetchStudy(const std::string& studyInstanceUid)
-  {
-    FetchInternal(studyInstanceUid, "");
-  }
-  
-  void FetchSeries(const std::string& studyInstanceUid,
-                   const std::string& seriesInstanceUid)
-  {
-    FetchInternal(studyInstanceUid, seriesInstanceUid);
-  }
-
-  size_t GetStudiesCount() const
-  {
-    return studies_->GetSize();
-  }
-
-  size_t GetSeriesCount() const
-  {
-    return series_->GetSize();
-  }
-
-  void GetStudy(Orthanc::DicomMap& target,
-                size_t i)
-  {
-    target.Assign(studies_->GetResource(i));
-  }
-
-  void GetSeries(Orthanc::DicomMap& target,
-                 size_t i)
-  {
-    target.Assign(series_->GetResource(i));
-
-    // Complement with the study-level tags
-    std::string studyInstanceUid;
-    if (target.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
-        studies_->HasResource(studyInstanceUid))
-    {
-      studies_->MergeResource(target, studyInstanceUid);
-    }
-  }
-
-  OrthancStone::SeriesThumbnailType GetSeriesThumbnail(std::string& image,
-                                                       std::string& mime,
-                                                       const std::string& seriesInstanceUid)
-  {
-    return thumbnailsLoader_->GetSeriesThumbnail(image, mime, seriesInstanceUid);
-  }
-
-  void FetchSeriesMetadata(int priority,
-                           const std::string& studyInstanceUid,
-                           const std::string& seriesInstanceUid)
-  {
-    metadataLoader_->ScheduleLoadSeries(priority, source_, studyInstanceUid, seriesInstanceUid);
-  }
-
-  bool IsSeriesComplete(const std::string& seriesInstanceUid)
-  {
-    OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid);
-    return accessor.IsComplete();
-  }
-
-  bool SortSeriesFrames(OrthancStone::SortedFrames& target,
-                        const std::string& seriesInstanceUid)
-  {
-    OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid);
-    
-    if (accessor.IsComplete())
-    {
-      target.Clear();
-
-      for (size_t i = 0; i < accessor.GetInstancesCount(); i++)
-      {
-        target.AddInstance(accessor.GetInstance(i));
-      }
-
-      target.Sort();
-      
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  void AcquireObserver(IObserver* observer)
-  {  
-    observer_.reset(observer);
-  }
-};
-
-
-
-class FramesCache : public boost::noncopyable
-{
-private:
-  class CachedImage : public Orthanc::ICacheable
-  {
-  private:
-    std::unique_ptr<Orthanc::ImageAccessor>  image_;
-    unsigned int                             quality_;
-
-  public:
-    CachedImage(Orthanc::ImageAccessor* image,
-                unsigned int quality) :
-      image_(image),
-      quality_(quality)
-    {
-      assert(image != NULL);
-    }
-
-    virtual size_t GetMemoryUsage() const
-    {    
-      assert(image_.get() != NULL);
-      return (image_->GetBytesPerPixel() * image_->GetPitch() * image_->GetHeight());
-    }
-
-    const Orthanc::ImageAccessor& GetImage() const
-    {
-      assert(image_.get() != NULL);
-      return *image_;
-    }
-
-    unsigned int GetQuality() const
-    {
-      return quality_;
-    }
-  };
-
-
-  static std::string GetKey(const std::string& sopInstanceUid,
-                            size_t frameIndex)
-  {
-    return sopInstanceUid + "|" + boost::lexical_cast<std::string>(frameIndex);
-  }
-  
-
-  Orthanc::MemoryObjectCache  cache_;
-  
-public:
-  FramesCache()
-  {
-    SetMaximumSize(100 * 1024 * 1024);  // 100 MB
-  }
-  
-  size_t GetMaximumSize()
-  {
-    return cache_.GetMaximumSize();
-  }
-    
-  void SetMaximumSize(size_t size)
-  {
-    cache_.SetMaximumSize(size);
-  }
-
-  /**
-   * Returns "true" iff the provided image has better quality than the
-   * previously cached one, or if no cache was previously available.
-   **/
-  bool Acquire(const std::string& sopInstanceUid,
-               size_t frameIndex,
-               Orthanc::ImageAccessor* image /* transfer ownership */,
-               unsigned int quality)
-  {
-    std::unique_ptr<Orthanc::ImageAccessor> protection(image);
-    
-    if (image == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else if (image->GetFormat() != Orthanc::PixelFormat_Float32 &&
-             image->GetFormat() != Orthanc::PixelFormat_RGB24)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-
-    const std::string& key = GetKey(sopInstanceUid, frameIndex);
-
-    bool invalidate = false;
-    
-    {
-      /**
-       * Access the previous cached entry, with side effect of tagging
-       * it as the most recently accessed frame (update of LRU recycling)
-       **/
-      Orthanc::MemoryObjectCache::Accessor accessor(cache_, key, false /* unique lock */);
-
-      if (accessor.IsValid())
-      {
-        const CachedImage& previous = dynamic_cast<const CachedImage&>(accessor.GetValue());
-        
-        // There is already a cached image for this frame
-        if (previous.GetQuality() < quality)
-        {
-          // The previously stored image has poorer quality
-          invalidate = true;
-        }
-        else
-        {
-          // No update in the quality, don't change the cache
-          return false;   
-        }
-      }
-      else
-      {
-        invalidate = false;
-      }
-    }
-
-    if (invalidate)
-    {
-      cache_.Invalidate(key);
-    }
-        
-    cache_.Acquire(key, new CachedImage(protection.release(), quality));
-    return true;
-  }
-
-  class Accessor : public boost::noncopyable
-  {
-  private:
-    Orthanc::MemoryObjectCache::Accessor accessor_;
-
-    const CachedImage& GetCachedImage() const
-    {
-      if (IsValid())
-      {
-        return dynamic_cast<CachedImage&>(accessor_.GetValue());
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-    
-  public:
-    Accessor(FramesCache& that,
-             const std::string& sopInstanceUid,
-             size_t frameIndex) :
-      accessor_(that.cache_, GetKey(sopInstanceUid, frameIndex), false /* shared lock */)
-    {
-    }
-
-    bool IsValid() const
-    {
-      return accessor_.IsValid();
-    }
-
-    const Orthanc::ImageAccessor& GetImage() const
-    {
-      return GetCachedImage().GetImage();
-    }
-
-    unsigned int GetQuality() const
-    {
-      return GetCachedImage().GetQuality();
-    }
-  };
-};
-
-
-
-class SeriesCursor : public boost::noncopyable
-{
-public:
-  enum Action
-  {
-    Action_FastPlus,
-    Action_Plus,
-    Action_None,
-    Action_Minus,
-    Action_FastMinus
-  };
-  
-private:
-  std::vector<size_t>  prefetch_;
-  int                  framesCount_;
-  int                  currentFrame_;
-  bool                 isCircular_;
-  int                  fastDelta_;
-  Action               lastAction_;
-
-  int ComputeNextFrame(int currentFrame,
-                       Action action) const
-  {
-    if (framesCount_ == 0)
-    {
-      assert(currentFrame == 0);
-      return 0;
-    }
-
-    int nextFrame = currentFrame;
-    
-    switch (action)
-    {
-      case Action_FastPlus:
-        nextFrame += fastDelta_;
-        break;
-
-      case Action_Plus:
-        nextFrame += 1;
-        break;
-
-      case Action_None:
-        break;
-
-      case Action_Minus:
-        nextFrame -= 1;
-        break;
-
-      case Action_FastMinus:
-        nextFrame -= fastDelta_;
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    if (isCircular_)
-    {
-      while (nextFrame < 0)
-      {
-        nextFrame += framesCount_;
-      }
-
-      while (nextFrame >= framesCount_)
-      {
-        nextFrame -= framesCount_;
-      }
-    }
-    else
-    {
-      if (nextFrame < 0)
-      {
-        nextFrame = 0;
-      }
-      else if (nextFrame >= framesCount_)
-      {
-        nextFrame = framesCount_ - 1;
-      }
-    }
-
-    return nextFrame;
-  }
-  
-  void UpdatePrefetch()
-  {
-    /**
-     * This method will order the frames of the series according to
-     * the number of "actions" (i.e. mouse wheels) that are necessary
-     * to reach them, starting from the current frame. It is assumed
-     * that once one action is done, it is more likely that the user
-     * will do the same action just afterwards.
-     **/
-    
-    prefetch_.clear();
-
-    if (framesCount_ == 0)
-    {
-      return;
-    }
-
-    prefetch_.reserve(framesCount_);
-    
-    // Breadth-first search using a FIFO. The queue associates a frame
-    // and the action that is the most likely in this frame
-    typedef std::list< std::pair<int, Action> >  Queue;
-
-    Queue queue;
-    std::set<int>  visited;  // Frames that have already been visited
-
-    queue.push_back(std::make_pair(currentFrame_, lastAction_));
-
-    while (!queue.empty())
-    {
-      int frame = queue.front().first;
-      Action previousAction = queue.front().second;
-      queue.pop_front();
-
-      if (visited.find(frame) == visited.end())
-      {
-        visited.insert(frame);
-        prefetch_.push_back(frame);
-
-        switch (previousAction)
-        {
-          case Action_None:
-          case Action_Plus:
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
-            break;
-          
-          case Action_Minus:
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
-            break;
-
-          case Action_FastPlus:
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
-            break;
-              
-          case Action_FastMinus:
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus));
-            queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus));
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-      }
-    }
-
-    assert(prefetch_.size() == framesCount_);
-  }
-
-  bool CheckFrameIndex(int frame) const
-  {
-    return ((framesCount_ == 0 && frame == 0) ||
-            (framesCount_ > 0 && frame >= 0 && frame < framesCount_));
-  }
-  
-public:
-  SeriesCursor(size_t framesCount) :
-    framesCount_(framesCount),
-    currentFrame_(framesCount / 2),  // Start at the middle frame    
-    isCircular_(false),
-    lastAction_(Action_None)
-  {
-    SetFastDelta(framesCount / 20);
-    UpdatePrefetch();
-  }
-
-  void SetCircular(bool isCircular)
-  {
-    isCircular_ = isCircular;
-    UpdatePrefetch();
-  }
-
-  void SetFastDelta(int delta)
-  {
-    fastDelta_ = (delta < 0 ? -delta : delta);
-
-    if (fastDelta_ <= 0)
-    {
-      fastDelta_ = 1;
-    }
-  }
-
-  void SetCurrentIndex(size_t frame)
-  {
-    if (frame >= framesCount_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      currentFrame_ = frame;
-      lastAction_ = Action_None;
-      UpdatePrefetch();
-    }
-  }
-
-  size_t GetCurrentIndex() const
-  {
-    assert(CheckFrameIndex(currentFrame_));
-    return static_cast<size_t>(currentFrame_);
-  }
-
-  void Apply(Action action)
-  {
-    currentFrame_ = ComputeNextFrame(currentFrame_, action);
-    lastAction_ = action;
-    UpdatePrefetch();
-  }
-
-  size_t GetPrefetchSize() const
-  {
-    assert(prefetch_.size() == framesCount_);
-    return prefetch_.size();
-  }
-
-  size_t GetPrefetchFrameIndex(size_t i) const
-  {
-    if (i >= prefetch_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(CheckFrameIndex(prefetch_[i]));
-      return static_cast<size_t>(prefetch_[i]);
-    }
-  }
-};
-
-
-
-
-class FrameGeometry
-{
-private:
-  bool                              isValid_;
-  std::string                       frameOfReferenceUid_;
-  OrthancStone::CoordinateSystem3D  coordinates_;
-  double                            pixelSpacingX_;
-  double                            pixelSpacingY_;
-  OrthancStone::Extent2D            extent_;
-
-public:
-  FrameGeometry() :
-    isValid_(false)
-  {
-  }
-    
-  FrameGeometry(const Orthanc::DicomMap& tags) :
-    isValid_(false),
-    coordinates_(tags)
-  {
-    if (!tags.LookupStringValue(
-          frameOfReferenceUid_, Orthanc::DICOM_TAG_FRAME_OF_REFERENCE_UID, false))
-    {
-      frameOfReferenceUid_.clear();
-    }
-
-    OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, tags);
-
-    unsigned int rows, columns;
-    if (tags.HasTag(Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT) &&
-        tags.HasTag(Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) &&
-        tags.ParseUnsignedInteger32(rows, Orthanc::DICOM_TAG_ROWS) &&
-        tags.ParseUnsignedInteger32(columns, Orthanc::DICOM_TAG_COLUMNS))
-    {
-      double ox = -pixelSpacingX_ / 2.0;
-      double oy = -pixelSpacingY_ / 2.0;
-      extent_.AddPoint(ox, oy);
-      extent_.AddPoint(ox + pixelSpacingX_ * static_cast<double>(columns),
-                       oy + pixelSpacingY_ * static_cast<double>(rows));
-
-      isValid_ = true;
-    }
-  }
-
-  bool IsValid() const
-  {
-    return isValid_;
-  }
-
-  const std::string& GetFrameOfReferenceUid() const
-  {
-    if (isValid_)
-    {
-      return frameOfReferenceUid_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  const OrthancStone::CoordinateSystem3D& GetCoordinates() const
-  {
-    if (isValid_)
-    {
-      return coordinates_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  double GetPixelSpacingX() const
-  {
-    if (isValid_)
-    {
-      return pixelSpacingX_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  double GetPixelSpacingY() const
-  {
-    if (isValid_)
-    {
-      return pixelSpacingY_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  bool Intersect(double& x1,  // Coordinates of the clipped line (out)
-                 double& y1,
-                 double& x2,
-                 double& y2,
-                 const FrameGeometry& other) const
-  {
-    if (this == &other)
-    {
-      return false;
-    }
-    
-    OrthancStone::Vector direction, origin;
-        
-    if (IsValid() &&
-        other.IsValid() &&
-        !extent_.IsEmpty() &&
-        frameOfReferenceUid_ == other.frameOfReferenceUid_ &&
-        OrthancStone::GeometryToolbox::IntersectTwoPlanes(
-          origin, direction,
-          coordinates_.GetOrigin(), coordinates_.GetNormal(),
-          other.coordinates_.GetOrigin(), other.coordinates_.GetNormal()))
-    {
-      double ax, ay, bx, by;
-      coordinates_.ProjectPoint(ax, ay, origin);
-      coordinates_.ProjectPoint(bx, by, origin + 100.0 * direction);
-      
-      return OrthancStone::GeometryToolbox::ClipLineToRectangle(
-        x1, y1, x2, y2,
-        ax, ay, bx, by,
-        extent_.GetX1(), extent_.GetY1(), extent_.GetX2(), extent_.GetY2());
-    }
-    else
-    {
-      return false;
-    }
-  }
-};
-  
-
-
-class ViewerViewport : public OrthancStone::ObserverBase<ViewerViewport>
-{
-public:
-  class IObserver : public boost::noncopyable
-  {
-  public:
-    virtual ~IObserver()
-    {
-    }
-
-    virtual void SignalFrameUpdated(const ViewerViewport& viewport,
-                                    size_t currentFrame,
-                                    size_t countFrames,
-                                    DisplayedFrameQuality quality) = 0;
-  };
-
-private:
-  static const int LAYER_TEXTURE = 0;
-  static const int LAYER_REFERENCE_LINES = 1;
-  
-  
-  class ICommand : public Orthanc::IDynamicObject
-  {
-  private:
-    boost::shared_ptr<ViewerViewport>  viewport_;
-    
-  public:
-    ICommand(boost::shared_ptr<ViewerViewport> viewport) :
-      viewport_(viewport)
-    {
-      if (viewport == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-    
-    virtual ~ICommand()
-    {
-    }
-
-    ViewerViewport& GetViewport() const
-    {
-      assert(viewport_ != NULL);
-      return *viewport_;
-    }
-    
-    virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-    
-    virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  };
-
-  class SetDefaultWindowingCommand : public ICommand
-  {
-  public:
-    SetDefaultWindowingCommand(boost::shared_ptr<ViewerViewport> viewport) :
-      ICommand(viewport)
-    {
-    }
-    
-    virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE
-    {
-      if (message.GetResources()->GetSize() != 1)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      const Orthanc::DicomMap& dicom = message.GetResources()->GetResource(0);
-      
-      {
-        OrthancStone::DicomInstanceParameters params(dicom);
-
-        if (params.HasDefaultWindowing())
-        {
-          GetViewport().defaultWindowingCenter_ = params.GetDefaultWindowingCenter();
-          GetViewport().defaultWindowingWidth_ = params.GetDefaultWindowingWidth();
-          LOG(INFO) << "Default windowing: " << params.GetDefaultWindowingCenter()
-                    << "," << params.GetDefaultWindowingWidth();
-
-          GetViewport().windowingCenter_ = params.GetDefaultWindowingCenter();
-          GetViewport().windowingWidth_ = params.GetDefaultWindowingWidth();
-        }
-        else
-        {
-          LOG(INFO) << "No default windowing";
-          GetViewport().ResetDefaultWindowing();
-        }
-      }
-
-      GetViewport().DisplayCurrentFrame();
-    }
-  };
-
-  class SetLowQualityFrame : public ICommand
-  {
-  private:
-    std::string   sopInstanceUid_;
-    unsigned int  frameIndex_;
-    float         windowCenter_;
-    float         windowWidth_;
-    bool          isMonochrome1_;
-    bool          isPrefetch_;
-    
-  public:
-    SetLowQualityFrame(boost::shared_ptr<ViewerViewport> viewport,
-                       const std::string& sopInstanceUid,
-                       unsigned int frameIndex,
-                       float windowCenter,
-                       float windowWidth,
-                       bool isMonochrome1,
-                       bool isPrefetch) :
-      ICommand(viewport),
-      sopInstanceUid_(sopInstanceUid),
-      frameIndex_(frameIndex),
-      windowCenter_(windowCenter),
-      windowWidth_(windowWidth),
-      isMonochrome1_(isMonochrome1),
-      isPrefetch_(isPrefetch)
-    {
-    }
-    
-    virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const ORTHANC_OVERRIDE
-    {
-      std::unique_ptr<Orthanc::JpegReader> jpeg(new Orthanc::JpegReader);
-      jpeg->ReadFromMemory(message.GetAnswer());
-
-      bool updatedCache;
-      
-      switch (jpeg->GetFormat())
-      {
-        case Orthanc::PixelFormat_RGB24:
-          updatedCache = GetViewport().cache_->Acquire(
-            sopInstanceUid_, frameIndex_, jpeg.release(), QUALITY_JPEG);
-          break;
-
-        case Orthanc::PixelFormat_Grayscale8:
-        {
-          if (isMonochrome1_)
-          {
-            Orthanc::ImageProcessing::Invert(*jpeg);
-          }
-
-          std::unique_ptr<Orthanc::Image> converted(
-            new Orthanc::Image(Orthanc::PixelFormat_Float32, jpeg->GetWidth(),
-                               jpeg->GetHeight(), false));
-
-          Orthanc::ImageProcessing::Convert(*converted, *jpeg);
-
-          /**
-
-             Orthanc::ImageProcessing::ShiftScale() computes "(x + offset) * scaling".
-             The system to solve is thus:           
-
-             (0 + offset) * scaling = windowingCenter - windowingWidth / 2     [a]
-             (255 + offset) * scaling = windowingCenter + windowingWidth / 2   [b]
-
-             Resolution:
-
-             [b - a] => 255 * scaling = windowingWidth
-             [a] => offset = (windowingCenter - windowingWidth / 2) / scaling
-
-          **/
-
-          const float scaling = windowWidth_ / 255.0f;
-          const float offset = (OrthancStone::LinearAlgebra::IsCloseToZero(scaling) ? 0 :
-                                (windowCenter_ - windowWidth_ / 2.0f) / scaling);
-
-          Orthanc::ImageProcessing::ShiftScale(*converted, offset, scaling, false);
-          updatedCache = GetViewport().cache_->Acquire(
-            sopInstanceUid_, frameIndex_, converted.release(), QUALITY_JPEG);
-          break;
-        }
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      if (updatedCache)
-      {
-        GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_);
-      }
-
-      if (isPrefetch_)
-      {
-        GetViewport().ScheduleNextPrefetch();
-      }
-    }
-  };
-
-
-  class SetFullDicomFrame : public ICommand
-  {
-  private:
-    std::string   sopInstanceUid_;
-    unsigned int  frameIndex_;
-    bool          isPrefetch_;
-    
-  public:
-    SetFullDicomFrame(boost::shared_ptr<ViewerViewport> viewport,
-                      const std::string& sopInstanceUid,
-                      unsigned int frameIndex,
-                      bool isPrefetch) :
-      ICommand(viewport),
-      sopInstanceUid_(sopInstanceUid),
-      frameIndex_(frameIndex),
-      isPrefetch_(isPrefetch)
-    {
-    }
-    
-    virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const ORTHANC_OVERRIDE
-    {
-      Orthanc::DicomMap tags;
-      message.GetDicom().ExtractDicomSummary(tags, ORTHANC_STONE_MAX_TAG_LENGTH);
-
-      std::string s;
-      if (!tags.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
-      {
-        // Safety check
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }      
-      
-      std::unique_ptr<Orthanc::ImageAccessor> frame(
-        Orthanc::DicomImageDecoder::Decode(message.GetDicom(), frameIndex_));
-
-      if (frame.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      bool updatedCache;
-
-      if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
-      {
-        updatedCache = GetViewport().cache_->Acquire(
-          sopInstanceUid_, frameIndex_, frame.release(), QUALITY_FULL);
-      }
-      else
-      {
-        double a = 1;
-        double b = 0;
-
-        double doseScaling;
-        if (tags.ParseDouble(doseScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
-        {
-          a = doseScaling;
-        }
-      
-        double rescaleIntercept, rescaleSlope;
-        if (tags.ParseDouble(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
-            tags.ParseDouble(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE))
-        {
-          a *= rescaleSlope;
-          b = rescaleIntercept;
-        }
-
-        std::unique_ptr<Orthanc::ImageAccessor> converted(
-          new Orthanc::Image(Orthanc::PixelFormat_Float32, frame->GetWidth(), frame->GetHeight(), false));
-        Orthanc::ImageProcessing::Convert(*converted, *frame);
-        Orthanc::ImageProcessing::ShiftScale2(*converted, b, a, false);
-
-        updatedCache = GetViewport().cache_->Acquire(
-          sopInstanceUid_, frameIndex_, converted.release(), QUALITY_FULL);
-      }
-      
-      if (updatedCache)
-      {
-        GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_);
-      }
-
-      if (isPrefetch_)
-      {
-        GetViewport().ScheduleNextPrefetch();
-      }
-    }
-  };
-
-
-  class PrefetchItem
-  {
-  private:
-    size_t   frameIndex_;
-    bool     isFull_;
-
-  public:
-    PrefetchItem(size_t frameIndex,
-                 bool isFull) :
-      frameIndex_(frameIndex),
-      isFull_(isFull)
-    {
-    }
-
-    size_t GetFrameIndex() const
-    {
-      return frameIndex_;
-    }
-
-    bool IsFull() const
-    {
-      return isFull_;
-    }
-  };
-  
-
-  std::unique_ptr<IObserver>                    observer_;
-  OrthancStone::ILoadersContext&               context_;
-  boost::shared_ptr<OrthancStone::WebGLViewport>   viewport_;
-  boost::shared_ptr<OrthancStone::DicomResourcesLoader> loader_;
-  OrthancStone::DicomSource                    source_;
-  boost::shared_ptr<FramesCache>               cache_;  
-  std::unique_ptr<OrthancStone::SortedFrames>  frames_;
-  std::unique_ptr<SeriesCursor>                cursor_;
-  float                                        windowingCenter_;
-  float                                        windowingWidth_;
-  float                                        defaultWindowingCenter_;
-  float                                        defaultWindowingWidth_;
-  bool                                         inverted_;
-  bool                                         fitNextContent_;
-  bool                                         isCtrlDown_;
-  FrameGeometry                                currentFrameGeometry_;
-  std::list<PrefetchItem>                      prefetchQueue_;
-
-  void ScheduleNextPrefetch()
-  {
-    while (!prefetchQueue_.empty())
-    {
-      size_t index = prefetchQueue_.front().GetFrameIndex();
-      bool isFull = prefetchQueue_.front().IsFull();
-      prefetchQueue_.pop_front();
-      
-      const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
-      unsigned int frame = frames_->GetFrameIndex(index);
-
-      {
-        FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame);
-        if (!accessor.IsValid() ||
-            (isFull && accessor.GetQuality() == 0))
-        {
-          if (isFull)
-          {
-            ScheduleLoadFullDicomFrame(index, PRIORITY_NORMAL, true);
-          }
-          else
-          {
-            ScheduleLoadRenderedFrame(index, PRIORITY_NORMAL, true);
-          }
-          return;
-        }
-      }
-    }
-  }
-  
-  
-  void ResetDefaultWindowing()
-  {
-    defaultWindowingCenter_ = 128;
-    defaultWindowingWidth_ = 256;
-
-    windowingCenter_ = defaultWindowingCenter_;
-    windowingWidth_ = defaultWindowingWidth_;
-
-    inverted_ = false;
-  }
-
-  void SignalUpdatedFrame(const std::string& sopInstanceUid,
-                          unsigned int frameIndex)
-  {
-    if (cursor_.get() != NULL &&
-        frames_.get() != NULL)
-    {
-      size_t index = cursor_->GetCurrentIndex();
-
-      if (frames_->GetFrameSopInstanceUid(index) == sopInstanceUid &&
-          frames_->GetFrameIndex(index) == frameIndex)
-      {
-        DisplayCurrentFrame();
-      }
-    }
-  }
-
-  void DisplayCurrentFrame()
-  {
-    DisplayedFrameQuality quality = DisplayedFrameQuality_None;
-    
-    if (cursor_.get() != NULL &&
-        frames_.get() != NULL)
-    {
-      const size_t index = cursor_->GetCurrentIndex();
-      
-      unsigned int cachedQuality;
-      if (!DisplayFrame(cachedQuality, index))
-      {
-        // This frame is not cached yet: Load it
-        if (source_.HasDicomWebRendered())
-        {
-          ScheduleLoadRenderedFrame(index, PRIORITY_HIGH, false /* not a prefetch */);
-        }
-        else
-        {
-          ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */);
-        }
-      }
-      else if (cachedQuality < QUALITY_FULL)
-      {
-        // This frame is only available in low-res: Download the full DICOM
-        ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */);
-        quality = DisplayedFrameQuality_Low;
-      }
-      else
-      {
-        quality = DisplayedFrameQuality_High;
-      }
-
-      currentFrameGeometry_ = FrameGeometry(frames_->GetFrameTags(index));
-
-      {
-        // Prepare prefetching
-        prefetchQueue_.clear();
-        for (size_t i = 0; i < cursor_->GetPrefetchSize() && i < 16; i++)
-        {
-          size_t a = cursor_->GetPrefetchFrameIndex(i);
-          if (a != index)
-          {
-            prefetchQueue_.push_back(PrefetchItem(a, i < 2));
-          }
-        }
-
-        ScheduleNextPrefetch();
-      }      
-
-      if (observer_.get() != NULL)
-      {
-        observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(),
-                                      frames_->GetFramesCount(), quality);
-      }
-    }
-    else
-    {
-      currentFrameGeometry_ = FrameGeometry();
-    }
-  }
-
-  void ClearViewport()
-  {
-    {
-      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-      lock->GetController().GetScene().DeleteLayer(LAYER_TEXTURE);
-      //lock->GetCompositor().Refresh(lock->GetController().GetScene());
-      lock->Invalidate();
-    }
-  }
-
-  bool DisplayFrame(unsigned int& quality,
-                    size_t index)
-  {
-    if (frames_.get() == NULL)
-    {
-      return false;
-    }
-
-    const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
-    unsigned int frame = frames_->GetFrameIndex(index);
-
-    FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame);
-    if (accessor.IsValid())
-    {
-      {
-        std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-
-        OrthancStone::Scene2D& scene = lock->GetController().GetScene();
-
-        // Save the current windowing (that could have been altered by
-        // GrayscaleWindowingSceneTracker), so that it can be reused
-        // by the next frames
-        if (scene.HasLayer(LAYER_TEXTURE) &&
-            scene.GetLayer(LAYER_TEXTURE).GetType() == OrthancStone::ISceneLayer::Type_FloatTexture)
-        {
-          OrthancStone::FloatTextureSceneLayer& layer =
-            dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(scene.GetLayer(LAYER_TEXTURE));
-          layer.GetWindowing(windowingCenter_, windowingWidth_);
-        }
-      }
-      
-      quality = accessor.GetQuality();
-
-      std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer;
-
-      switch (accessor.GetImage().GetFormat())
-      {
-        case Orthanc::PixelFormat_RGB24:
-          layer.reset(new OrthancStone::ColorTextureSceneLayer(accessor.GetImage()));
-          break;
-
-        case Orthanc::PixelFormat_Float32:
-        {
-          std::unique_ptr<OrthancStone::FloatTextureSceneLayer> tmp(
-            new OrthancStone::FloatTextureSceneLayer(accessor.GetImage()));
-          tmp->SetCustomWindowing(windowingCenter_, windowingWidth_);
-          tmp->SetInverted(inverted_ ^ frames_->IsFrameMonochrome1(index));
-          layer.reset(tmp.release());
-          break;
-        }
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      layer->SetLinearInterpolation(true);
-
-      double pixelSpacingX, pixelSpacingY;
-      OrthancStone::GeometryToolbox::GetPixelSpacing(
-        pixelSpacingX, pixelSpacingY, frames_->GetFrameTags(index));
-      layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY);
-
-      if (layer.get() == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-
-        OrthancStone::Scene2D& scene = lock->GetController().GetScene();
-
-        scene.SetLayer(LAYER_TEXTURE, layer.release());
-
-        if (fitNextContent_)
-        {
-          lock->GetCompositor().RefreshCanvasSize();
-          lock->GetCompositor().FitContent(scene);
-          fitNextContent_ = false;
-        }
-        
-        //lock->GetCompositor().Refresh(scene);
-        lock->Invalidate();
-        return true;
-      }
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  void ScheduleLoadFullDicomFrame(size_t index,
-                                  int priority,
-                                  bool isPrefetch)
-  {
-    if (frames_.get() != NULL)
-    {
-      std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
-      unsigned int frame = frames_->GetFrameIndex(index);
-      
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-        lock->Schedule(
-          GetSharedObserver(), priority, OrthancStone::ParseDicomFromWadoCommand::Create(
-            source_, frames_->GetStudyInstanceUid(), frames_->GetSeriesInstanceUid(),
-            sopInstanceUid, false /* transcoding (TODO) */,
-            Orthanc::DicomTransferSyntax_LittleEndianExplicit /* TODO */,
-            new SetFullDicomFrame(GetSharedObserver(), sopInstanceUid, frame, isPrefetch)));
-      }
-    }
-  }
-
-  void ScheduleLoadRenderedFrame(size_t index,
-                                 int priority,
-                                 bool isPrefetch)
-  {
-    if (!source_.HasDicomWebRendered())
-    {
-      ScheduleLoadFullDicomFrame(index, priority, isPrefetch);
-    }
-    else if (frames_.get() != NULL)
-    {
-      std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index);
-      unsigned int frame = frames_->GetFrameIndex(index);
-      bool isMonochrome1 = frames_->IsFrameMonochrome1(index);
-
-      const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() +
-                               "/series/" + frames_->GetSeriesInstanceUid() +
-                               "/instances/" + sopInstanceUid +
-                               "/frames/" + boost::lexical_cast<std::string>(frame + 1) + "/rendered");
-
-      std::map<std::string, std::string> headers, arguments;
-      arguments["window"] = (
-        boost::lexical_cast<std::string>(windowingCenter_) + ","  +
-        boost::lexical_cast<std::string>(windowingWidth_) + ",linear");
-
-      std::unique_ptr<OrthancStone::IOracleCommand> command(
-        source_.CreateDicomWebCommand(
-          uri, arguments, headers, new SetLowQualityFrame(
-            GetSharedObserver(), sopInstanceUid, frame,
-            windowingCenter_, windowingWidth_, isMonochrome1, isPrefetch)));
-
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-        lock->Schedule(GetSharedObserver(), priority, command.release());
-      }
-    }
-  }
-
-  ViewerViewport(OrthancStone::ILoadersContext& context,
-                 const OrthancStone::DicomSource& source,
-                 const std::string& canvas,
-                 boost::shared_ptr<FramesCache> cache) :
-    context_(context),
-    source_(source),
-    viewport_(OrthancStone::WebGLViewport::Create(canvas)),
-    cache_(cache),
-    fitNextContent_(true),
-    isCtrlDown_(false)
-  {
-    if (!cache_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    
-    emscripten_set_wheel_callback(viewport_->GetCanvasCssSelector().c_str(), this, true, OnWheel);
-    emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey);
-    emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey);
-
-    ResetDefaultWindowing();
-  }
-
-  static EM_BOOL OnKey(int eventType,
-                       const EmscriptenKeyboardEvent *event,
-                       void *userData)
-  {
-    /**
-     * WARNING: There is a problem with Firefox 71 that seems to mess
-     * the "ctrlKey" value.
-     **/
-    
-    ViewerViewport& that = *reinterpret_cast<ViewerViewport*>(userData);
-    that.isCtrlDown_ = event->ctrlKey;
-    return false;
-  }
-
-  
-  static EM_BOOL OnWheel(int eventType,
-                         const EmscriptenWheelEvent *wheelEvent,
-                         void *userData)
-  {
-    ViewerViewport& that = *reinterpret_cast<ViewerViewport*>(userData);
-
-    if (that.cursor_.get() != NULL)
-    {
-      if (wheelEvent->deltaY < 0)
-      {
-        that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastMinus : SeriesCursor::Action_Minus);
-      }
-      else if (wheelEvent->deltaY > 0)
-      {
-        that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastPlus : SeriesCursor::Action_Plus);
-      }
-    }
-    
-    return true;
-  }
-
-  void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message)
-  {
-    dynamic_cast<const ICommand&>(message.GetUserPayload()).Handle(message);
-  }
-
-  void Handle(const OrthancStone::HttpCommand::SuccessMessage& message)
-  {
-    dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message);
-  }
-
-  void Handle(const OrthancStone::ParseDicomSuccessMessage& message)
-  {
-    dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message);
-  }
-  
-public:
-  static boost::shared_ptr<ViewerViewport> Create(OrthancStone::ILoadersContext::ILock& lock,
-                                                  const OrthancStone::DicomSource& source,
-                                                  const std::string& canvas,
-                                                  boost::shared_ptr<FramesCache> cache)
-  {
-    boost::shared_ptr<ViewerViewport> viewport(
-      new ViewerViewport(lock.GetContext(), source, canvas, cache));
-
-    viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(lock);
-    viewport->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>(
-      *viewport->loader_, &ViewerViewport::Handle);
-
-    viewport->Register<OrthancStone::HttpCommand::SuccessMessage>(
-      lock.GetOracleObservable(), &ViewerViewport::Handle);
-
-    viewport->Register<OrthancStone::ParseDicomSuccessMessage>(
-      lock.GetOracleObservable(), &ViewerViewport::Handle);
-
-    return viewport;    
-  }
-
-  void SetFrames(OrthancStone::SortedFrames* frames)
-  {
-    if (frames == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    fitNextContent_ = true;
-
-    frames_.reset(frames);
-    cursor_.reset(new SeriesCursor(frames_->GetFramesCount()));
-
-    LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount();
-
-    ResetDefaultWindowing();
-    ClearViewport();
-    prefetchQueue_.clear();
-    currentFrameGeometry_ = FrameGeometry();
-
-    if (observer_.get() != NULL)
-    {
-      observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(),
-                                    frames_->GetFramesCount(), DisplayedFrameQuality_None);
-    }
-    
-    if (frames_->GetFramesCount() != 0)
-    {
-      const std::string& sopInstanceUid = frames_->GetFrameSopInstanceUid(cursor_->GetCurrentIndex());
-
-      {
-        // Fetch the default windowing for the central instance
-        const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() +
-                                 "/series/" + frames_->GetSeriesInstanceUid() +
-                                 "/instances/" + sopInstanceUid + "/metadata");
-        
-        loader_->ScheduleGetDicomWeb(
-          boost::make_shared<OrthancStone::LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
-          0, source_, uri, new SetDefaultWindowingCommand(GetSharedObserver()));
-      }
-    }
-  }
-
-  // This method is used when the layout of the HTML page changes,
-  // which does not trigger the "emscripten_set_resize_callback()"
-  void UpdateSize(bool fitContent)
-  {
-    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-    lock->GetCompositor().RefreshCanvasSize();
-
-    if (fitContent)
-    {
-      lock->GetCompositor().FitContent(lock->GetController().GetScene());
-    }
-
-    lock->Invalidate();
-  }
-
-  void AcquireObserver(IObserver* observer)
-  {  
-    observer_.reset(observer);
-  }
-
-  const std::string& GetCanvasId() const
-  {
-    assert(viewport_);
-    return viewport_->GetCanvasId();
-  }
-
-  void ChangeFrame(SeriesCursor::Action action)
-  {
-    if (cursor_.get() != NULL)
-    {
-      size_t previous = cursor_->GetCurrentIndex();
-      
-      cursor_->Apply(action);
-      
-      size_t current = cursor_->GetCurrentIndex();
-      if (previous != current)
-      {
-        DisplayCurrentFrame();
-      }
-    }
-  }
-
-  const FrameGeometry& GetCurrentFrameGeometry() const
-  {
-    return currentFrameGeometry_;
-  }
-
-  void UpdateReferenceLines(const std::list<const FrameGeometry*>& planes)
-  {
-    std::unique_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer);
-    
-    if (GetCurrentFrameGeometry().IsValid())
-    {
-      for (std::list<const FrameGeometry*>::const_iterator
-             it = planes.begin(); it != planes.end(); ++it)
-      {
-        assert(*it != NULL);
-        
-        double x1, y1, x2, y2;
-        if (GetCurrentFrameGeometry().Intersect(x1, y1, x2, y2, **it))
-        {
-          OrthancStone::PolylineSceneLayer::Chain chain;
-          chain.push_back(OrthancStone::ScenePoint2D(x1, y1));
-          chain.push_back(OrthancStone::ScenePoint2D(x2, y2));
-          layer->AddChain(chain, false, 0, 255, 0);
-        }
-      }
-    }
-    
-    {
-      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-
-      if (layer->GetChainsCount() == 0)
-      {
-        lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES);
-      }
-      else
-      {
-        lock->GetController().GetScene().SetLayer(LAYER_REFERENCE_LINES, layer.release());
-      }
-      
-      //lock->GetCompositor().Refresh(lock->GetController().GetScene());
-      lock->Invalidate();
-    }
-  }
-
-
-  void ClearReferenceLines()
-  {
-    {
-      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-      lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES);
-      lock->Invalidate();
-    }
-  }
-
-
-  void SetDefaultWindowing()
-  {
-    SetWindowing(defaultWindowingCenter_, defaultWindowingWidth_);
-  }
-
-  void SetWindowing(float windowingCenter,
-                    float windowingWidth)
-  {
-    windowingCenter_ = windowingCenter;
-    windowingWidth_ = windowingWidth;
-
-    {
-      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-
-      if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) &&
-          lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() ==
-          OrthancStone::ISceneLayer::Type_FloatTexture)
-      {
-        dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(
-          lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)).
-          SetCustomWindowing(windowingCenter_, windowingWidth_);
-        lock->Invalidate();
-      }
-    }
-  }
-
-  void Invert()
-  {
-    inverted_ = !inverted_;
-    
-    {
-      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-
-      if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) &&
-          lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() ==
-          OrthancStone::ISceneLayer::Type_FloatTexture)
-      {
-        OrthancStone::FloatTextureSceneLayer& layer = 
-          dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(
-            lock->GetController().GetScene().GetLayer(LAYER_TEXTURE));
-
-        // NB: Using "IsInverted()" instead of "inverted_" is for
-        // compatibility with MONOCHROME1 images
-        layer.SetInverted(!layer.IsInverted());
-        lock->Invalidate();
-      }
-    }
-  }
-};
-
-
-
-
-
-typedef std::map<std::string, boost::shared_ptr<ViewerViewport> >  Viewports;
-static Viewports allViewports_;
-static bool showReferenceLines_ = true;
-
-
-static void UpdateReferenceLines()
-{
-  if (showReferenceLines_)
-  {
-    std::list<const FrameGeometry*> planes;
-    
-    for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
-    {
-      assert(it->second != NULL);
-      planes.push_back(&it->second->GetCurrentFrameGeometry());
-    }
-
-    for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
-    {
-      assert(it->second != NULL);
-      it->second->UpdateReferenceLines(planes);
-    }
-  }
-  else
-  {
-    for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
-    {
-      assert(it->second != NULL);
-      it->second->ClearReferenceLines();
-    }
-  }
-}
-
-
-class WebAssemblyObserver : public ResourcesLoader::IObserver,
-                            public ViewerViewport::IObserver
-{
-public:
-  virtual void SignalResourcesLoaded() ORTHANC_OVERRIDE
-  {
-    DISPATCH_JAVASCRIPT_EVENT("ResourcesLoaded");
-  }
-
-  virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid,
-                                           const std::string& seriesInstanceUid) ORTHANC_OVERRIDE
-  {
-    EM_ASM({
-        const customEvent = document.createEvent("CustomEvent");
-        customEvent.initCustomEvent("ThumbnailLoaded", false, false,
-                                    { "studyInstanceUid" : UTF8ToString($0),
-                                        "seriesInstanceUid" : UTF8ToString($1) });
-        window.dispatchEvent(customEvent);
-      },
-      studyInstanceUid.c_str(),
-      seriesInstanceUid.c_str());
-  }
-
-  virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid,
-                                          const std::string& seriesInstanceUid) ORTHANC_OVERRIDE
-  {
-    EM_ASM({
-        const customEvent = document.createEvent("CustomEvent");
-        customEvent.initCustomEvent("MetadataLoaded", false, false,
-                                    { "studyInstanceUid" : UTF8ToString($0),
-                                        "seriesInstanceUid" : UTF8ToString($1) });
-        window.dispatchEvent(customEvent);
-      },
-      studyInstanceUid.c_str(),
-      seriesInstanceUid.c_str());
-  }
-
-  virtual void SignalFrameUpdated(const ViewerViewport& viewport,
-                                  size_t currentFrame,
-                                  size_t countFrames,
-                                  DisplayedFrameQuality quality) ORTHANC_OVERRIDE
-  {
-    EM_ASM({
-        const customEvent = document.createEvent("CustomEvent");
-        customEvent.initCustomEvent("FrameUpdated", false, false,
-                                    { "canvasId" : UTF8ToString($0),
-                                        "currentFrame" : $1,
-                                        "framesCount" : $2,
-                                        "quality" : $3 });
-        window.dispatchEvent(customEvent);
-      },
-      viewport.GetCanvasId().c_str(),
-      static_cast<int>(currentFrame),
-      static_cast<int>(countFrames),
-      quality);
-
-
-    UpdateReferenceLines();
-  };
-};
-
-
-
-static OrthancStone::DicomSource source_;
-static boost::shared_ptr<FramesCache> cache_;
-static boost::shared_ptr<OrthancStone::WebAssemblyLoadersContext> context_;
-static std::string stringBuffer_;
-
-
-
-static void FormatTags(std::string& target,
-                       const Orthanc::DicomMap& tags)
-{
-  Orthanc::DicomArray arr(tags);
-  Json::Value v = Json::objectValue;
-
-  for (size_t i = 0; i < arr.GetSize(); i++)
-  {
-    const Orthanc::DicomElement& element = arr.GetElement(i);
-    if (!element.GetValue().IsBinary() &&
-        !element.GetValue().IsNull())
-    {
-      v[element.GetTag().Format()] = element.GetValue().GetContent();
-    }
-  }
-
-  target = v.toStyledString();
-}
-
-
-static ResourcesLoader& GetResourcesLoader()
-{
-  static boost::shared_ptr<ResourcesLoader>  resourcesLoader_;
-
-  if (!resourcesLoader_)
-  {
-    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock());
-    resourcesLoader_ = ResourcesLoader::Create(*lock, source_);
-    resourcesLoader_->AcquireObserver(new WebAssemblyObserver);
-  }
-
-  return *resourcesLoader_;
-}
-
-
-static boost::shared_ptr<ViewerViewport> GetViewport(const std::string& canvas)
-{
-  Viewports::iterator found = allViewports_.find(canvas);
-  if (found == allViewports_.end())
-  {
-    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock());
-    boost::shared_ptr<ViewerViewport> viewport(ViewerViewport::Create(*lock, source_, canvas, cache_));
-    viewport->AcquireObserver(new WebAssemblyObserver);
-    allViewports_[canvas] = viewport;
-    return viewport;
-  }
-  else
-  {
-    return found->second;
-  }
-}
-
-
-extern "C"
-{
-  int main(int argc, char const *argv[]) 
-  {
-    printf("OK\n");
-    Orthanc::InitializeFramework("", true);
-    Orthanc::Logging::EnableInfoLevel(true);
-    //Orthanc::Logging::EnableTraceLevel(true);
-
-    context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
-    cache_.reset(new FramesCache);
-    
-    DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetOrthancRoot(const char* uri,
-                      int useRendered)
-  {
-    try
-    {
-      context_->SetLocalOrthanc(uri);  // For "source_.SetDicomWebThroughOrthancSource()"
-      source_.SetDicomWebSource(std::string(uri) + "/dicom-web");
-      source_.SetDicomWebRendered(useRendered != 0);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetDicomWebServer(const char* serverName,
-                         int hasRendered)
-  {
-    try
-    {
-      source_.SetDicomWebThroughOrthancSource(serverName);
-      source_.SetDicomWebRendered(hasRendered != 0);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  void FetchAllStudies()
-  {
-    try
-    {
-      GetResourcesLoader().FetchAllStudies();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  void FetchStudy(const char* studyInstanceUid)
-  {
-    try
-    {
-      GetResourcesLoader().FetchStudy(studyInstanceUid);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  void FetchSeries(const char* studyInstanceUid,
-                   const char* seriesInstanceUid)
-  {
-    try
-    {
-      GetResourcesLoader().FetchSeries(studyInstanceUid, seriesInstanceUid);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-  EMSCRIPTEN_KEEPALIVE
-  int GetStudiesCount()
-  {
-    try
-    {
-      return GetResourcesLoader().GetStudiesCount();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-    return 0;  // on exception
-  }
-  
-  EMSCRIPTEN_KEEPALIVE
-  int GetSeriesCount()
-  {
-    try
-    {
-      return GetResourcesLoader().GetSeriesCount();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-    return 0;  // on exception
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  const char* GetStringBuffer()
-  {
-    return stringBuffer_.c_str();
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  void LoadStudyTags(int i)
-  {
-    try
-    {
-      if (i < 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      
-      Orthanc::DicomMap dicom;
-      GetResourcesLoader().GetStudy(dicom, i);
-      FormatTags(stringBuffer_, dicom);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  void LoadSeriesTags(int i)
-  {
-    try
-    {
-      if (i < 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      
-      Orthanc::DicomMap dicom;
-      GetResourcesLoader().GetSeries(dicom, i);
-      FormatTags(stringBuffer_, dicom);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  int LoadSeriesThumbnail(const char* seriesInstanceUid)
-  {
-    try
-    {
-      std::string image, mime;
-      switch (GetResourcesLoader().GetSeriesThumbnail(image, mime, seriesInstanceUid))
-      {
-        case OrthancStone::SeriesThumbnailType_Image:
-          Orthanc::Toolbox::EncodeDataUriScheme(stringBuffer_, mime, image);
-          return ThumbnailType_Image;
-          
-        case OrthancStone::SeriesThumbnailType_Pdf:
-          return ThumbnailType_Pdf;
-          
-        case OrthancStone::SeriesThumbnailType_Video:
-          return ThumbnailType_Video;
-          
-        case OrthancStone::SeriesThumbnailType_NotLoaded:
-          return ThumbnailType_Loading;
-          
-        case OrthancStone::SeriesThumbnailType_Unsupported:
-          return ThumbnailType_NoPreview;
-
-        default:
-          return ThumbnailType_Unknown;
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-    return ThumbnailType_Unknown;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void SpeedUpFetchSeriesMetadata(const char* studyInstanceUid,
-                                  const char* seriesInstanceUid)
-  {
-    try
-    {
-      GetResourcesLoader().FetchSeriesMetadata(PRIORITY_HIGH, studyInstanceUid, seriesInstanceUid);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  int IsSeriesComplete(const char* seriesInstanceUid)
-  {
-    try
-    {
-      return GetResourcesLoader().IsSeriesComplete(seriesInstanceUid) ? 1 : 0;
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-    return 0;
-  }
-
-  EMSCRIPTEN_KEEPALIVE
-  int LoadSeriesInViewport(const char* canvas,
-                           const char* seriesInstanceUid)
-  {
-    try
-    {
-      std::unique_ptr<OrthancStone::SortedFrames> frames(new OrthancStone::SortedFrames);
-      
-      if (GetResourcesLoader().SortSeriesFrames(*frames, seriesInstanceUid))
-      {
-        GetViewport(canvas)->SetFrames(frames.release());
-        return 1;
-      }
-      else
-      {
-        return 0;
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-    return 0;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void AllViewportsUpdateSize(int fitContent)
-  {
-    try
-    {
-      for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
-      {
-        assert(it->second != NULL);
-        it->second->UpdateSize(fitContent != 0);
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void DecrementFrame(const char* canvas,
-                      int fitContent)
-  {
-    try
-    {
-      GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Minus);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void IncrementFrame(const char* canvas,
-                      int fitContent)
-  {
-    try
-    {
-      GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Plus);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }  
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void ShowReferenceLines(int show)
-  {
-    try
-    {
-      showReferenceLines_ = (show != 0);
-      UpdateReferenceLines();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }  
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetDefaultWindowing(const char* canvas)
-  {
-    try
-    {
-      GetViewport(canvas)->SetDefaultWindowing();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }  
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetWindowing(const char* canvas,
-                    int center,
-                    int width)
-  {
-    try
-    {
-      GetViewport(canvas)->SetWindowing(center, width);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }  
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void InvertContrast(const char* canvas)
-  {
-    try
-    {
-      GetViewport(canvas)->Invert();
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }  
-}
--- a/StoneWebViewer/WebAssembly/docker-build.sh	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-set -ex
-
-IMAGE=jodogne/wasm-builder:1.39.17-upstream
-#IMAGE=wasm-builder
-
-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}/wasm-binaries
-
-docker run -t ${DOCKER_FLAGS} --rm \
-    --user $(id -u):$(id -g) \
-    -v ${ROOT_DIR}:/source:ro \
-    -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \
-    bash /source/StoneWebViewer/WebAssembly/docker-internal.sh $1
-
-ls -lR ${ROOT_DIR}/wasm-binaries/StoneWebViewer/
--- a/StoneWebViewer/WebAssembly/docker-internal.sh	Tue Aug 11 12:47:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#!/bin/bash
-set -ex
-
-source /opt/emsdk/emsdk_env.sh
-
-# Use a folder that is writeable by non-root users for the Emscripten cache
-export EM_CACHE=/tmp/emscripten-cache
-
-# Get the Orthanc framework
-cd /tmp/
-hg clone https://hg.orthanc-server.com/orthanc/
-
-# Make a copy of the read-only folder containing the source code into
-# a writeable folder, because of "DownloadPackage.cmake" that writes
-# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt"
-cd /source
-hg clone /source /tmp/source-writeable
-
-mkdir /tmp/build
-cd /tmp/build
-
-cmake /tmp/source-writeable/StoneWebViewer/WebAssembly \
-      -DCMAKE_BUILD_TYPE=$1 \
-      -DCMAKE_INSTALL_PREFIX=/target/StoneWebViewer \
-      -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-      -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \
-      -DSTATIC_BUILD=ON \
-      -G Ninja
-
-ninja -j2 install