changeset 1586:b5417e377636

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 22 Oct 2020 16:17:10 +0200
parents 94edbfa64c97
children a1405ab3a91c
files Applications/Resources/Colormaps/GenerateColormaps.py Applications/Resources/Colormaps/blue.lut Applications/Resources/Colormaps/green.lut Applications/Resources/Colormaps/hot.lut Applications/Resources/Colormaps/jet.lut Applications/Resources/Colormaps/red.lut Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.h Applications/Resources/Graveyard/Messaging/IOrthancConnection.h Applications/Resources/Graveyard/ReferenceLineFactory.cpp Applications/Resources/Graveyard/ReferenceLineFactory.h Applications/Resources/Graveyard/Threading/BinarySemaphore.cpp Applications/Resources/Graveyard/Threading/BinarySemaphore.h Applications/Resources/Graveyard/Threading/IThreadSafety.h Applications/Resources/Graveyard/Threading/SdlBuffering.cpp Applications/Resources/Graveyard/Threading/SdlBuffering.h Applications/Resources/Graveyard/Threading/SharedValue.h Applications/Resources/Graveyard/Toolbox/DicomDataset.cpp Applications/Resources/Graveyard/Toolbox/DicomDataset.h Applications/Resources/LinuxStandardBaseUic.py Applications/Samples/Sdl/CMakeLists.txt Applications/Samples/WebAssembly/CMakeLists.txt OrthancStone/Docs/Conventions.txt OrthancStone/Docs/stone-object-model-reference.md OrthancStone/Resources/CMake/LinuxStandardBaseUic.py OrthancStone/Resources/Colormaps/GenerateColormaps.py OrthancStone/Resources/Colormaps/blue.lut OrthancStone/Resources/Colormaps/green.lut OrthancStone/Resources/Colormaps/hot.lut OrthancStone/Resources/Colormaps/jet.lut OrthancStone/Resources/Colormaps/red.lut OrthancStone/Resources/Documentation/Conventions.txt OrthancStone/Resources/Documentation/stone-object-model-reference.md OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.h OrthancStone/Resources/Graveyard/Messaging/IOrthancConnection.h OrthancStone/Resources/Graveyard/ReferenceLineFactory.cpp OrthancStone/Resources/Graveyard/ReferenceLineFactory.h OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.cpp OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.h OrthancStone/Resources/Graveyard/Threading/IThreadSafety.h OrthancStone/Resources/Graveyard/Threading/SdlBuffering.cpp OrthancStone/Resources/Graveyard/Threading/SdlBuffering.h OrthancStone/Resources/Graveyard/Threading/SharedValue.h OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.cpp OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.h
diffstat 46 files changed, 1790 insertions(+), 1790 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Colormaps/GenerateColormaps.py	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import array
+import matplotlib.pyplot as plt
+
+def GenerateColormap(name):
+    colormap = []
+
+    for gray in range(256):
+        if name == 'red':
+            color = (gray / 255.0, 0, 0)
+        elif name == 'green':
+            color = (0, gray / 255.0, 0)
+        elif name == 'blue':
+            color = (0, 0, gray / 255.0)
+        else:
+            color = plt.get_cmap(name) (gray)
+
+        colormap += map(lambda k: int(round(color[k] * 255)), range(3))
+
+    colormap[0] = 0
+    colormap[1] = 0
+    colormap[2] = 0
+
+    return array.array('B', colormap).tostring()
+
+
+for name in [ 
+        'hot', 
+        'jet', 
+        'blue',
+        'green',
+        'red',
+]:
+    with open('%s.lut' % name, 'w') as f:
+        f.write(GenerateColormap(name))
Binary file Applications/Resources/Colormaps/blue.lut has changed
Binary file Applications/Resources/Colormaps/green.lut has changed
Binary file Applications/Resources/Colormaps/hot.lut has changed
Binary file Applications/Resources/Colormaps/jet.lut has changed
Binary file Applications/Resources/Colormaps/red.lut has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,72 @@
+/**
+ * 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 "CurlOrthancConnection.h"
+
+#if ORTHANC_ENABLE_CURL == 1
+
+#include "../../Resources/Orthanc/Core/HttpClient.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  void CurlOrthancConnection::RestApiGet(std::string& result,
+                                         const std::string& uri)
+  {
+    /**
+     * TODO: This function sometimes crashes if compiled with
+     * MinGW-W64 (32 bit) in Release mode, on Windows XP. Introducing
+     * a mutex here fixes the issue. Not sure of what is the
+     * culprit. Maybe a bug in a old version of MinGW?
+     **/
+
+    Orthanc::HttpClient client(parameters_, uri);
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client.SetRedirectionFollowed(false);  
+   
+    if (!client.Apply(result))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+
+  void CurlOrthancConnection::RestApiPost(std::string& result,
+                                          const std::string& uri,
+                                          const std::string& body)
+  {
+    Orthanc::HttpClient client(parameters_, uri);
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client.SetRedirectionFollowed(false);  
+
+    client.SetBody(body);
+    client.SetMethod(Orthanc::HttpMethod_Post);
+   
+    if (!client.Apply(result))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,57 @@
+/**
+ * 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 "IOrthancConnection.h"
+
+#if ORTHANC_ENABLE_CURL == 1
+
+#include "../../Resources/Orthanc/Core/WebServiceParameters.h"
+
+namespace OrthancStone
+{
+  class CurlOrthancConnection : public IOrthancConnection
+  {
+  private:
+    Orthanc::WebServiceParameters  parameters_;
+
+  public:
+    CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) :
+      parameters_(parameters)
+    {
+    }
+
+    const Orthanc::WebServiceParameters& GetParameters() const
+    {
+      return parameters_;
+    }
+
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri);
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body);
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Messaging/IOrthancConnection.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,41 @@
+/**
+ * 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 "../Toolbox/IThreadSafety.h"
+
+#include <string>
+
+namespace OrthancStone
+{
+  // Derived classes must be thread-safe
+  class IOrthancConnection : public IThreadSafe
+  {
+  public:
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri) = 0;
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/ReferenceLineFactory.cpp	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,137 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-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 "ReferenceLineFactory.h"
+
+#include "LineLayerRenderer.h"
+
+namespace OrthancStone
+{
+  ReferenceLineFactory::ReferenceLineFactory(SliceViewerWidget& owner,
+                                             SliceViewerWidget& sibling) :
+    owner_(owner),
+    sibling_(sibling),
+    hasLayerIndex_(false)
+  {
+    style_.SetColor(0, 255, 0);
+    slice_ = sibling.GetSlice();
+    sibling_.Register(*this);
+  }
+
+
+  void ReferenceLineFactory::NotifySliceContentChange(const SliceViewerWidget& source,
+                                               const SliceGeometry& slice)
+  {
+    if (&source == &sibling_)
+    {
+      SetSlice(slice);
+    }
+  }
+
+
+  void ReferenceLineFactory::SetLayerIndex(size_t layerIndex)
+  {
+    hasLayerIndex_ = true;
+    layerIndex_ = layerIndex;
+  }
+
+
+  void ReferenceLineFactory::SetStyle(const RenderStyle& style)
+  {
+    style_ = style;
+  }
+
+
+  RenderStyle ReferenceLineFactory::GetRenderStyle()
+  {
+    return style_;
+  }
+
+
+  void ReferenceLineFactory::SetSlice(const SliceGeometry& slice)
+  {
+    slice_ = slice;
+
+    if (hasLayerIndex_)
+    {
+      owner_.InvalidateLayer(layerIndex_);
+    }
+  }
+
+
+  ILayerRenderer* ReferenceLineFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    Vector p, d;
+
+    // Compute the line of intersection between the two slices
+    if (!GeometryToolbox::IntersectTwoPlanes(p, d, 
+                                             slice_.GetOrigin(), slice_.GetNormal(),
+                                             viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
+    {
+      // The two slice are parallel, don't try and display the intersection
+      return NULL;
+    }
+
+    double x1, y1, x2, y2;
+    viewportSlice.ProjectPoint(x1, y1, p);
+    viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
+
+    double sx1, sy1, sx2, sy2;
+    owner_.GetView().GetSceneExtent(sx1, sy1, sx2, sy2);
+        
+    if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, 
+                                             x1, y1, x2, y2,
+                                             sx1, sy1, sx2, sy2))
+    {
+      std::unique_ptr<ILayerRenderer> layer(new LineLayerRenderer(x1, y1, x2, y2));
+      layer->SetLayerStyle(style_);
+      return layer.release();
+    }
+    else
+    {
+      // Parallel slices
+      return NULL;
+    }
+  }
+
+
+  ISliceableVolume& ReferenceLineFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void ReferenceLineFactory::Configure(SliceViewerWidget& a,
+                                       SliceViewerWidget& b)
+  {
+    {
+      size_t layerIndex;
+      ILayerRendererFactory& factory = a.AddLayer(layerIndex, new ReferenceLineFactory(a, b));
+      dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex);
+    }
+
+    {
+      size_t layerIndex;
+      ILayerRendererFactory& factory = b.AddLayer(layerIndex, new ReferenceLineFactory(b, a));
+      dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/ReferenceLineFactory.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,77 @@
+/**
+ * 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 "../Widgets/SliceViewerWidget.h"
+
+namespace OrthancStone
+{
+  class ReferenceLineFactory : 
+    public ILayerRendererFactory,
+    public SliceViewerWidget::ISliceObserver
+  {
+  private:
+    SliceViewerWidget&   owner_;
+    SliceViewerWidget&   sibling_;
+    SliceGeometry        slice_;
+    RenderStyle          style_;
+    bool                 hasLayerIndex_;
+    size_t               layerIndex_;
+
+      
+  public:
+    ReferenceLineFactory(SliceViewerWidget& owner,
+                         SliceViewerWidget& sibling);
+
+    virtual void NotifySliceContentChange(const SliceViewerWidget& source,
+                                   const SliceGeometry& slice);
+
+    void SetLayerIndex(size_t layerIndex);
+
+    void SetStyle(const RenderStyle& style);
+
+    RenderStyle GetRenderStyle();
+
+    void SetSlice(const SliceGeometry& slice);
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& viewportSlice)
+    {
+      return false;
+    }
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+
+    static void Configure(SliceViewerWidget& a,
+                          SliceViewerWidget& b);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Threading/BinarySemaphore.cpp	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,50 @@
+/**
+ * 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 "BinarySemaphore.h"
+
+namespace OrthancStone
+{
+  BinarySemaphore::BinarySemaphore() : 
+    proceed_(false)
+  {
+  }
+
+  void BinarySemaphore::Signal()
+  {
+    //boost::mutex::scoped_lock lock(mutex_);
+
+    proceed_ = true;
+    condition_.notify_one(); 
+  }
+
+  void BinarySemaphore::Wait()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (!proceed_)
+    {
+      condition_.wait(lock);
+    }
+
+    proceed_ = false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Threading/BinarySemaphore.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,43 @@
+/**
+ * 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 <boost/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+
+namespace OrthancStone
+{
+  class BinarySemaphore : public boost::noncopyable
+  {
+  private:
+    bool proceed_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit BinarySemaphore();
+
+    void Signal();
+
+    void Wait();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Threading/IThreadSafety.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,57 @@
+/**
+ * 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 <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  /**
+   * Dummy interface to explicitely tag the interfaces whose derived
+   * class must be thread-safe. The different methods of such classes
+   * might be simlultaneously invoked by several threads, and should
+   * be properly protected by mutexes.
+   **/
+  class IThreadSafe : public boost::noncopyable
+  {
+  public:
+    virtual ~IThreadSafe()
+    {
+    }
+  };
+
+
+  /**
+   * Dummy interface to explicitely tag the interfaces that are NOT
+   * expected to be thread-safe. The Orthanc Stone framework ensures
+   * that at most one method of such classes will be invoked at a
+   * given time. Such classes are automatically protected by the
+   * Orthanc Stone framework wherever required.
+   **/
+  class IThreadUnsafe : public boost::noncopyable
+  {
+  public:
+    virtual ~IThreadUnsafe()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Threading/SdlBuffering.cpp	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,134 @@
+/**
+ * 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 "SdlBuffering.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  SdlBuffering::SdlBuffering() :
+    sdlSurface_(NULL),
+    pendingFrame_(false)
+  {
+  }
+
+
+  SdlBuffering::~SdlBuffering()
+  {
+    if (sdlSurface_)
+    {
+      SDL_FreeSurface(sdlSurface_);
+    }
+  }
+
+
+  void SdlBuffering::SetSize(unsigned int width,
+                             unsigned int height,
+                             IViewport& viewport)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    viewport.SetSize(width, height);
+
+    if (offscreenSurface_.get() == NULL ||
+        offscreenSurface_->GetWidth() != width ||
+        offscreenSurface_->GetHeight() != height)
+    {
+      offscreenSurface_.reset(new CairoSurface(width, height));
+    }
+
+    if (onscreenSurface_.get() == NULL ||
+        onscreenSurface_->GetWidth() != width ||
+        onscreenSurface_->GetHeight() != height)
+    {
+      onscreenSurface_.reset(new CairoSurface(width, height));
+
+      // TODO Big endian?
+      static const uint32_t rmask = 0x00ff0000;
+      static const uint32_t gmask = 0x0000ff00;
+      static const uint32_t bmask = 0x000000ff;
+
+      if (sdlSurface_)
+      {
+        SDL_FreeSurface(sdlSurface_);
+      }
+
+      sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32,
+                                             onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0);
+      if (!sdlSurface_)
+      {
+        LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }    
+    }
+
+    pendingFrame_ = false;
+  }
+
+
+  bool SdlBuffering::RenderOffscreen(IViewport& viewport)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (offscreenSurface_.get() == NULL)
+    {
+      return false;
+    }
+
+    Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor();
+
+    if (viewport.Render(target) &&
+        !pendingFrame_)
+    {
+      pendingFrame_ = true;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void SdlBuffering::SwapToScreen(SdlWindow& window)
+  {
+    if (!pendingFrame_ ||
+        offscreenSurface_.get() == NULL ||
+        onscreenSurface_.get() == NULL)
+    {
+      return;
+    }
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      onscreenSurface_->Copy(*offscreenSurface_);
+    }
+    
+    window.Render(sdlSurface_);
+    pendingFrame_ = false;
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Threading/SdlBuffering.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,60 @@
+/**
+ * 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
+
+#include "SdlWindow.h"
+#include "../../Framework/Viewport/CairoSurface.h"
+#include "../../Framework/Viewport/IViewport.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  class SdlBuffering : public boost::noncopyable
+  {
+  private:
+    boost::mutex                 mutex_;
+    std::unique_ptr<CairoSurface>  offscreenSurface_;
+    std::unique_ptr<CairoSurface>  onscreenSurface_;
+    SDL_Surface*                 sdlSurface_;
+    bool                         pendingFrame_;
+
+  public:
+    SdlBuffering();
+
+    ~SdlBuffering();
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 IViewport& viewport);
+
+    // Returns "true" if a new refresh of the display should be
+    // triggered afterwards
+    bool RenderOffscreen(IViewport& viewport);
+
+    void SwapToScreen(SdlWindow& window);
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Threading/SharedValue.h	Thu Oct 22 16:17:10 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 <boost/noncopyable.hpp>
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  // A value that is protected by a mutex, in order to be shared by
+  // multiple threads
+  template <typename T>
+  class SharedValue : public boost::noncopyable
+  {
+  private:
+    boost::mutex   mutex_;
+    T              value_;
+    
+  public:
+    class Locker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      T&                         value_;
+
+    public:
+      Locker(SharedValue& shared) :
+        lock_(shared.mutex_),
+        value_(shared.value_)
+      {
+      }
+
+      T& GetValue() const
+      {
+        return value_;
+      }
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Toolbox/DicomDataset.cpp	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,304 @@
+/**
+ * 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 "DicomDataset.h"
+
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/Toolbox.h"
+
+#include <boost/lexical_cast.hpp>
+#include <json/value.h>
+#include <json/reader.h>
+
+namespace OrthancStone
+{
+  static uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+
+  static uint16_t GetHexadecimalValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+
+  static DicomDataset::Tag ParseTag(const std::string& tag)
+  {
+    if (tag.size() == 9 &&
+        isxdigit(tag[0]) &&
+        isxdigit(tag[1]) &&
+        isxdigit(tag[2]) &&
+        isxdigit(tag[3]) &&
+        (tag[4] == '-' || tag[4] == ',') &&
+        isxdigit(tag[5]) &&
+        isxdigit(tag[6]) &&
+        isxdigit(tag[7]) &&
+        isxdigit(tag[8]))        
+    {
+      uint16_t group = GetHexadecimalValue(tag.c_str());
+      uint16_t element = GetHexadecimalValue(tag.c_str() + 5);
+      return std::make_pair(group, element);
+    }
+    else if (tag.size() == 8 &&
+             isxdigit(tag[0]) &&
+             isxdigit(tag[1]) &&
+             isxdigit(tag[2]) &&
+             isxdigit(tag[3]) &&
+             isxdigit(tag[4]) &&
+             isxdigit(tag[5]) &&
+             isxdigit(tag[6]) &&
+             isxdigit(tag[7]))        
+    {
+      uint16_t group = GetHexadecimalValue(tag.c_str());
+      uint16_t element = GetHexadecimalValue(tag.c_str() + 4);
+      return std::make_pair(group, element);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+    }
+  }
+
+  void DicomDataset::Parse(const std::string& content)
+  {
+    Json::Value json;
+    Json::Reader reader;
+    if (!reader.parse(content, json))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+    }
+
+    Parse(json);
+  }
+
+
+  void DicomDataset::Parse(const Json::Value& content)
+  {
+    if (content.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+    }
+
+    Json::Value::Members members = content.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      Tag tag = ParseTag(members[i]);
+
+      const Json::Value& item = content[members[i]];
+
+      if (item.type() != Json::objectValue ||
+          !item.isMember("Type") ||
+          !item.isMember("Value") ||
+          !item.isMember("Name") ||
+          item["Type"].type() != Json::stringValue ||
+          item["Name"].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+      }
+
+      if (item["Type"].asString() == "String")
+      {
+        if (item["Value"].type() == Json::stringValue)
+        {
+          values_[tag] = item["Value"].asString();
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
+        }
+      }
+    }
+  }
+
+
+  DicomDataset::DicomDataset(OrthancPlugins::IOrthancConnection& orthanc,
+                             const std::string& instanceId)
+  {
+    std::string content;
+    orthanc.RestApiGet(content, "/instances/" + instanceId + "/tags");
+
+    Parse(content);
+  }
+
+
+  std::string DicomDataset::GetStringValue(const Tag& tag) const
+  {
+    Values::const_iterator it = values_.find(tag);
+
+    if (it == values_.end())
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not set in a DICOM dataset";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  std::string DicomDataset::GetStringValue(const Tag& tag,
+                                           const std::string& defaultValue) const
+  {
+    Values::const_iterator it = values_.find(tag);
+
+    if (it == values_.end())
+    {
+      return defaultValue;
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  float DicomDataset::GetFloatValue(const Tag& tag) const
+  {
+    try 
+    {
+      return boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not a float";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  double DicomDataset::GetDoubleValue(const Tag& tag) const
+  {
+    try 
+    {
+      return boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not a float";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  int DicomDataset::GetIntegerValue(const Tag& tag) const
+  {
+    try 
+    {
+      return boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not an integer";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  unsigned int DicomDataset::GetUnsignedIntegerValue(const Tag& tag) const
+  {
+    int v = GetIntegerValue(tag);
+
+    if (v < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      return static_cast<unsigned int>(v);
+    }
+  }
+
+
+  void DicomDataset::GetVectorValue(Vector& vector, 
+                                    const Tag& tag) const
+  {
+    if (!GeometryToolbox::ParseVector(vector, Orthanc::Toolbox::StripSpaces(GetStringValue(tag))))
+    {
+      LOG(ERROR) << "Trying to access a DICOM tag that is not a vector";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void DicomDataset::GetVectorValue(Vector& vector, 
+                                    const Tag& tag,
+                                    size_t expectedSize) const
+  {
+    GetVectorValue(vector, tag);
+
+    if (vector.size() != expectedSize)
+    {
+      LOG(ERROR) << "A vector in a DICOM tag has a bad size";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void DicomDataset::Print() const
+  {
+    for (Values::const_iterator it = values_.begin(); it != values_.end(); ++it)
+    {
+      printf("%04x,%04x = [%s]\n", it->first.first, it->first.second, it->second.c_str());
+    }
+    printf("\n");
+  }
+
+
+  bool DicomDataset::IsGrayscale() const
+  {
+    std::string photometric = Orthanc::Toolbox::StripSpaces(GetStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION));
+
+    return (photometric == "MONOCHROME1" ||
+            photometric == "MONOCHROME2");
+  }
+
+
+  void DicomDataset::GetPixelSpacing(double& spacingX,
+                                     double& spacingY) const
+  {
+    if (HasTag(DICOM_TAG_PIXEL_SPACING))
+    {
+      Vector spacing;
+      GetVectorValue(spacing, DICOM_TAG_PIXEL_SPACING, 2);
+      spacingX = spacing[0];
+      spacingY = spacing[1];
+    }
+    else
+    {
+      spacingX = 1.0;
+      spacingY = 1.0;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/Graveyard/Toolbox/DicomDataset.h	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,109 @@
+/**
+ * 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 "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
+
+#include <map>
+#include <stdint.h>
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  // This class is NOT thread-safe
+  // This is a lightweight alternative to Orthanc::DicomMap
+  class DicomDataset : public boost::noncopyable
+  {
+  public:
+    typedef std::pair<uint16_t, uint16_t>  Tag;
+
+  private:
+    typedef std::map<Tag, std::string>  Values;
+
+    Values  values_;
+
+    void Parse(const std::string& content);
+
+    void Parse(const Json::Value& content);
+
+  public:
+    DicomDataset(const std::string& content)
+    {
+      Parse(content);
+    }
+
+    DicomDataset(const Json::Value& content)
+    {
+      Parse(content);
+    }
+
+    DicomDataset(OrthancPlugins::IOrthancConnection& orthanc,
+                 const std::string& instanceId);
+
+    bool HasTag(const Tag& tag) const
+    {
+      return values_.find(tag) != values_.end();
+    }
+
+    std::string GetStringValue(const Tag& tag) const;
+
+    std::string GetStringValue(const Tag& tag,
+                               const std::string& defaultValue) const;
+
+    float GetFloatValue(const Tag& tag) const;
+
+    double GetDoubleValue(const Tag& tag) const;
+
+    int GetIntegerValue(const Tag& tag) const;
+
+    unsigned int GetUnsignedIntegerValue(const Tag& tag) const;
+
+    void GetVectorValue(Vector& vector, 
+                        const Tag& tag,
+                        size_t expectedSize) const;
+
+    void GetVectorValue(Vector& vector, 
+                        const Tag& tag) const;
+
+    void Print() const;
+
+    bool IsGrayscale() const;
+
+    void GetPixelSpacing(double& spacingX,
+                         double& spacingY) const;
+  };
+
+
+  static const DicomDataset::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomDataset::Tag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
+  static const DicomDataset::Tag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
+  static const DicomDataset::Tag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomDataset::Tag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomDataset::Tag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030);
+  static const DicomDataset::Tag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052);
+  static const DicomDataset::Tag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053);
+  static const DicomDataset::Tag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomDataset::Tag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050);
+  static const DicomDataset::Tag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
+  static const DicomDataset::Tag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
+  static const DicomDataset::Tag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Resources/LinuxStandardBaseUic.py	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+import subprocess
+import sys
+
+if len(sys.argv) <= 1:
+    sys.stderr.write('Please provide arguments for uic\n')
+    sys.exit(-1)
+
+path = ''
+pos = 1
+while pos < len(sys.argv):
+    if sys.argv[pos].startswith('-'):
+        pos += 2
+    else:
+        path = sys.argv[pos]
+        break
+
+if len(path) == 0:
+    sys.stderr.write('Unable to find the input file in the arguments to uic\n')
+    sys.exit(-1)
+
+with open(path, 'r') as f:
+    lines = f.read().split('\n')
+    if (len(lines) > 1 and
+        lines[0].startswith('<?')):
+        content = '\n'.join(lines[1:])
+    else:
+        content = '\n'.join(lines)
+        
+# Remove the source file from the arguments
+args = sys.argv[1:pos] + sys.argv[pos+1:]
+
+p = subprocess.Popen([ '/opt/lsb/bin/uic' ] + args,
+                     stdin = subprocess.PIPE)
+p.communicate(input = content)
--- a/Applications/Samples/Sdl/CMakeLists.txt	Wed Oct 21 18:00:34 2020 +0200
+++ b/Applications/Samples/Sdl/CMakeLists.txt	Thu Oct 22 16:17:10 2020 +0200
@@ -40,7 +40,7 @@
   "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
 
 EmbedResources(
-  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut
+  COLORMAP_HOT ${CMAKE_SOURCE_DIR}/../../Resources/Colormaps/hot.lut
   UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
   )
 
--- a/Applications/Samples/WebAssembly/CMakeLists.txt	Wed Oct 21 18:00:34 2020 +0200
+++ b/Applications/Samples/WebAssembly/CMakeLists.txt	Thu Oct 22 16:17:10 2020 +0200
@@ -52,7 +52,7 @@
   "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83")
 
 EmbedResources(
-  COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut  
+  COLORMAP_HOT ${CMAKE_SOURCE_DIR}/../../Resources/Colormaps/hot.lut
   UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
   )
 
--- a/OrthancStone/Docs/Conventions.txt	Wed Oct 21 18:00:34 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/Docs/stone-object-model-reference.md	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,429 +0,0 @@
-## Scene2D and viewport-related object reference
-
-### `Scene2D` 
-
-Represents a collection of layers that display 2D data.
-
-These layers must implement `ISceneLayer`
-
-The layers must be created externally and set to a specific Z-order index 
-with the `SetLayer` method.
-
-The `Scene2D` object merely acts as a layer container. It has no rendering 
-or layer creation facility on its own.
-
-The `Scene2D` contains an `AffineTransform2D` structure that defines how 
-the various layer item coordinates are transformed before being displayed 
-on the viewport (aka canvas)
-
-It is up to each layer type-specific renderer to choose how this transformation
-is used. See the various kinds of layer below for more details.
-
-Examining the `Scene2D` contents can be done either by implementing the 
-`Scene2D::IVisitor` interface and calling `Apply(IVisitor& visitor)` or by 
-iterating between `GetMinDepth()` and `GetMaxDepth()` and calling the 
-`ISceneLayer& GetLayer(int depth)` getter.
-
-### `ISceneLayer` 
-
-Interface that must be implemented by `Scene2D` layers. This is a closed list
-that, as of 2020-03, contains:
-
-```
-  Type_InfoPanel,
-  Type_ColorTexture,
-  Type_Polyline,
-  Type_Text,
-  Type_FloatTexture,
-  Type_LookupTableTexture
-```
-
-Please note that this interface mandates the implementation of a `GetRevision`
-method returning an `uint64_t`. 
-
-The idea is that when a model gets converted to a set of `ISceneLayer` 
-instances, changes in the model that result in changes to the layers must 
-increase the revision number of these layers.
-
-That allows the rendering process to safely assume that a given layers whose
-revision does not change hasn't been modified (this helps with caching).
-
-Every mutable method in `ISceneLayer` instances that possibly change the visual
-representation of an `ISceneLayer` must increase this revision number.
-
-### Implementation: `FloatTextureSceneLayer`
-
-Layer that renders an `Orthanc::ImageAccessor` object that must be convertible
-to `Float32` image.
-
-The constructor only uses the image accessor to perform a copy. It can safely 
-be deleted afterwards.
-
-The input values are mapped to the output values by taking into account various
-properties that can be modified with:
-
-- `SetWindowing`: uses windowing presets like "bone" or "lung"
-- `SetCustomWindowing`: with manual window center and width
-- `SetInverted`: toggles black <-> white inversion after windowing
-- `SetApplyLog`: uses a non-linear response curve described in 
-  https://theailearner.com/2019/01/01/log-transformation/ that expands contrast
-  in dark areas while compressing contrast in bright ones. This is **not** 
-  implemented in the OpenGL renderer!
-
-The corresponding renderers are `OpenGLFloatTextureRenderer` and 
-`CairoFloatTextureRenderer`. The scene transformation is applied during
-rendering.
-
-### Implementation: `ColorTextureSceneLayer`
-
-Layer that renders an `Orthanc::ImageAccessor` object an RGBA image (alpha must
-be premultiplied). 
-
-The constructor only uses the image accessor to perform a copy. It can safely 
-be deleted afterwards.
-
-The corresponding renderers are `OpenGLColorTextureRenderer` and 
-`CairoColorTextureRenderer`. The scene transformation is applied during
-rendering.
-
-### Implementation: `LookupTableTextureSceneLayer`
-
-Layer that renders an `Orthanc::ImageAccessor` object that must be convertible
-to `Float32` image.
-
-The constructor only uses the image accessor to perform a copy. It can safely 
-be deleted afterwards.
-
-The final on-screen color of each pixel is determined by passing the input 
-`Float32` value through a 256-entry look-up table (LUT) that can be passed as 
-an array of either 256 x 3 bytes (for opaque RGB colors) or 256 x 4 bytes (for
-RGBA pixels). The LUT is not specified at construction time, but with 
-calls to `SetLookupTable` or `SetLookupTableGrayscale` (that fills the LUT 
-with a gradient from black to white, fully opaque)
-
-The range of input values that is mapped to the entirety of the LUT is, by
-default, the full image range, but can be customized with `SetRange`.
-
-The corresponding renderers are `OpenGLLookupTableTextureRenderer` and
-`CairoLookupTableTextureRenderer`. The scene transformation is applied during
-rendering.
-
-### Implementation: `PolylineSceneLayer`
-
-Layer that renders vector-based polygonal lines. 
-
-Polylines can be added with the `AddChain` method, that accepts a `Chain`, that
-is a typedef to `std::vector<ScenePoint2D>`, a flag to specify whether the 
-chain must be automatically close (last point of the vector connected to the
-first one) and the chain color (a `Color` structure).
-
-Please note that the line thickness is, contrary to the color, specified 
-per-chain but rather per-layer. 
-
-If you need multiple line thicknesses, multiple `PolylineSceneLayer` must be
-created.
-
-The corresponding renderers are `OpenGLAdvancedPolylineRenderer` and
-`CairoPolylineRenderer`. The scene transformation is applied during
-rendering.
-
-### Implementation: `TextSceneLayer`
-
-This layers renders a paragraph of text. 
-
-The inputs to the layer can be changed after creation and are:
-- The text iself, supplied as an UTF-8 encoded string in `SetText`
-- The font used for rendering, set by `SetFontIndex`.
-- The text anchoring, through `SetAnchor`: the text can be anchored to 
-  various positions, such as top lef, center, bottom center,... These 
-  various anchors are part of the `BitmapAnchor` enumeration.
-- The text position, relative to its anchor, through `SetPosition`.
-
-The font is supplied as an index. This is an index in the set of fonts 
-that has been registered in the viewport compositor. The following code 
-shows how to set such a font:
-
-```
-std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_.Lock());
-lock->GetCompositor().SetFont(0, 
-                              Orthanc::EmbeddedResources::UBUNTU_FONT, 
-                              32, Orthanc::Encoding_Latin1);
-// where 32 is the font size in pixels
-```
-
-This call uses the embedded `UBUNTU_FONT` resource that has been defined in
-the `CMakeLists.txt` file with:
-
-```
-EmbedResources(
-  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
-)
-```
-
-Please note that you must supply a font: there is no default font provided by
-the OpenGL or Cairo compositors.
-
-The corresponding renderers are `OpenGLTextRenderer` and
-`CairoTextRenderer`. The scene transformation is not applied during rendering,
-because the text anchoring, position and scaling are computed relative to the
-viewport/canvas.
-
-### Implementation: `InfoPanelSceneLayer`
-
-This layer is designed to display an image, supplied through an 
-`Orthanc::ImageAccessor` reference (only used at construction time).
-
-The image is not transformed according to the normal layer transformation but 
-is rather positioned relative to the canvas, with the same mechanism as the
-`TextSceneLayer` described above.
-
-The image position is specified with the sole means of the `SetAnchor` method.
-
-The corresponding renderers are `OpenGLInfoPanelRenderer` and 
-`CairoInfoPanelRenderer`.
-
-### `IViewport`
-
-https://bitbucket.org/sjodogne/orthanc-stone/src/broker/Framework/Viewport/IViewport.h
-
-(**not** the one in `Deprecated`)
-- Implemented by classes that:
-  - manage the on-screen display of a `Scene2D` trough a compositor.
-  - Own the `ICompositor` object that performs the rendering. 
-  - Own the `Scene2D` (TODO: currently through `ViewportController` --> `Scene2D`)
-  - Provide a `Lock` method that returns a RAII, that must be kept alive when 
-    modifying the underlying objects (controller, compositor, scene), but not 
-    longer.
-
-#### Implementation: `SdlOpenGLViewport`
-- Implementation of a viewport rendered on a SDL window, that uses OpenGL for 
-  rendering.
-- Instantiating this object creates an SDL window. Automatic scaling for hiDPI
-  displays can be toggled on or off. 
-
-#### Implementation: `WebGLViewport`
-- Implementation of a viewport rendered on a DOM canvas, that uses OpenGL for 
-  rendering.
-- Contrary to the SDL OpenGL viewport, the canvas must already be existing 
-  when the ctor is called.
-
-### `ICompositor`
-The interface providing a rendering service for `Scene2D` objects. 
-
-**Subclasses:** `CairoCompositor`, `OpenGLCompositor`
- 
-You do not need to create compositor instances. They are created for you when
-instantiating a viewport.
-
-### `ViewportController`
-This concrete class is instantiated by its `IViewport` owner. 
-
-**TODO:** its functionality is not well defined and should be moved into the 
-viewport base class. Support for measuring tools should be moved to a special
-interactor.
-
-- contains:
-  - array of `MeasureTool`
-  - ref to `IViewport`
-  - `activeTracker_`
-  - owns a `Scene2D`
-  - weak ref to `UndoStack`
-  - cached `canvasToSceneFactor_`
-
-- contains logic to:
-  - pass commands to undostack (trivial)
-  - logic to locate `MeasureTool` in the HitTest
-  - OTOH, the meat of the measuring tool logic (highlighting etc..) is 
-    done in app-specific code (`VolumeSlicerWidget`)
-  - accept new Scene transform and notify listeners
-  - **the code that uses the interactor** (`HandleMousePress`) is only 
-    called by the new `WebAssemblyViewport` !!! **TODO** clean this mess
-
-### `IViewportInteractor`
-- must provide logic to respond to `CreateTracker`
-
-### `DefaultViewportInteractor`
-- provides Pan+Rotate+Zoom trackers
-
-### `WebGLViewportsRegistry`
-
-This class is a singleton (accessible through `GetWebGLViewportsRegistry()` 
-that deals with context losses in the WebGL contexts.
-
-You use it by creating a WebGLViewport in the following fashion:
-
-```
-boost::shared_ptr<OrthancStone::WebGLViewport> viewport(
-  OrthancStone::GetWebGLViewportsRegistry().Add(canvasId));
-```
-
-## Source data related
-
-### `IVolumeSlicer`
-
-A very simple interface with a single method:
-`IVolumeSlicer::IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)`
-
-### `IVolumeSlicer::IExtractedSlice`
-
-On a slice has been extracted from a volume by an `IVolumeSlicer`, it can
-report its *revision number*. 
-
-If another call to `ExtractSlice` with the same cutting plane is made, but 
-the returned slice revision is different, it means that the volume has 
-changed and the scene layer must be refreshed.
-
-Please see `VolumeSceneLayerSource::Update` to check how this logic is 
-implemented.
-
-
-### `OrthancSeriesVolumeProgressiveLoader`
-
-This class implements `IVolumeSlicer` (and `IObservable`) and can be used to 
-load a volume stored in a Dicom series on an Orthanc server.
-
-Over the course of the series loading, various notifications are sent:
-
-The first one is `OrthancStone::DicomVolumeImage::GeometryReadyMessage` that
-is sent when the volume extent and geometric properties are known.
-
-Then, as slices get loaded and the volume is filled,  
-`OrthancStone::DicomVolumeImage::ContentUpdatedMessage` are sent.
-
-Once all the highest-quality slices have been loaded, the 
-`OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality` 
-notification is sent.
-
-Please note that calling `ExtractSlice` *before* the geometry is loaded will
-yield an instance of `InvalidSlice` that cannot be used to create a layer.
-
-On the other hand, 
-
-### `VolumeSceneLayerSource`
-
-This class makes the bridge between a volume (supplied by an `IVolumeSlicer`
-interface) and a `Scene2D`. 
-
-Please note that the bulk of the work is done the objects implementing 
-`IVolumeSlicer` and this object merely connects things together.
-
-For instance, deciding whether an image (texture) or vector (polyline) layer
-is done by the `IVolumeSlicer` implementation.
-
-- contains:
-  - reference to Scene2D
-  - `layerIndex_` (fixed at ctor) that is the index, in the Scene2D layer 
-    stack, of the layer that will be created/updated
-  - `IVolumeSlicer`
-
-- contains logic to:
-  - extract a slice from the slicer and set/refresh the Scene2D layer at 
-    the supplied `layerIndex_`
-  - refresh this based on the slice revision or configuration revision
-  - accept a configuration that will be applied to the layer
-  - the `Update()` method will 
-
-## Updates and the configurators
-
-`ISceneLayer` does not expose mutable methods. 
-
-The way to change a layer once it has been created is through configurator 
-objets.
-
-If you plan to set (even only once) or modify some layer properties after 
-layer creation, you need to create a matching configurator objet.
-
-For instance, in the `VolumeSceneLayerSource`, the `SetConfigurator` method
-will store a `ILayerStyleConfigurator* configurator_`.
-
-In the `OrthancView` ctor, you can see how it is used:
-
-```
-std::unique_ptr<GrayscaleStyleConfigurator> style(
-  new GrayscaleStyleConfigurator);
-
-style->SetLinearInterpolation(true);
-
-...<some more code>...
-
-std::unique_ptr<LookupTableStyleConfigurator> config(
-  new LookupTableStyleConfigurator);
-
-config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
-
-```
-
-The configurator type are created according to the type of layer.¸
-
-Later, in `VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane)`, 
-if the cutting plane has **not** changed and if the layer revision has **not**
-changed, we test `configurator_->GetRevision() != lastConfiguratorRevision_` 
-and, if different, we call `configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));`
-
-This allows to change layer properties that do not depend on the layer model
-contents.
-
-On the other hand, if the layer revision has changed, when compared to the 
-last time it has been rendered (stored in `lastRevision_`), then we need to 
-ask the slice to create a brand new layer.
-
-Another way to see it is that layer rendering depend on model data and view 
-data. The model data is not mutable in the layer and, if the model changes, the 
-layer must be recreated.
-
-If only the view properties change (the configurator), we call ApplyStyle 
-(that **will** mutate some of the layer internals)
-
-Please note that the renderer does **not** know about the configurator : the 
-renderer uses properies in the layer and does not care whether those have 
-been set once at construction time or at every frame (configuration time).
-
-
-## Cookbook
-
-### Simple application
-
-#### Building
-
-In order to create a Stone application, you need to:
-
-- CMake-based application:
-    ```
-    include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake)
-    ```
-  with this library target that you have to define:
-    ```
-    add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES})
-    ```
-  then link with this library:
-    ```
-    target_link_libraries(MyStoneApplication OrthancStone)
-    ```
-
-Building is supported with emscripten, Visual C++ (>= 9.0), gcc...
-
-emscripten recommended version >= 1.38.41
-
-These are very rough guidelines. See the `Samples` folder for actual examples.
-
-#### Structure
-
-The code requires a loader (object that )
-
-Initialize:
-
-```
-Orthanc::Logging::Initialize();
-Orthanc::Logging::EnableInfoLevel(true);
-```
-Call, in WASM:
-```
-DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
-```
-
-# Notes
-
-- It is NOT possible to abandon the existing loaders : they contain too much loader-specific getters
-
-
-
--- a/OrthancStone/Resources/CMake/LinuxStandardBaseUic.py	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-
-import subprocess
-import sys
-
-if len(sys.argv) <= 1:
-    sys.stderr.write('Please provide arguments for uic\n')
-    sys.exit(-1)
-
-path = ''
-pos = 1
-while pos < len(sys.argv):
-    if sys.argv[pos].startswith('-'):
-        pos += 2
-    else:
-        path = sys.argv[pos]
-        break
-
-if len(path) == 0:
-    sys.stderr.write('Unable to find the input file in the arguments to uic\n')
-    sys.exit(-1)
-
-with open(path, 'r') as f:
-    lines = f.read().split('\n')
-    if (len(lines) > 1 and
-        lines[0].startswith('<?')):
-        content = '\n'.join(lines[1:])
-    else:
-        content = '\n'.join(lines)
-        
-# Remove the source file from the arguments
-args = sys.argv[1:pos] + sys.argv[pos+1:]
-
-p = subprocess.Popen([ '/opt/lsb/bin/uic' ] + args,
-                     stdin = subprocess.PIPE)
-p.communicate(input = content)
--- a/OrthancStone/Resources/Colormaps/GenerateColormaps.py	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/python
-
-import array
-import matplotlib.pyplot as plt
-
-def GenerateColormap(name):
-    colormap = []
-
-    for gray in range(256):
-        if name == 'red':
-            color = (gray / 255.0, 0, 0)
-        elif name == 'green':
-            color = (0, gray / 255.0, 0)
-        elif name == 'blue':
-            color = (0, 0, gray / 255.0)
-        else:
-            color = plt.get_cmap(name) (gray)
-
-        colormap += map(lambda k: int(round(color[k] * 255)), range(3))
-
-    colormap[0] = 0
-    colormap[1] = 0
-    colormap[2] = 0
-
-    return array.array('B', colormap).tostring()
-
-
-for name in [ 
-        'hot', 
-        'jet', 
-        'blue',
-        'green',
-        'red',
-]:
-    with open('%s.lut' % name, 'w') as f:
-        f.write(GenerateColormap(name))
Binary file OrthancStone/Resources/Colormaps/blue.lut has changed
Binary file OrthancStone/Resources/Colormaps/green.lut has changed
Binary file OrthancStone/Resources/Colormaps/hot.lut has changed
Binary file OrthancStone/Resources/Colormaps/jet.lut has changed
Binary file OrthancStone/Resources/Colormaps/red.lut has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Documentation/Conventions.txt	Thu Oct 22 16:17:10 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<>".
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Resources/Documentation/stone-object-model-reference.md	Thu Oct 22 16:17:10 2020 +0200
@@ -0,0 +1,429 @@
+## Scene2D and viewport-related object reference
+
+### `Scene2D` 
+
+Represents a collection of layers that display 2D data.
+
+These layers must implement `ISceneLayer`
+
+The layers must be created externally and set to a specific Z-order index 
+with the `SetLayer` method.
+
+The `Scene2D` object merely acts as a layer container. It has no rendering 
+or layer creation facility on its own.
+
+The `Scene2D` contains an `AffineTransform2D` structure that defines how 
+the various layer item coordinates are transformed before being displayed 
+on the viewport (aka canvas)
+
+It is up to each layer type-specific renderer to choose how this transformation
+is used. See the various kinds of layer below for more details.
+
+Examining the `Scene2D` contents can be done either by implementing the 
+`Scene2D::IVisitor` interface and calling `Apply(IVisitor& visitor)` or by 
+iterating between `GetMinDepth()` and `GetMaxDepth()` and calling the 
+`ISceneLayer& GetLayer(int depth)` getter.
+
+### `ISceneLayer` 
+
+Interface that must be implemented by `Scene2D` layers. This is a closed list
+that, as of 2020-03, contains:
+
+```
+  Type_InfoPanel,
+  Type_ColorTexture,
+  Type_Polyline,
+  Type_Text,
+  Type_FloatTexture,
+  Type_LookupTableTexture
+```
+
+Please note that this interface mandates the implementation of a `GetRevision`
+method returning an `uint64_t`. 
+
+The idea is that when a model gets converted to a set of `ISceneLayer` 
+instances, changes in the model that result in changes to the layers must 
+increase the revision number of these layers.
+
+That allows the rendering process to safely assume that a given layers whose
+revision does not change hasn't been modified (this helps with caching).
+
+Every mutable method in `ISceneLayer` instances that possibly change the visual
+representation of an `ISceneLayer` must increase this revision number.
+
+### Implementation: `FloatTextureSceneLayer`
+
+Layer that renders an `Orthanc::ImageAccessor` object that must be convertible
+to `Float32` image.
+
+The constructor only uses the image accessor to perform a copy. It can safely 
+be deleted afterwards.
+
+The input values are mapped to the output values by taking into account various
+properties that can be modified with:
+
+- `SetWindowing`: uses windowing presets like "bone" or "lung"
+- `SetCustomWindowing`: with manual window center and width
+- `SetInverted`: toggles black <-> white inversion after windowing
+- `SetApplyLog`: uses a non-linear response curve described in 
+  https://theailearner.com/2019/01/01/log-transformation/ that expands contrast
+  in dark areas while compressing contrast in bright ones. This is **not** 
+  implemented in the OpenGL renderer!
+
+The corresponding renderers are `OpenGLFloatTextureRenderer` and 
+`CairoFloatTextureRenderer`. The scene transformation is applied during
+rendering.
+
+### Implementation: `ColorTextureSceneLayer`
+
+Layer that renders an `Orthanc::ImageAccessor` object an RGBA image (alpha must
+be premultiplied). 
+
+The constructor only uses the image accessor to perform a copy. It can safely 
+be deleted afterwards.
+
+The corresponding renderers are `OpenGLColorTextureRenderer` and 
+`CairoColorTextureRenderer`. The scene transformation is applied during
+rendering.
+
+### Implementation: `LookupTableTextureSceneLayer`
+
+Layer that renders an `Orthanc::ImageAccessor` object that must be convertible
+to `Float32` image.
+
+The constructor only uses the image accessor to perform a copy. It can safely 
+be deleted afterwards.
+
+The final on-screen color of each pixel is determined by passing the input 
+`Float32` value through a 256-entry look-up table (LUT) that can be passed as 
+an array of either 256 x 3 bytes (for opaque RGB colors) or 256 x 4 bytes (for
+RGBA pixels). The LUT is not specified at construction time, but with 
+calls to `SetLookupTable` or `SetLookupTableGrayscale` (that fills the LUT 
+with a gradient from black to white, fully opaque)
+
+The range of input values that is mapped to the entirety of the LUT is, by
+default, the full image range, but can be customized with `SetRange`.
+
+The corresponding renderers are `OpenGLLookupTableTextureRenderer` and
+`CairoLookupTableTextureRenderer`. The scene transformation is applied during
+rendering.
+
+### Implementation: `PolylineSceneLayer`
+
+Layer that renders vector-based polygonal lines. 
+
+Polylines can be added with the `AddChain` method, that accepts a `Chain`, that
+is a typedef to `std::vector<ScenePoint2D>`, a flag to specify whether the 
+chain must be automatically close (last point of the vector connected to the
+first one) and the chain color (a `Color` structure).
+
+Please note that the line thickness is, contrary to the color, specified 
+per-chain but rather per-layer. 
+
+If you need multiple line thicknesses, multiple `PolylineSceneLayer` must be
+created.
+
+The corresponding renderers are `OpenGLAdvancedPolylineRenderer` and
+`CairoPolylineRenderer`. The scene transformation is applied during
+rendering.
+
+### Implementation: `TextSceneLayer`
+
+This layers renders a paragraph of text. 
+
+The inputs to the layer can be changed after creation and are:
+- The text iself, supplied as an UTF-8 encoded string in `SetText`
+- The font used for rendering, set by `SetFontIndex`.
+- The text anchoring, through `SetAnchor`: the text can be anchored to 
+  various positions, such as top lef, center, bottom center,... These 
+  various anchors are part of the `BitmapAnchor` enumeration.
+- The text position, relative to its anchor, through `SetPosition`.
+
+The font is supplied as an index. This is an index in the set of fonts 
+that has been registered in the viewport compositor. The following code 
+shows how to set such a font:
+
+```
+std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_.Lock());
+lock->GetCompositor().SetFont(0, 
+                              Orthanc::EmbeddedResources::UBUNTU_FONT, 
+                              32, Orthanc::Encoding_Latin1);
+// where 32 is the font size in pixels
+```
+
+This call uses the embedded `UBUNTU_FONT` resource that has been defined in
+the `CMakeLists.txt` file with:
+
+```
+EmbedResources(
+  UBUNTU_FONT  ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf
+)
+```
+
+Please note that you must supply a font: there is no default font provided by
+the OpenGL or Cairo compositors.
+
+The corresponding renderers are `OpenGLTextRenderer` and
+`CairoTextRenderer`. The scene transformation is not applied during rendering,
+because the text anchoring, position and scaling are computed relative to the
+viewport/canvas.
+
+### Implementation: `InfoPanelSceneLayer`
+
+This layer is designed to display an image, supplied through an 
+`Orthanc::ImageAccessor` reference (only used at construction time).
+
+The image is not transformed according to the normal layer transformation but 
+is rather positioned relative to the canvas, with the same mechanism as the
+`TextSceneLayer` described above.
+
+The image position is specified with the sole means of the `SetAnchor` method.
+
+The corresponding renderers are `OpenGLInfoPanelRenderer` and 
+`CairoInfoPanelRenderer`.
+
+### `IViewport`
+
+https://bitbucket.org/sjodogne/orthanc-stone/src/broker/Framework/Viewport/IViewport.h
+
+(**not** the one in `Deprecated`)
+- Implemented by classes that:
+  - manage the on-screen display of a `Scene2D` trough a compositor.
+  - Own the `ICompositor` object that performs the rendering. 
+  - Own the `Scene2D` (TODO: currently through `ViewportController` --> `Scene2D`)
+  - Provide a `Lock` method that returns a RAII, that must be kept alive when 
+    modifying the underlying objects (controller, compositor, scene), but not 
+    longer.
+
+#### Implementation: `SdlOpenGLViewport`
+- Implementation of a viewport rendered on a SDL window, that uses OpenGL for 
+  rendering.
+- Instantiating this object creates an SDL window. Automatic scaling for hiDPI
+  displays can be toggled on or off. 
+
+#### Implementation: `WebGLViewport`
+- Implementation of a viewport rendered on a DOM canvas, that uses OpenGL for 
+  rendering.
+- Contrary to the SDL OpenGL viewport, the canvas must already be existing 
+  when the ctor is called.
+
+### `ICompositor`
+The interface providing a rendering service for `Scene2D` objects. 
+
+**Subclasses:** `CairoCompositor`, `OpenGLCompositor`
+ 
+You do not need to create compositor instances. They are created for you when
+instantiating a viewport.
+
+### `ViewportController`
+This concrete class is instantiated by its `IViewport` owner. 
+
+**TODO:** its functionality is not well defined and should be moved into the 
+viewport base class. Support for measuring tools should be moved to a special
+interactor.
+
+- contains:
+  - array of `MeasureTool`
+  - ref to `IViewport`
+  - `activeTracker_`
+  - owns a `Scene2D`
+  - weak ref to `UndoStack`
+  - cached `canvasToSceneFactor_`
+
+- contains logic to:
+  - pass commands to undostack (trivial)
+  - logic to locate `MeasureTool` in the HitTest
+  - OTOH, the meat of the measuring tool logic (highlighting etc..) is 
+    done in app-specific code (`VolumeSlicerWidget`)
+  - accept new Scene transform and notify listeners
+  - **the code that uses the interactor** (`HandleMousePress`) is only 
+    called by the new `WebAssemblyViewport` !!! **TODO** clean this mess
+
+### `IViewportInteractor`
+- must provide logic to respond to `CreateTracker`
+
+### `DefaultViewportInteractor`
+- provides Pan+Rotate+Zoom trackers
+
+### `WebGLViewportsRegistry`
+
+This class is a singleton (accessible through `GetWebGLViewportsRegistry()` 
+that deals with context losses in the WebGL contexts.
+
+You use it by creating a WebGLViewport in the following fashion:
+
+```
+boost::shared_ptr<OrthancStone::WebGLViewport> viewport(
+  OrthancStone::GetWebGLViewportsRegistry().Add(canvasId));
+```
+
+## Source data related
+
+### `IVolumeSlicer`
+
+A very simple interface with a single method:
+`IVolumeSlicer::IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)`
+
+### `IVolumeSlicer::IExtractedSlice`
+
+On a slice has been extracted from a volume by an `IVolumeSlicer`, it can
+report its *revision number*. 
+
+If another call to `ExtractSlice` with the same cutting plane is made, but 
+the returned slice revision is different, it means that the volume has 
+changed and the scene layer must be refreshed.
+
+Please see `VolumeSceneLayerSource::Update` to check how this logic is 
+implemented.
+
+
+### `OrthancSeriesVolumeProgressiveLoader`
+
+This class implements `IVolumeSlicer` (and `IObservable`) and can be used to 
+load a volume stored in a Dicom series on an Orthanc server.
+
+Over the course of the series loading, various notifications are sent:
+
+The first one is `OrthancStone::DicomVolumeImage::GeometryReadyMessage` that
+is sent when the volume extent and geometric properties are known.
+
+Then, as slices get loaded and the volume is filled,  
+`OrthancStone::DicomVolumeImage::ContentUpdatedMessage` are sent.
+
+Once all the highest-quality slices have been loaded, the 
+`OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality` 
+notification is sent.
+
+Please note that calling `ExtractSlice` *before* the geometry is loaded will
+yield an instance of `InvalidSlice` that cannot be used to create a layer.
+
+On the other hand, 
+
+### `VolumeSceneLayerSource`
+
+This class makes the bridge between a volume (supplied by an `IVolumeSlicer`
+interface) and a `Scene2D`. 
+
+Please note that the bulk of the work is done the objects implementing 
+`IVolumeSlicer` and this object merely connects things together.
+
+For instance, deciding whether an image (texture) or vector (polyline) layer
+is done by the `IVolumeSlicer` implementation.
+
+- contains:
+  - reference to Scene2D
+  - `layerIndex_` (fixed at ctor) that is the index, in the Scene2D layer 
+    stack, of the layer that will be created/updated
+  - `IVolumeSlicer`
+
+- contains logic to:
+  - extract a slice from the slicer and set/refresh the Scene2D layer at 
+    the supplied `layerIndex_`
+  - refresh this based on the slice revision or configuration revision
+  - accept a configuration that will be applied to the layer
+  - the `Update()` method will 
+
+## Updates and the configurators
+
+`ISceneLayer` does not expose mutable methods. 
+
+The way to change a layer once it has been created is through configurator 
+objets.
+
+If you plan to set (even only once) or modify some layer properties after 
+layer creation, you need to create a matching configurator objet.
+
+For instance, in the `VolumeSceneLayerSource`, the `SetConfigurator` method
+will store a `ILayerStyleConfigurator* configurator_`.
+
+In the `OrthancView` ctor, you can see how it is used:
+
+```
+std::unique_ptr<GrayscaleStyleConfigurator> style(
+  new GrayscaleStyleConfigurator);
+
+style->SetLinearInterpolation(true);
+
+...<some more code>...
+
+std::unique_ptr<LookupTableStyleConfigurator> config(
+  new LookupTableStyleConfigurator);
+
+config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
+
+```
+
+The configurator type are created according to the type of layer.¸
+
+Later, in `VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane)`, 
+if the cutting plane has **not** changed and if the layer revision has **not**
+changed, we test `configurator_->GetRevision() != lastConfiguratorRevision_` 
+and, if different, we call `configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));`
+
+This allows to change layer properties that do not depend on the layer model
+contents.
+
+On the other hand, if the layer revision has changed, when compared to the 
+last time it has been rendered (stored in `lastRevision_`), then we need to 
+ask the slice to create a brand new layer.
+
+Another way to see it is that layer rendering depend on model data and view 
+data. The model data is not mutable in the layer and, if the model changes, the 
+layer must be recreated.
+
+If only the view properties change (the configurator), we call ApplyStyle 
+(that **will** mutate some of the layer internals)
+
+Please note that the renderer does **not** know about the configurator : the 
+renderer uses properies in the layer and does not care whether those have 
+been set once at construction time or at every frame (configuration time).
+
+
+## Cookbook
+
+### Simple application
+
+#### Building
+
+In order to create a Stone application, you need to:
+
+- CMake-based application:
+    ```
+    include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake)
+    ```
+  with this library target that you have to define:
+    ```
+    add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES})
+    ```
+  then link with this library:
+    ```
+    target_link_libraries(MyStoneApplication OrthancStone)
+    ```
+
+Building is supported with emscripten, Visual C++ (>= 9.0), gcc...
+
+emscripten recommended version >= 1.38.41
+
+These are very rough guidelines. See the `Samples` folder for actual examples.
+
+#### Structure
+
+The code requires a loader (object that )
+
+Initialize:
+
+```
+Orthanc::Logging::Initialize();
+Orthanc::Logging::EnableInfoLevel(true);
+```
+Call, in WASM:
+```
+DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
+```
+
+# Notes
+
+- It is NOT possible to abandon the existing loaders : they contain too much loader-specific getters
+
+
+
--- a/OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +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 "CurlOrthancConnection.h"
-
-#if ORTHANC_ENABLE_CURL == 1
-
-#include "../../Resources/Orthanc/Core/HttpClient.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  void CurlOrthancConnection::RestApiGet(std::string& result,
-                                         const std::string& uri)
-  {
-    /**
-     * TODO: This function sometimes crashes if compiled with
-     * MinGW-W64 (32 bit) in Release mode, on Windows XP. Introducing
-     * a mutex here fixes the issue. Not sure of what is the
-     * culprit. Maybe a bug in a old version of MinGW?
-     **/
-
-    Orthanc::HttpClient client(parameters_, uri);
-
-    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
-    client.SetRedirectionFollowed(false);  
-   
-    if (!client.Apply(result))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-
-
-  void CurlOrthancConnection::RestApiPost(std::string& result,
-                                          const std::string& uri,
-                                          const std::string& body)
-  {
-    Orthanc::HttpClient client(parameters_, uri);
-
-    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
-    client.SetRedirectionFollowed(false);  
-
-    client.SetBody(body);
-    client.SetMethod(Orthanc::HttpMethod_Post);
-   
-    if (!client.Apply(result))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-}
-
-#endif
--- a/OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +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 "IOrthancConnection.h"
-
-#if ORTHANC_ENABLE_CURL == 1
-
-#include "../../Resources/Orthanc/Core/WebServiceParameters.h"
-
-namespace OrthancStone
-{
-  class CurlOrthancConnection : public IOrthancConnection
-  {
-  private:
-    Orthanc::WebServiceParameters  parameters_;
-
-  public:
-    CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) :
-      parameters_(parameters)
-    {
-    }
-
-    const Orthanc::WebServiceParameters& GetParameters() const
-    {
-      return parameters_;
-    }
-
-    virtual void RestApiGet(std::string& result,
-                            const std::string& uri);
-
-    virtual void RestApiPost(std::string& result,
-                             const std::string& uri,
-                             const std::string& body);
-  };
-}
-
-#endif
--- a/OrthancStone/Resources/Graveyard/Messaging/IOrthancConnection.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +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 "../Toolbox/IThreadSafety.h"
-
-#include <string>
-
-namespace OrthancStone
-{
-  // Derived classes must be thread-safe
-  class IOrthancConnection : public IThreadSafe
-  {
-  public:
-    virtual void RestApiGet(std::string& result,
-                            const std::string& uri) = 0;
-
-    virtual void RestApiPost(std::string& result,
-                             const std::string& uri,
-                             const std::string& body) = 0;
-  };
-}
--- a/OrthancStone/Resources/Graveyard/ReferenceLineFactory.cpp	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +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 "ReferenceLineFactory.h"
-
-#include "LineLayerRenderer.h"
-
-namespace OrthancStone
-{
-  ReferenceLineFactory::ReferenceLineFactory(SliceViewerWidget& owner,
-                                             SliceViewerWidget& sibling) :
-    owner_(owner),
-    sibling_(sibling),
-    hasLayerIndex_(false)
-  {
-    style_.SetColor(0, 255, 0);
-    slice_ = sibling.GetSlice();
-    sibling_.Register(*this);
-  }
-
-
-  void ReferenceLineFactory::NotifySliceContentChange(const SliceViewerWidget& source,
-                                               const SliceGeometry& slice)
-  {
-    if (&source == &sibling_)
-    {
-      SetSlice(slice);
-    }
-  }
-
-
-  void ReferenceLineFactory::SetLayerIndex(size_t layerIndex)
-  {
-    hasLayerIndex_ = true;
-    layerIndex_ = layerIndex;
-  }
-
-
-  void ReferenceLineFactory::SetStyle(const RenderStyle& style)
-  {
-    style_ = style;
-  }
-
-
-  RenderStyle ReferenceLineFactory::GetRenderStyle()
-  {
-    return style_;
-  }
-
-
-  void ReferenceLineFactory::SetSlice(const SliceGeometry& slice)
-  {
-    slice_ = slice;
-
-    if (hasLayerIndex_)
-    {
-      owner_.InvalidateLayer(layerIndex_);
-    }
-  }
-
-
-  ILayerRenderer* ReferenceLineFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
-  {
-    Vector p, d;
-
-    // Compute the line of intersection between the two slices
-    if (!GeometryToolbox::IntersectTwoPlanes(p, d, 
-                                             slice_.GetOrigin(), slice_.GetNormal(),
-                                             viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
-    {
-      // The two slice are parallel, don't try and display the intersection
-      return NULL;
-    }
-
-    double x1, y1, x2, y2;
-    viewportSlice.ProjectPoint(x1, y1, p);
-    viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
-
-    double sx1, sy1, sx2, sy2;
-    owner_.GetView().GetSceneExtent(sx1, sy1, sx2, sy2);
-        
-    if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, 
-                                             x1, y1, x2, y2,
-                                             sx1, sy1, sx2, sy2))
-    {
-      std::unique_ptr<ILayerRenderer> layer(new LineLayerRenderer(x1, y1, x2, y2));
-      layer->SetLayerStyle(style_);
-      return layer.release();
-    }
-    else
-    {
-      // Parallel slices
-      return NULL;
-    }
-  }
-
-
-  ISliceableVolume& ReferenceLineFactory::GetSourceVolume() const
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void ReferenceLineFactory::Configure(SliceViewerWidget& a,
-                                       SliceViewerWidget& b)
-  {
-    {
-      size_t layerIndex;
-      ILayerRendererFactory& factory = a.AddLayer(layerIndex, new ReferenceLineFactory(a, b));
-      dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex);
-    }
-
-    {
-      size_t layerIndex;
-      ILayerRendererFactory& factory = b.AddLayer(layerIndex, new ReferenceLineFactory(b, a));
-      dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex);
-    }
-  }
-}
--- a/OrthancStone/Resources/Graveyard/ReferenceLineFactory.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +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 "../Widgets/SliceViewerWidget.h"
-
-namespace OrthancStone
-{
-  class ReferenceLineFactory : 
-    public ILayerRendererFactory,
-    public SliceViewerWidget::ISliceObserver
-  {
-  private:
-    SliceViewerWidget&   owner_;
-    SliceViewerWidget&   sibling_;
-    SliceGeometry        slice_;
-    RenderStyle          style_;
-    bool                 hasLayerIndex_;
-    size_t               layerIndex_;
-
-      
-  public:
-    ReferenceLineFactory(SliceViewerWidget& owner,
-                         SliceViewerWidget& sibling);
-
-    virtual void NotifySliceContentChange(const SliceViewerWidget& source,
-                                   const SliceGeometry& slice);
-
-    void SetLayerIndex(size_t layerIndex);
-
-    void SetStyle(const RenderStyle& style);
-
-    RenderStyle GetRenderStyle();
-
-    void SetSlice(const SliceGeometry& slice);
-
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
-                           const SliceGeometry& viewportSlice)
-    {
-      return false;
-    }
-
-    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
-
-    virtual bool HasSourceVolume() const
-    {
-      return false;
-    }
-
-    virtual ISliceableVolume& GetSourceVolume() const;
-
-    static void Configure(SliceViewerWidget& a,
-                          SliceViewerWidget& b);
-  };
-}
--- a/OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.cpp	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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 "BinarySemaphore.h"
-
-namespace OrthancStone
-{
-  BinarySemaphore::BinarySemaphore() : 
-    proceed_(false)
-  {
-  }
-
-  void BinarySemaphore::Signal()
-  {
-    //boost::mutex::scoped_lock lock(mutex_);
-
-    proceed_ = true;
-    condition_.notify_one(); 
-  }
-
-  void BinarySemaphore::Wait()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    while (!proceed_)
-    {
-      condition_.wait(lock);
-    }
-
-    proceed_ = false;
-  }
-}
--- a/OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +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 <boost/thread/mutex.hpp>
-#include <boost/thread/condition.hpp>
-
-namespace OrthancStone
-{
-  class BinarySemaphore : public boost::noncopyable
-  {
-  private:
-    bool proceed_;
-    boost::mutex mutex_;
-    boost::condition_variable condition_;
-
-  public:
-    explicit BinarySemaphore();
-
-    void Signal();
-
-    void Wait();
-  };
-}
--- a/OrthancStone/Resources/Graveyard/Threading/IThreadSafety.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +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 <boost/noncopyable.hpp>
-
-namespace OrthancStone
-{
-  /**
-   * Dummy interface to explicitely tag the interfaces whose derived
-   * class must be thread-safe. The different methods of such classes
-   * might be simlultaneously invoked by several threads, and should
-   * be properly protected by mutexes.
-   **/
-  class IThreadSafe : public boost::noncopyable
-  {
-  public:
-    virtual ~IThreadSafe()
-    {
-    }
-  };
-
-
-  /**
-   * Dummy interface to explicitely tag the interfaces that are NOT
-   * expected to be thread-safe. The Orthanc Stone framework ensures
-   * that at most one method of such classes will be invoked at a
-   * given time. Such classes are automatically protected by the
-   * Orthanc Stone framework wherever required.
-   **/
-  class IThreadUnsafe : public boost::noncopyable
-  {
-  public:
-    virtual ~IThreadUnsafe()
-    {
-    }
-  };
-}
--- a/OrthancStone/Resources/Graveyard/Threading/SdlBuffering.cpp	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +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 "SdlBuffering.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  SdlBuffering::SdlBuffering() :
-    sdlSurface_(NULL),
-    pendingFrame_(false)
-  {
-  }
-
-
-  SdlBuffering::~SdlBuffering()
-  {
-    if (sdlSurface_)
-    {
-      SDL_FreeSurface(sdlSurface_);
-    }
-  }
-
-
-  void SdlBuffering::SetSize(unsigned int width,
-                             unsigned int height,
-                             IViewport& viewport)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    viewport.SetSize(width, height);
-
-    if (offscreenSurface_.get() == NULL ||
-        offscreenSurface_->GetWidth() != width ||
-        offscreenSurface_->GetHeight() != height)
-    {
-      offscreenSurface_.reset(new CairoSurface(width, height));
-    }
-
-    if (onscreenSurface_.get() == NULL ||
-        onscreenSurface_->GetWidth() != width ||
-        onscreenSurface_->GetHeight() != height)
-    {
-      onscreenSurface_.reset(new CairoSurface(width, height));
-
-      // TODO Big endian?
-      static const uint32_t rmask = 0x00ff0000;
-      static const uint32_t gmask = 0x0000ff00;
-      static const uint32_t bmask = 0x000000ff;
-
-      if (sdlSurface_)
-      {
-        SDL_FreeSurface(sdlSurface_);
-      }
-
-      sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32,
-                                             onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0);
-      if (!sdlSurface_)
-      {
-        LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }    
-    }
-
-    pendingFrame_ = false;
-  }
-
-
-  bool SdlBuffering::RenderOffscreen(IViewport& viewport)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (offscreenSurface_.get() == NULL)
-    {
-      return false;
-    }
-
-    Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor();
-
-    if (viewport.Render(target) &&
-        !pendingFrame_)
-    {
-      pendingFrame_ = true;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  void SdlBuffering::SwapToScreen(SdlWindow& window)
-  {
-    if (!pendingFrame_ ||
-        offscreenSurface_.get() == NULL ||
-        onscreenSurface_.get() == NULL)
-    {
-      return;
-    }
-
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      onscreenSurface_->Copy(*offscreenSurface_);
-    }
-    
-    window.Render(sdlSurface_);
-    pendingFrame_ = false;
-  }
-}
-
-#endif
--- a/OrthancStone/Resources/Graveyard/Threading/SdlBuffering.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +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
-
-#include "SdlWindow.h"
-#include "../../Framework/Viewport/CairoSurface.h"
-#include "../../Framework/Viewport/IViewport.h"
-
-#include <boost/thread/mutex.hpp>
-
-namespace OrthancStone
-{
-  class SdlBuffering : public boost::noncopyable
-  {
-  private:
-    boost::mutex                 mutex_;
-    std::unique_ptr<CairoSurface>  offscreenSurface_;
-    std::unique_ptr<CairoSurface>  onscreenSurface_;
-    SDL_Surface*                 sdlSurface_;
-    bool                         pendingFrame_;
-
-  public:
-    SdlBuffering();
-
-    ~SdlBuffering();
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 IViewport& viewport);
-
-    // Returns "true" if a new refresh of the display should be
-    // triggered afterwards
-    bool RenderOffscreen(IViewport& viewport);
-
-    void SwapToScreen(SdlWindow& window);
-  };
-}
-
-#endif
--- a/OrthancStone/Resources/Graveyard/Threading/SharedValue.h	Wed Oct 21 18:00:34 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 <boost/noncopyable.hpp>
-#include <boost/thread/mutex.hpp>
-
-namespace OrthancStone
-{
-  // A value that is protected by a mutex, in order to be shared by
-  // multiple threads
-  template <typename T>
-  class SharedValue : public boost::noncopyable
-  {
-  private:
-    boost::mutex   mutex_;
-    T              value_;
-    
-  public:
-    class Locker : public boost::noncopyable
-    {
-    private:
-      boost::mutex::scoped_lock  lock_;
-      T&                         value_;
-
-    public:
-      Locker(SharedValue& shared) :
-        lock_(shared.mutex_),
-        value_(shared.value_)
-      {
-      }
-
-      T& GetValue() const
-      {
-        return value_;
-      }
-    };
-  };
-}
--- a/OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.cpp	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,304 +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 "DicomDataset.h"
-
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/Toolbox.h"
-
-#include <boost/lexical_cast.hpp>
-#include <json/value.h>
-#include <json/reader.h>
-
-namespace OrthancStone
-{
-  static uint16_t GetCharValue(char c)
-  {
-    if (c >= '0' && c <= '9')
-      return c - '0';
-    else if (c >= 'a' && c <= 'f')
-      return c - 'a' + 10;
-    else if (c >= 'A' && c <= 'F')
-      return c - 'A' + 10;
-    else
-      return 0;
-  }
-
-
-  static uint16_t GetHexadecimalValue(const char* c)
-  {
-    return ((GetCharValue(c[0]) << 12) + 
-            (GetCharValue(c[1]) << 8) + 
-            (GetCharValue(c[2]) << 4) + 
-            GetCharValue(c[3]));
-  }
-
-
-  static DicomDataset::Tag ParseTag(const std::string& tag)
-  {
-    if (tag.size() == 9 &&
-        isxdigit(tag[0]) &&
-        isxdigit(tag[1]) &&
-        isxdigit(tag[2]) &&
-        isxdigit(tag[3]) &&
-        (tag[4] == '-' || tag[4] == ',') &&
-        isxdigit(tag[5]) &&
-        isxdigit(tag[6]) &&
-        isxdigit(tag[7]) &&
-        isxdigit(tag[8]))        
-    {
-      uint16_t group = GetHexadecimalValue(tag.c_str());
-      uint16_t element = GetHexadecimalValue(tag.c_str() + 5);
-      return std::make_pair(group, element);
-    }
-    else if (tag.size() == 8 &&
-             isxdigit(tag[0]) &&
-             isxdigit(tag[1]) &&
-             isxdigit(tag[2]) &&
-             isxdigit(tag[3]) &&
-             isxdigit(tag[4]) &&
-             isxdigit(tag[5]) &&
-             isxdigit(tag[6]) &&
-             isxdigit(tag[7]))        
-    {
-      uint16_t group = GetHexadecimalValue(tag.c_str());
-      uint16_t element = GetHexadecimalValue(tag.c_str() + 4);
-      return std::make_pair(group, element);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
-    }
-  }
-
-  void DicomDataset::Parse(const std::string& content)
-  {
-    Json::Value json;
-    Json::Reader reader;
-    if (!reader.parse(content, json))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
-    }
-
-    Parse(json);
-  }
-
-
-  void DicomDataset::Parse(const Json::Value& content)
-  {
-    if (content.type() != Json::objectValue)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
-    }
-
-    Json::Value::Members members = content.getMemberNames();
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      Tag tag = ParseTag(members[i]);
-
-      const Json::Value& item = content[members[i]];
-
-      if (item.type() != Json::objectValue ||
-          !item.isMember("Type") ||
-          !item.isMember("Value") ||
-          !item.isMember("Name") ||
-          item["Type"].type() != Json::stringValue ||
-          item["Name"].type() != Json::stringValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
-      }
-
-      if (item["Type"].asString() == "String")
-      {
-        if (item["Value"].type() == Json::stringValue)
-        {
-          values_[tag] = item["Value"].asString();
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);        
-        }
-      }
-    }
-  }
-
-
-  DicomDataset::DicomDataset(OrthancPlugins::IOrthancConnection& orthanc,
-                             const std::string& instanceId)
-  {
-    std::string content;
-    orthanc.RestApiGet(content, "/instances/" + instanceId + "/tags");
-
-    Parse(content);
-  }
-
-
-  std::string DicomDataset::GetStringValue(const Tag& tag) const
-  {
-    Values::const_iterator it = values_.find(tag);
-
-    if (it == values_.end())
-    {
-      LOG(ERROR) << "Trying to access a DICOM tag that is not set in a DICOM dataset";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
-    }
-    else
-    {
-      return it->second;
-    }
-  }
-
-
-  std::string DicomDataset::GetStringValue(const Tag& tag,
-                                           const std::string& defaultValue) const
-  {
-    Values::const_iterator it = values_.find(tag);
-
-    if (it == values_.end())
-    {
-      return defaultValue;
-    }
-    else
-    {
-      return it->second;
-    }
-  }
-
-
-  float DicomDataset::GetFloatValue(const Tag& tag) const
-  {
-    try 
-    {
-      return boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      LOG(ERROR) << "Trying to access a DICOM tag that is not a float";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  double DicomDataset::GetDoubleValue(const Tag& tag) const
-  {
-    try 
-    {
-      return boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      LOG(ERROR) << "Trying to access a DICOM tag that is not a float";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  int DicomDataset::GetIntegerValue(const Tag& tag) const
-  {
-    try 
-    {
-      return boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag)));
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      LOG(ERROR) << "Trying to access a DICOM tag that is not an integer";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  unsigned int DicomDataset::GetUnsignedIntegerValue(const Tag& tag) const
-  {
-    int v = GetIntegerValue(tag);
-
-    if (v < 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      return static_cast<unsigned int>(v);
-    }
-  }
-
-
-  void DicomDataset::GetVectorValue(Vector& vector, 
-                                    const Tag& tag) const
-  {
-    if (!GeometryToolbox::ParseVector(vector, Orthanc::Toolbox::StripSpaces(GetStringValue(tag))))
-    {
-      LOG(ERROR) << "Trying to access a DICOM tag that is not a vector";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  void DicomDataset::GetVectorValue(Vector& vector, 
-                                    const Tag& tag,
-                                    size_t expectedSize) const
-  {
-    GetVectorValue(vector, tag);
-
-    if (vector.size() != expectedSize)
-    {
-      LOG(ERROR) << "A vector in a DICOM tag has a bad size";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  void DicomDataset::Print() const
-  {
-    for (Values::const_iterator it = values_.begin(); it != values_.end(); ++it)
-    {
-      printf("%04x,%04x = [%s]\n", it->first.first, it->first.second, it->second.c_str());
-    }
-    printf("\n");
-  }
-
-
-  bool DicomDataset::IsGrayscale() const
-  {
-    std::string photometric = Orthanc::Toolbox::StripSpaces(GetStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION));
-
-    return (photometric == "MONOCHROME1" ||
-            photometric == "MONOCHROME2");
-  }
-
-
-  void DicomDataset::GetPixelSpacing(double& spacingX,
-                                     double& spacingY) const
-  {
-    if (HasTag(DICOM_TAG_PIXEL_SPACING))
-    {
-      Vector spacing;
-      GetVectorValue(spacing, DICOM_TAG_PIXEL_SPACING, 2);
-      spacingX = spacing[0];
-      spacingY = spacing[1];
-    }
-    else
-    {
-      spacingX = 1.0;
-      spacingY = 1.0;
-    }
-  }
-}
--- a/OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.h	Wed Oct 21 18:00:34 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +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 "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
-
-#include <map>
-#include <stdint.h>
-#include <json/value.h>
-
-namespace OrthancStone
-{
-  // This class is NOT thread-safe
-  // This is a lightweight alternative to Orthanc::DicomMap
-  class DicomDataset : public boost::noncopyable
-  {
-  public:
-    typedef std::pair<uint16_t, uint16_t>  Tag;
-
-  private:
-    typedef std::map<Tag, std::string>  Values;
-
-    Values  values_;
-
-    void Parse(const std::string& content);
-
-    void Parse(const Json::Value& content);
-
-  public:
-    DicomDataset(const std::string& content)
-    {
-      Parse(content);
-    }
-
-    DicomDataset(const Json::Value& content)
-    {
-      Parse(content);
-    }
-
-    DicomDataset(OrthancPlugins::IOrthancConnection& orthanc,
-                 const std::string& instanceId);
-
-    bool HasTag(const Tag& tag) const
-    {
-      return values_.find(tag) != values_.end();
-    }
-
-    std::string GetStringValue(const Tag& tag) const;
-
-    std::string GetStringValue(const Tag& tag,
-                               const std::string& defaultValue) const;
-
-    float GetFloatValue(const Tag& tag) const;
-
-    double GetDoubleValue(const Tag& tag) const;
-
-    int GetIntegerValue(const Tag& tag) const;
-
-    unsigned int GetUnsignedIntegerValue(const Tag& tag) const;
-
-    void GetVectorValue(Vector& vector, 
-                        const Tag& tag,
-                        size_t expectedSize) const;
-
-    void GetVectorValue(Vector& vector, 
-                        const Tag& tag) const;
-
-    void Print() const;
-
-    bool IsGrayscale() const;
-
-    void GetPixelSpacing(double& spacingX,
-                         double& spacingY) const;
-  };
-
-
-  static const DicomDataset::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011);
-  static const DicomDataset::Tag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
-  static const DicomDataset::Tag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
-  static const DicomDataset::Tag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
-  static const DicomDataset::Tag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
-  static const DicomDataset::Tag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030);
-  static const DicomDataset::Tag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052);
-  static const DicomDataset::Tag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053);
-  static const DicomDataset::Tag DICOM_TAG_ROWS(0x0028, 0x0010);
-  static const DicomDataset::Tag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050);
-  static const DicomDataset::Tag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
-  static const DicomDataset::Tag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
-  static const DicomDataset::Tag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
-}